/***************************************************************************
 *   Copyright (C) 2003 by liuspider                                       *
 *   liuspider@users.sourceforge.net                                       *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/
#define USE_SCIM_KDE_SETTINGS
#include "skimpluginmanager.h"
#include "skimglobalactions.h"
#include "utils/scimactions.h"

#include <qpopupmenu.h>
#include <kiconloader.h>
#include <khelpmenu.h>
#include <kapplication.h>

SkimGlobalActions::SkimGlobalActions(QObject *parent, const char *name)
: KActionCollection(0, parent, name), /*mAboutKDE(0),*/ mAboutApp(0), helpDialog(0) {
    kil = KGlobal::iconLoader();

    mc = (SkimPluginManager *)QObject::parent ();
    InputServer = mc->getInputServer();
    mHelpMenu = new KHelpMenu( 0 );
    connect(mHelpMenu,SIGNAL(showAboutApplication()), this, SLOT(aboutApp()));

    new KAction( KGuiItem( i18n( "Reload all modules" ), "reload" ),
                 "", this, SLOT(requestReloadAllModules()), this, "reload");
    new KAction( KGuiItem( i18n( "SCIM Help" ), "scim_help" ),
                 "", InputServer, SLOT(getHelp()), this, "scim_help");
    KStdAction::help(mHelpMenu, SLOT(appHelpActivated()), this);
    KStdAction::aboutKDE(mHelpMenu,SLOT(aboutKDE ()),this,"aboutkde");
    KStdAction::aboutApp(this,SLOT(aboutApp()),this,"aboutapp");
    connect( InputServer, SIGNAL(showHelp(const QString & ) ) , this, SLOT(showHelp(const QString & )));

    ServerAction = new ScimComboAction(i18n("Input Method"), kil->loadIcon("keyboard", KIcon::Small), 
      "", InputServer, SLOT(getFactoryList()), this,"change_server");
    connect(ServerAction, SIGNAL(itemActivated(const QString &)), 
      SLOT(changeFactory(const QString &)));

    connect( InputServer, SIGNAL( updateFactoryInfoReq( const QString &, const QString &) ), 
      SLOT( SetFactoryInfo( const QString &, const QString &) ) );

    connect( InputServer,
             SIGNAL(showFactoryMenu(std::vector<scim::String> &, std::vector<scim::String> &,
                                   std::vector<scim::String> &, std::vector<scim::String> &)) ,
             this,
             SLOT(receiveFactoryMenu(std::vector<scim::String> &, std::vector<scim::String> &,
                                    std::vector<scim::String> &, std::vector<scim::String> &)));

//     connect(mc, SIGNAL(settingsChanged()), SLOT(changeSetting()));

    externalActions = new KActionCollection(0, parent, "external actions");
    
    connect(InputServer, SIGNAL(registerProperties(const scim::PropertyList&, int)),
      SLOT(registerProperties(const scim::PropertyList&, int)));
    connect(InputServer, SIGNAL(guiClientDisconnected(int)), SLOT(deleteProperties(int)));
    connect(InputServer,SIGNAL(updateProperty(const scim::Property&, int )),
      SLOT(updateProperty(const scim::Property&, int )));
}


SkimGlobalActions::~SkimGlobalActions() {
    ScimKdeSettings::writeConfig();
    mHelpMenu->deleteLater();
    if( helpDialog )
        helpDialog->deleteLater();
    if( mAboutApp )
        mAboutApp->deleteLater();
}

void SkimGlobalActions::requestReloadAllModules() {
    mc->reloadAllPlugins();
}

