/***************************************************************************
 *   Copyright (C) 2005-09 by the Quassel Project                          *
 *   devel@quassel-irc.org                                                 *
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************
 * Parts of this implementation are based on KDE's KActionCollection.      *
 ***************************************************************************/

#include "actioncollection.h"
#include "action.h"
#include "icon.h"
#include <QCoreApplication>
#include <QSettings>
#include <QMetaMethod>

static const char *constProp="Category";
static ActionCollection *coll=nullptr;
static QWidget *mainWidget=nullptr;
void ActionCollection::setMainWidget(QWidget *w)
{
    mainWidget=w;
}

ActionCollection * ActionCollection::get()
{
    if (!coll) {
        coll=new ActionCollection(mainWidget);
        coll->setProperty(constProp, QCoreApplication::applicationName());
        if (mainWidget) {
            coll->addAssociatedWidget(mainWidget);
        }
    }
    return coll;
}

Action * ActionCollection::createAction(const QString &name, const QString &text, const char *icon, const QString &whatsThis)
{
    Action *act = static_cast<Action *>(addAction(name));
    act->setText(text);
    if (nullptr!=icon) {
        act->setIcon(Icon::get(icon));
    }
    if (!whatsThis.isEmpty()) {
        act->setWhatsThis(whatsThis);
    }
    Action::initIcon(act);
    return act;
}

Action * ActionCollection::createAction(const QString &name, const QString &text, const QIcon &icon, const QString &whatsThis)
{
    Action *act = static_cast<Action *>(addAction(name));
    act->setText(text);
    if (!icon.isNull()) {
        act->setIcon(icon);
    }
    if (!whatsThis.isEmpty()) {
        act->setWhatsThis(whatsThis);
    }
    Action::initIcon(act);
    return act;
}

void ActionCollection::updateToolTips()
{
    for (QAction *act: actions()) {
        QString prev=act->toolTip();
        Action::updateToolTip(act);
        if (prev!=act->toolTip()) {
            emit tooltipUpdated(act);
        }
    }
}

ActionCollection::ActionCollection(QObject *parent) : QObject(parent) {
  _connectTriggered = _connectHovered = false;
}

ActionCollection::~ActionCollection() {
}

void ActionCollection::clear() {
  _actionByName.clear();
  qDeleteAll(_actions);
  _actions.clear();
}

QAction *ActionCollection::action(const QString &name) const {
  return _actionByName.value(name, nullptr);
}

QList<QAction *> ActionCollection::actions() const {
  return _actions;
}

Action *ActionCollection::addAction(const QString &name, Action *action) {
  QAction *act = addAction(name, static_cast<QAction *>(action));
  Q_UNUSED(act)
  Q_ASSERT(act == action);
  return action;
}

Action *ActionCollection::addAction(const QString &name, const QObject *receiver, const char *member) {
  Action *a = new Action(this);
  if(receiver && member)
    connect(a, SIGNAL(triggered(bool)), receiver, member);
  return addAction(name, a);
}

QAction *ActionCollection::addAction(const QString &name, QAction *action) {
  if(!action)
    return action;

  const QString origName = action->objectName();
  QString indexName = name;

  if(indexName.isEmpty())
    indexName = action->objectName();
  else
    action->setObjectName(indexName);
  if(indexName.isEmpty())
    indexName = QString::asprintf("unnamed-%p", (void *)action);

  // do we already have this action?
  if(_actionByName.value(indexName, nullptr) == action)
    return action;
  // or maybe another action under this name?
  if(QAction *oldAction = _actionByName.value(indexName))
    takeAction(oldAction);

  // do we already have this action under a different name?
  int oldIndex = _actions.indexOf(action);
  if(oldIndex != -1) {
    _actionByName.remove(origName);
    _actions.removeAt(oldIndex);
  }

  // add action
  _actionByName.insert(indexName, action);
  _actions.append(action);

  for (QWidget *widget: _associatedWidgets) {
    widget->addAction(action);
  }

  connect(action, SIGNAL(destroyed(QObject *)), SLOT(actionDestroyed(QObject *)));
  if(_connectHovered)
    connect(action, SIGNAL(hovered()), SLOT(slotActionHovered()));
  if(_connectTriggered)
    connect(action, SIGNAL(triggered(bool)), SLOT(slotActionTriggered()));

  emit inserted(action);
  return action;
}

void ActionCollection::removeAction(QAction *action) {
  delete takeAction(action);
}

