/***************************************************************************
 *   Copyright (C) 2003-2005 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 <qpixmap.h>
#include <qpopupmenu.h>
#include <kiconloader.h>
#include <khelpmenu.h>
#include <kapplication.h>

SkimGlobalActions::SkimGlobalActions(QObject *parent, const char *name)
: KActionCollection(0, parent, name), mAboutApp(0), helpDialog(0) {
    mc = static_cast<SkimPluginManager *>(parent);
    InputServer = mc->getInputServer();

    helperActions = new KActionCollection(0, parent, "helper actions");

    kil = KGlobal::iconLoader();

    mHelpMenu = new KHelpMenu( 0 );
    connect(mHelpMenu,SIGNAL(showAboutApplication()), this, SLOT(aboutApp()));

    new KAction( KGuiItem( i18n( "SCIM Help" ), "scim_help" ),
                 "", InputServer, SLOT(getHelp()), this, "scim_help");
    new KAction( KGuiItem( i18n( "SCIM Setup" ), "scim_setup" ),
                 "", this, SLOT(startSCIMConfigureHelper()), this, "scim_help");

    KStdAction::help(mHelpMenu, SLOT(appHelpActivated()), this);
    KStdAction::aboutKDE(mHelpMenu,SLOT(aboutKDE ()),this,"aboutkde");
    KStdAction::aboutApp(this,SLOT(aboutApp()),this,"aboutapp");

#if ENABLE_DEBUG
    new KAction( KGuiItem( i18n( "Reload all modules" ), "reload" ),
                 "", this, SLOT(requestReloadAllModules()), this, "reload");

    KStdAction::quit(mc, SLOT(shutdown()), this);
#endif

    connect( InputServer, SIGNAL(showHelp(const QString & ) ) , this, SLOT(showHelp(const QString & )));

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

    connect( InputServer, SIGNAL( updateFactoryInfoReq( const scim::PanelFactoryInfo &) ),
      SLOT( SetFactoryInfo( const scim::PanelFactoryInfo &) ) );

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

//     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(removeHelper(int)), SLOT(deleteProperties(int)));
    connect(InputServer,SIGNAL(updateProperty(const scim::Property&, int )),
      SLOT(updateProperty(const scim::Property&, int )));

    //only after loadAllPlugins, overload standalone module list can be established
    connect(InputServer,SIGNAL(standaloneHelpersChanged()), this, SLOT(initStantaloneHelperActions()));
}

void SkimGlobalActions::startSCIMConfigureHelper()
{
    InputServer->start_helper("8034d025-bdfc-4a10-86a4-82b9461b32b0");
}

void SkimGlobalActions::initStantaloneHelperActions() {
    for(uint i=0; i<helperActions->count();i++)
    {
        helperActions->action(i)->unplugAll();
    }

    helperActions->clear();

    //add standalone helper into an actioncollection
    std::vector<scim::HelperInfo>  _helper_list;
    InputServer->getStandaloneHelperList(_helper_list);

    helper_uuids.clear();
    for (size_t i = 0; i < _helper_list.size (); ++i) {
        helper_uuids.push_back(_helper_list [i].uuid);
            QString text = QString::fromUtf8(_helper_list [i].name.c_str ());
            ScimAction * node = new ScimAction(text, helperActions,
            i, _helper_list [i].uuid.c_str ());
//             node->setOption(scim_kde::ToolBarPreferIcon);
            node->setVisible(true);
            node->setEnabled(true);
            node->setDisplayedText(text);
            QString filename = QString::fromUtf8(_helper_list [i].icon.c_str());
            node->setIcon(filename);
            node->setWhatsThis(QString::fromUtf8(_helper_list [i].description.c_str ()));
            connect(node, SIGNAL(activated(int)), SLOT(helperActivated(int)));
    }

    emit standaloneHelperActionsChanged();
}

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

void SkimGlobalActions::helperActivated(int i) {
    if ( static_cast<size_t>(i) < helper_uuids.size() )
         InputServer->start_helper(helper_uuids[i]);
}

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

void SkimGlobalActions::receiveFactoryMenu(const std::vector <scim::PanelFactoryInfo> &menu) {
    if(menu.size() != uuids.size()) {

      uuids.clear();

      std::map <scim::String, std::vector <size_t> > groups;
      for (size_t i = 0; i < menu.size (); ++i)
          groups [menu [i].lang].push_back (i);

      ServerAction->clear();

      if(groups.size() == 1)
          for(uint i = 0; i < menu.size(); i++) {
              ServerAction->insertItem("/" + QString::fromLatin1(menu[i].uuid.c_str()),
              menu[i].icon.c_str(), QString::fromUtf8(menu[i].name.c_str()), false);
              uuids.push_back(menu[i].uuid.c_str());
          }
      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(menu[it->second [i]].uuid.c_str()), 
                    menu[it->second [i]].icon.c_str(), 
                    QString::fromUtf8(menu[it->second[i]].name.c_str()), false);
                  uuids.push_back(menu[it->second [i]].uuid.c_str());
              }
          }

      //append an entry for direct keyboard
      ServerAction->insertItem("/", kil->loadIcon("scim_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(_global_property_dict.contains(id)) {
      kapp->lock();
      InputServer->activateProperty(_global_property_dict[id].first,
        _property_repository[_global_property_dict[id].first][_global_property_dict[id].second].property.get_key());
      kapp->unlock();
    }
}

void SkimGlobalActions::deleteProperties(int client) {
    bool changed = false;
    if(_property_repository.contains(client)) {
        ScimAction * action;
        for(PropertyRepository::iterator it = _property_repository[client].begin ();
        it != _property_repository[client].end (); it++) {
            switch (it->objectType) {
                case scim_kde::Action:
                case scim_kde::ComboAction:
                    action = it->parentObject.action;
                    if(client < 0)
                        _frontendPropertyActions.remove(action);
                    else
                        _guiPropertyActions.remove(action);
                    changed = true;
                    action->unplugAll();
                    action->deleteLater();
                break;
                case scim_kde::Popup:
                break;
                default:
                std::cerr<< "Unknow object type associated with a property in deleteProperties\n";
            }
        }
        _property_repository.erase(client);
    }
    if(changed) emit propertiesRemoved(client < 0);
}

void SkimGlobalActions::registerProperties(const scim::PropertyList & properties, int client) {
    bool included = true;

    scim::PropertyList::const_iterator pit = properties.begin ();

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

    if (!included)  // Construct new properties.
        create_properties (0, _property_repository,
                           properties, properties.begin(),
                           properties.end(), client);

    if(client < 0) { //registering a new set of frontend properties,
        _frontendPropertyActions.clear();
        pit = properties.begin ();

        if(_property_repository.contains(client)) {
            //add all registered properties to frontendPropertyActions
            for (; pit != properties.end (); ++pit) {
                prit = std::find(_property_repository[client].begin(), _property_repository[client].end(),*pit);
                if( prit != _property_repository[client].end())
                {
                    switch (prit->objectType) {
                        case scim_kde::Action:
                        case scim_kde::ComboAction:
                            _frontendPropertyActions.push_back(prit->parentObject.action);
                            //fall through
                        case scim_kde::Popup:
                            break;
                        default:
                            std::cerr<< "Unknow object type associated with a property\n";
                    }
                }
            }
        }
    }

    emit propertiesRegistered(client < 0);
}

void SkimGlobalActions::create_properties(QWidget * /*parent*/, ClientPropertyRepository &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());
                QPair<int, int> dictcand(client, 0);
                if(next != it + 1) {  //this property has child(ren), create comboaction
                  ScimComboAction * node = 0;
                  PropertyRepository::const_iterator eNodeIt; 
                  ClientPropertyInfo info = {*it, client, 0, scim_kde::ComboAction};
                  if(!repository.contains(client))
                    repository[client] = PropertyRepository();
                  if((eNodeIt = 
                        std::find(repository[client].begin(),  repository[client].end(),*it)) == repository[client].end()) {
                    info.globalUniqueID = root_property_id++;
                    node = new ScimComboAction(text, "", 0, 0, externalActions, it->get_key ().c_str());
                    if(client >= 0)
                        _guiPropertyActions.push_back(node);
                    info.parentObject.comboaction = node;
                    node->setDisplayedText(text);
                    node->setCurrentIconSet(it->get_icon ().c_str());
                    node->setOption(scim_kde::ToolBarPreferIcon);
//                     _propertyActions.push_back(node);
                    dictcand.second = repository[client].size();
                    _global_property_dict[info.globalUniqueID] = dictcand;
                    repository[client].push_back(info);
                    connect(node, SIGNAL(itemActivated(int)), SLOT(propertyActivated(int)));
                  } else {
                    if (eNodeIt->objectType == scim_kde::ComboAction) {
                      node = eNodeIt->parentObject.comboaction;
                      info = *eNodeIt;
                    } 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 property 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 = std::find(repository[client].begin(),  repository[client].end(),*it);
                    if(eNodeIt == repository[client].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.globalUniqueID = root_property_id++;
                      if(localNextIt == nodeEnd || !localNextIt->is_a_leaf_of (*it)) { //a normal menu entry
                          info.globalUniqueID = node->insertItem(name, it->get_icon ().c_str(), 
                                    text, false, info.globalUniqueID);
                      } else {  //a popup menu
                          info.globalUniqueID = node->insertItem(name, it->get_icon ().c_str(), 
                                    text, true, info.globalUniqueID);
                      }
                    dictcand.second = repository[client].size();
                    _global_property_dict[info.globalUniqueID] = dictcand;
                    repository[client].push_back(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 = 
                      std::find(repository[client].begin(),  repository[client].end(),*it);
                  if(eNodeIt == repository[client].end()) {
                      int cid = root_property_id++;
                      ScimAction * node = new ScimAction(text, externalActions,
                        cid, it->get_key ().c_str());
                      if(client >= 0)
                          _guiPropertyActions.push_back(node);
                      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, cid, node, scim_kde::Action};
                    dictcand.second = repository[client].size();
                    _global_property_dict[info.globalUniqueID] = dictcand;
                    repository[client].push_back(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->objectType) {
      case scim_kde::Action:
          action = it->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(client < 0);
          }
        break;
      case scim_kde::ComboAction:
          comboaction = it->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(client < 0);
          }
        break;
      case scim_kde::Popup:
          comboaction = it->parentObject.comboaction;
          comboaction->changeItem(pix,text, it->globalUniqueID);
          comboaction->setCurrentShown(true);
          comboaction->setItemVisible(it->globalUniqueID, property.visible());
          comboaction->setItemEnabled(it->globalUniqueID, property.active ());
        break;
      default:
        std::cerr<< "Unknow object type associated with a property\n";
    }

    it->property = property;
}
void SkimGlobalActions::updateProperty(const scim::Property &property, int client) {
    PropertyRepository::iterator it = 
        std::find(_property_repository[client].begin(), _property_repository[client].end(), property);
    if(it != _property_repository[client].end())
        updateProperty(it, property, client);
}
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 scim::PanelFactoryInfo & info ) {
    static scim::String old_uuid = "";
    if(old_uuid != info.uuid) {
        QString newservername = QString::fromUtf8( info.name.c_str() );
        ServerAction->setDisplayedText(newservername);
        ServerAction->setCurrentIconSet(info.icon.c_str());
        old_uuid = info.uuid;

        //all displayed frontend properties have to be hidden, for the new IMEngine may not have any properties
        scim::PropertyList emptyProperty;
        registerProperties(emptyProperty, -1);
    }
}

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"