void SkimGlobalActions::receiveFactoryMenu(std::vector <scim::String> & _uuids,
        std::vector <scim::String> & names, std::vector <scim::String> & langs,
        std::vector <scim::String> & icons) {
    if(_uuids != uuids) {
      
      uuids = _uuids;
  
      std::map <scim::String, std::vector <size_t> > groups;
      for (size_t i = 0; i < uuids.size (); ++i)
          groups [langs [i]].push_back (i);
  
      ServerAction->clear();
  
      if(groups.size() == 1)
          for(uint i = 0; i < names.size(); i++) {
              ServerAction->insertItem("/" + QString::fromLatin1(_uuids[i].c_str()),
              icons[i].c_str(), QString::fromUtf8(names[i].c_str()), false);
          }
      else
          for (std::map <scim::String, std::vector <size_t> >::iterator it = groups.begin ();
                  it != groups.end (); ++ it) {
              scim::String s = scim::scim_get_language_name((it->first).c_str());
              QString parentPath = "/" + QString::number(std::distance(groups.begin (), it));
              ServerAction->insertItem(parentPath, 0, QString::fromUtf8(s.c_str()), true);
              for (unsigned i = 0; i < it->second.size (); ++i) {
                  ServerAction->insertItem(parentPath + "/" + 
                    QString::fromLatin1(_uuids[it->second [i]].c_str()), 
                    icons[it->second [i]].c_str(), 
                    QString::fromUtf8(names[it->second[i]].c_str()), false);
              }
          }
  
      //append an entry for direct keyboard
      ServerAction->insertItem("/", kil->loadIcon("keyboard", KIcon::Small), i18n("Keyboard"));

      //since the content of this popupmenu has been changed, repopup it to settle it
      //in the proper place
      if(ServerAction->popup()->isVisible()) {
        ServerAction->popup()->hide();
      }
    }
    //if popup is not visible, we should popup it (when user press the shortcut to trigger this menu)
    if(!ServerAction->popup()->isVisible())
      ServerAction->slotPopup();
    
    return;
}
void SkimGlobalActions::propertyActivated(int id) {
    if(_property_repository.contains(id)) {
      kapp->lock();
      InputServer->activateProperty(_property_repository[id].client,
        _property_repository[id].property.get_key());
      kapp->unlock();
    }
}
void SkimGlobalActions::deleteProperties(int client) {
    PropertyRepository::iterator cache;
    ScimAction * action;
    for(PropertyRepository::iterator it = _property_repository.begin ();
      it != _property_repository.end (); it++) {
        if(it.data().client == client) {
          switch (it.data().objectType) {
            case scim_kde::Action:
            case scim_kde::ComboAction:
                action = it.data().parentObject.action;
                _propertyActions.remove(action);
                emit propertyChanged();
                action->unplugAll();
                action->deleteLater();
              break;
            case scim_kde::Popup:
              break;
            default:
              std::cerr<< "Unknow object type associated with a property in deleteProperties\n";
          }
          cache = it;
          _property_repository.erase(it);
          it = cache;
        }
    }
}

PropertyRepository::iterator SkimGlobalActions::find_saved_property
    (PropertyRepository::iterator begin,
     PropertyRepository::iterator end,
     const scim::Property & property)
{
    PropertyRepository::iterator cur;
    
    for(cur = begin; cur != end; cur++)
      if(cur.data().property.get_key () == property.get_key ())
        break;
    return cur;
}
                                                     
void SkimGlobalActions::registerProperties(const scim::PropertyList & properties, int client) {
    bool included = true, somethingchanged = false;

    scim::PropertyList::const_iterator pit = properties.begin ();
    
    PropertyRepository::iterator prit;
    
    //find out existing properties and update them
    for (; pit != properties.end (); ++pit) {
        prit = find_saved_property(_property_repository.begin(), _property_repository.end(), *pit);
        if( prit != _property_repository.end())
          updateProperty (prit, *pit, client);
        else
          included = false;
    }

    PropertyRepository::iterator it = _property_repository.begin ();
    ScimAction * action;
    ScimComboAction * comboaction;
    if(client < 0) {  //registering a new set of frontend properties, not GUI properties
      // currently not registerd properties should be set not currentshown
      for (; it != _property_repository.end (); ++it) {
        if(std::find(properties.begin(), properties.end(), it.data().property) 
          == properties.end())  {
          
          if(it.data().client >= 0)  //this is a GUI property, should always be shown
            continue;
          switch (it.data().objectType) {
            case scim_kde::Action:
            case scim_kde::ComboAction:
                action = it.data().parentObject.action;
                if(action->currentShown() == true)
                  somethingchanged = true;
                action->setCurrentShown(false);
              break;
            case scim_kde::Popup:
                comboaction = it.data().parentObject.comboaction;
                comboaction->setItemVisible(it.key(), false);
              break;
            default:
              std::cerr<< "Unknow object type associated with a property\n";
          }
        }
      }
    }

    if (!included) { // Construct new properties.
        somethingchanged = true;
        create_properties (0, _property_repository,
                           properties, properties.begin(),
                           properties.end(), client);
    }

    if( somethingchanged)
      emit propertiesRegistered();
}