QAction *ActionCollection::takeAction(QAction *action) {
  if(!unlistAction(action))
    return nullptr;

  for (QWidget *widget: _associatedWidgets) {
    widget->removeAction(action);
  }

  action->disconnect(this);
  return action;
}

QString ActionCollection::configKey() const
{
    QVariant v=property(constProp);
    if (v.isValid() && !v.toString().isEmpty()) {
        return QLatin1String("Shortcuts-")+v.toString();
    }
    return QLatin1String("Shortcuts");
}

void ActionCollection::readSettings() {
  QSettings s;
  s.beginGroup(configKey());
  QStringList savedShortcuts = s.childKeys();

  for (const QString &name: _actionByName.keys()) {
    if(!savedShortcuts.contains(name))
      continue;
    Action *action = qobject_cast<Action *>(_actionByName.value(name));
    if(action)
      action->setShortcut(s.value(name, QKeySequence()).value<QKeySequence>(), Action::ActiveShortcut);
  }
}

void ActionCollection::writeSettings() const {
  QSettings s;
  s.beginGroup(configKey());
  for (const QString &name: _actionByName.keys()) {
    Action *action = qobject_cast<Action *>(_actionByName.value(name));
    if(!action)
      continue;
    if(!action->isShortcutConfigurable())
      continue;
    if(action->shortcut(Action::ActiveShortcut) == action->shortcut(Action::DefaultShortcut)) {
      s.remove(name);
      continue;
    }
    s.setValue(name, action->shortcut(Action::ActiveShortcut));
  }
}

void ActionCollection::slotActionTriggered() {
  QAction *action = qobject_cast<QAction *>(sender());
  if(action)
    emit actionTriggered(action);
}

void ActionCollection::slotActionHovered() {
  QAction *action = qobject_cast<QAction *>(sender());
  if(action)
    emit actionHovered(action);
}

void ActionCollection::actionDestroyed(QObject *obj) {
  // remember that this is not an QAction anymore at this point
  QAction *action = static_cast<QAction *>(obj);

  unlistAction(action);
}

void ActionCollection::connectNotify(const QMetaMethod &signal) {
  if(_connectHovered && _connectTriggered)
    return;

  if(QMetaObject::normalizedSignature(SIGNAL(actionHovered(QAction*))) == signal.methodSignature()) {
    if(!_connectHovered) {
      _connectHovered = true;
      for (QAction* action: actions())
        connect(action, SIGNAL(hovered()), SLOT(slotActionHovered()));
    }
  } else if(QMetaObject::normalizedSignature(SIGNAL(actionTriggered(QAction*))) == signal.methodSignature()) {
    if(!_connectTriggered) {
      _connectTriggered = true;
      for (QAction* action: actions())
        connect(action, SIGNAL(triggered(bool)), SLOT(slotActionTriggered()));
    }
  }

  QObject::connectNotify(signal);
}

void ActionCollection::associateWidget(QWidget *widget) const {
  for (QAction *action: actions()) {
    if(!widget->actions().contains(action))
      widget->addAction(action);
  }
}

void ActionCollection::addAssociatedWidget(QWidget *widget) {
  if(!_associatedWidgets.contains(widget)) {
    widget->addActions(actions());
    _associatedWidgets.append(widget);
    connect(widget, SIGNAL(destroyed(QObject *)), SLOT(associatedWidgetDestroyed(QObject *)));
  }
}

void ActionCollection::removeAssociatedWidget(QWidget *widget) {
  for (QAction *action: actions())
    widget->removeAction(action);
  _associatedWidgets.removeAll(widget);
  disconnect(widget, SIGNAL(destroyed(QObject *)), this, SLOT(associatedWidgetDestroyed(QObject *)));
}

QList<QWidget *> ActionCollection::associatedWidgets() const {
  return _associatedWidgets;
}

void ActionCollection::clearAssociatedWidgets() {
  for (QWidget *widget: _associatedWidgets)
    for (QAction *action: actions())
      widget->removeAction(action);

  _associatedWidgets.clear();
}

void ActionCollection::associatedWidgetDestroyed(QObject *obj) {
  _associatedWidgets.removeAll(static_cast<QWidget *>(obj));
}

bool ActionCollection::unlistAction(QAction *action) {
  // This might be called with a partly destroyed QAction!

  int index = _actions.indexOf(action);
  if(index == -1) return false;

  QString name = action->objectName();
  _actionByName.remove(name);
  _actions.removeAt(index);

  // TODO: remove from ActionCategory if we ever get that

  return true;
}

#include "moc_actioncollection.cpp"