void SkimGlobalActions::create_properties(QWidget * /*parent*/, PropertyRepository &repository,
                                          const scim::PropertyList &/*properties*/, 
                                          scim::PropertyList::const_iterator  begin,
                                          scim::PropertyList::const_iterator  end,
                                          int client) {
    static int root_property_id = 0;
    scim::PropertyList::const_iterator it;
    scim::PropertyList::const_iterator next;

    if (begin == end) return;

    it = begin;
    next = begin + 1;

    while (it != end) {
        if (next == end || !next->is_a_leaf_of (*it)) {
            QString text = QString::fromUtf8(it->get_label ().c_str()),
                    name = QString::fromUtf8(it->get_key ().c_str());
                if(next != it + 1) {  //this property has child(ren), create comboaction
                  ScimComboAction * node = 0;
                  PropertyRepository::const_iterator eNodeIt = 
                      find_saved_property(repository.begin(), repository.end(), *it);
                  ClientPropertyInfo info = {*it, client, 0,
                      0, scim_kde::ComboAction};
                  if(eNodeIt == repository.end()) {
                    info.id = root_property_id++;
                    node = new ScimComboAction(text, "", 0, 0, 
                      externalActions, it->get_key ().c_str());
                    info.parentObject.comboaction = node;
                    node->setDisplayedText(text);
                    node->setCurrentIconSet(it->get_icon ().c_str());
                    _propertyActions.push_back(node);
                    repository[info.id] = info;
                    connect(node, SIGNAL(itemActivated(int)), SLOT(propertyActivated(int)));
                  } else {
                    if (eNodeIt.data().objectType == scim_kde::ComboAction) {
                      node = eNodeIt.data().parentObject.comboaction;
                      info = repository[eNodeIt.data().id];
                    } else {
                      std::cerr << "Oops, the root property " << it->get_key ().c_str() 
                        << " has been registered, but it was not a comboaction!\nI would skip all children properties of this one.";
                      it = next;
                      next++;
                      continue;
                    }
                  }
                  //add all children of this propery as a (multi-depth) menu
                  info.objectType = scim_kde::Popup;
                  it++;
                  scim::PropertyList::const_iterator nodeEnd = next, localNextIt;
                  localNextIt = it + 1;
                  for(; it != nodeEnd; it++, localNextIt++) {
                    eNodeIt = find_saved_property(repository.begin(), repository.end(), *it);
                    if(eNodeIt == repository.end()) {  //this property hasn't been registered
                      text = QString::fromUtf8(it->get_label ().c_str()),
                      name = QString::fromUtf8(it->get_key ().c_str());
                      info.property = *it;
                      info.id = root_property_id++;
                      if(localNextIt == nodeEnd || !localNextIt->is_a_leaf_of (*it)) { //a normal menu entry
                          info.id = node->insertItem(name, it->get_icon ().c_str(), 
                                    text, false, info.id);
                      } else {  //a popup menu
                          info.id = node->insertItem(name, it->get_icon ().c_str(), 
                                    text, true, info.id);
                      }
                      repository[info.id] = info;
                    } else {  //existing property has been updated in the registerProperties function, so nothing to do here
                    }
                  }
                } else {  //no child, create a std action
                  PropertyRepository::const_iterator eNodeIt = 
                      find_saved_property(repository.begin(), repository.end(), *it);
                  if(eNodeIt == repository.end()) {
                      int cid = root_property_id++;
                      ScimAction * node = new ScimAction(text, externalActions,
                        cid, it->get_key ().c_str());
                      node->setOption(scim_kde::ToolBarPreferIcon);
                      node->setVisible(it->visible());
                      node->setEnabled(it->active());
                      node->setDisplayedText(text);
                      node->setCurrentIconSet(it->get_icon ().c_str());
                      _propertyActions.push_back(node);
                      ClientPropertyInfo info = {*it, client, cid,
                        node, scim_kde::Action};
                      repository[info.id] = info;
                      connect(node, SIGNAL(activated(int)), SLOT(propertyActivated(int)));
                  } else {  //existing property has been updated in the registerProperties function, so nothing to do here
                  }
                }
            
            it = next;
        }
        ++ next;
    }
}

void SkimGlobalActions::updateProperty(PropertyRepository::iterator &it,
  const scim::Property &property, int /*client*/) {
    QString text = QString::fromUtf8(property.get_label ().c_str());
    const char * pix = property.get_icon ().c_str();
    
    ScimAction *action;
    ScimComboAction *comboaction;
    switch (it.data().objectType) {
      case scim_kde::Action:
          action = it.data().parentObject.action;
          action->setDisplayedText(text);
          action->setCurrentIconSet(pix);

          action->setEnabled(property.active ());
          if(action->visible() != property.visible() || action->currentShown() == false) {
            action->setCurrentShown(true);
            action->setVisible(property.visible());
            emit propertyChanged();
          }
        break;
      case scim_kde::ComboAction:
          comboaction = it.data().parentObject.comboaction;
          comboaction->setDisplayedText(text);
          comboaction->setCurrentIconSet(pix);

          comboaction->setEnabled(property.active ());
          if(comboaction->visible() != property.visible() || comboaction->currentShown() == false) {
            comboaction->setCurrentShown(true);
            comboaction->setVisible(property.visible());
            emit propertyChanged();
          }
        break;
      case scim_kde::Popup:
          comboaction = it.data().parentObject.comboaction;
          comboaction->changeItem(pix,text, it.data().id);
          comboaction->setCurrentShown(true);
          comboaction->setItemVisible(it.data().id, property.visible());
          comboaction->setItemEnabled(it.data().id, property.active ());
        break;
      default:
        std::cerr<< "Unknow object type associated with a property\n";
    }

    it.data().property = property;
}
void SkimGlobalActions::updateProperty(const scim::Property &property, int client) {
    PropertyRepository & repository = _property_repository;
    PropertyRepository::iterator it = repository.begin ();

    for (; it != repository.end (); ++ it) {
        if (it.data().property == property && it.data().client == client) {
            updateProperty(it, property, client);
            break;
        }
    }
}
void SkimGlobalActions::changeFactory(const QString & path) {
    QString uuid = path.section('/',-1);
    kapp->lock();
    if(!uuid.isNull())
      InputServer->changeFactory(uuid);
    else
      InputServer->changeFactory("");
    kapp->unlock();
}

void SkimGlobalActions::SetFactoryInfo( const QString & newservername, const QString & newiconstr ) {
    ServerAction->setDisplayedText(newservername);
    ServerAction->setCurrentIconSet(newiconstr.ascii());
}

void SkimGlobalActions::aboutApp() {
    if( mAboutApp == 0 ) {
        mAboutApp = new KAboutApplication( mc->getAboutData(),
                                           0, "about", false );
    }
    mAboutApp->show();
}
void SkimGlobalActions::showHelp( const QString & txt) {
    if( helpDialog == 0 )
        helpDialog = new ScimHelpDialog(0);

    if( !helpDialog->isVisible() ) {
        helpDialog->setGeneralInfo( txt );
        helpDialog->show();
    }
    if( helpDialog->isMinimized())
        helpDialog->showNormal();
    else
        helpDialog->raise();
}

#include "skimglobalactions.moc"
