/* This file is part of libhud-qt
 * Copyright 2013 Canonical Ltd.
 *
 * libhud-qt is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3,
 * as published by the Free Software Foundation.
 *
 * libhud-qt is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranties of
 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
 * PURPOSE.  See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// todo, how to provide preview if live preview is not available
// todo, partial live preview which would show in HUD?

#include "action.h"

#include <QMutex>
#include <QDebug>

// needed for glib includes.
#undef signals
#include <libhud-2/hud.h>

using namespace Ubuntu::HUD;

class Ubuntu::HUD::Action::Private
{
public:
    QString identifier;
    QString label;
    QString description;
    QString keywords;
    bool isBackgroundAction;
    bool enabled;

    QString commitLabel;
    bool hasLivePreview;
    bool hasNoPreview;
    bool requiresPreview;
    QList<Parameter *> parameters;

    GSimpleAction *simpleAction;
    HudActionDescription *desc;
    QString actionid;

    GMenu *paramMenu;


    static void action_activated(GSimpleAction *action,
                                 GVariant      *parameter,
                                 gpointer       user_data);
    static int actionNumber;
    static int generateActionNumber();
    static QMutex mutex;
};

int Action::Private::actionNumber = 0;
QMutex Action::Private::mutex;
int
Action::Private::generateActionNumber()
{
    QMutexLocker locker(&Action::Private::mutex);
    return Action::Private::actionNumber++;
}

void
Action::Private::action_activated(GSimpleAction *simpleaction,
                                  GVariant      *parameter,
                                  gpointer       user_data)
{
    Action *action = qobject_cast<Action *>((QObject *)user_data);
    if (action == 0) {
        qWarning("CAST FAILED.");
        return;
    }

    if (g_action_get_parameter_type(G_ACTION(simpleaction)) == NULL) {
        action->trigger();
        return;
    }

    if (g_variant_is_of_type(parameter, G_VARIANT_TYPE_STRING)) {
        QString state(g_variant_get_string(parameter, NULL));

        if (state == "start") {
            emit action->started();
            return;
        } else if (state == "end") {
            // just skip for now
            return;
        } else if (state == "commit") {
            emit action->triggered();
            return;
        } else if (state == "reset") {
            emit action->resetted();
            return;
        } else if (state == "cancel") {
            emit action->cancelled();
            return;
        } else {
            qWarning("Unknown Action state: %s", qPrintable(state));
        }
    }

    Q_ASSERT(0); // should not be reached
}

Action::Action(QObject *parent)
    : QObject(parent)
{
    g_type_init();

    d = new Private;
    d->isBackgroundAction = false;
    d->enabled = true;

    d->hasLivePreview = false;
    d->hasNoPreview = false;
    d->requiresPreview = false;

    d->actionid = QString("action-qt-") + QString::number(Action::Private::generateActionNumber());

    d->simpleAction = 0;
    d->paramMenu = g_menu_new();

    d->desc = hud_action_description_new(qPrintable(QString("hud.") + d->actionid), NULL);

    hud_action_description_set_attribute_value(d->desc, G_MENU_ATTRIBUTE_LABEL, g_variant_new_string(qPrintable(d->label)));
    hud_action_description_set_attribute_value(d->desc, "description", g_variant_new_string(qPrintable(d->description)));
    hud_action_description_set_attribute_value(d->desc, "keywords", g_variant_new_string(qPrintable(d->keywords)));
    hud_action_description_set_attribute_value(d->desc, "enabled", g_variant_new_boolean(d->enabled));
    hud_action_description_set_attribute_value(d->desc, "commitLabel", g_variant_new_string(qPrintable(d->commitLabel)));
    hud_action_description_set_attribute_value(d->desc, "hasLivePreview", g_variant_new_boolean(d->hasLivePreview));
}


Action::~Action()
{
    g_clear_object(&d->paramMenu);
    g_clear_object(&d->simpleAction);
    g_clear_object(&d->desc);
    delete d;
}

QString
Action::identifier() const
{
    return d->identifier;
}

void
Action::setIdentifier(const QString &identifier)
{
    if (d->identifier == identifier)
        return;
    d->identifier = identifier;
    emit identifierChanged(identifier);
}

QString
Action::label() const
{
    return d->label;
}

void
Action::setLabel(const QString &label)
{
    if (d->label == label)
        return;
    d->label = label;
    hud_action_description_set_attribute_value(d->desc, G_MENU_ATTRIBUTE_LABEL, g_variant_new_string(qPrintable(label)));
    emit labelChanged(label);
}

QString
Action::description() const
{
    return d->description;
}

void
Action::setDescription(const QString &description)
{
    if (d->description == description)
        return;
    d->description = description;
    hud_action_description_set_attribute_value(d->desc, "description", g_variant_new_string(qPrintable(description)));
    emit descriptionChanged(description);
}

QString
Action::keywords() const
{
    return d->keywords;
}

void
Action::setKeywords(QString keywords)
{
    if (d->keywords == keywords)
        return;
    d->keywords = keywords;
    hud_action_description_set_attribute_value(d->desc, "keywords", g_variant_new_string(qPrintable(keywords)));
    emit keywordsChanged(keywords);
}

bool
Action::isBackgroundAction() const
{
    return d->isBackgroundAction;
}

void
Action::setIsBackgroundAction(bool value)
{
    if (d->isBackgroundAction == value)
        return;
    d->isBackgroundAction = value;
    emit isBackgroundActionChanged(value);
}

bool
Action::enabled() const
{
    return d->enabled;
}

void
Action::setEnabled(bool value)
{
    if (d->enabled == value)
        return;
    d->enabled = value;
    hud_action_description_set_attribute_value(d->desc, "enabled", g_variant_new_boolean(value));
    emit enabledChanged(value);
}

void
Action::trigger() const
{
    emit triggered();
}

QString
Action::commitLabel() const
{
    return d->commitLabel;
}

void
Action::setCommitLabel(const QString &label)
{
    if (d->commitLabel == label)
        return;
    d->commitLabel = label;
    hud_action_description_set_attribute_value(d->desc, "commitLabel", g_variant_new_string(qPrintable(label)));
    emit commitLabelChanged(label);
}

bool
Action::hasLivePreview() const
{
    return d->hasLivePreview;
}

void
Action::setHasLivePreview(bool value)
{
    if (d->hasLivePreview == value)
        return;
    d->hasLivePreview = value;
    hud_action_description_set_attribute_value(d->desc, "hasLivePreview", g_variant_new_boolean(value));
    emit hasLivePreviewChanged(value);
}

bool
Action::hasNoPreview() const
{
    return d->hasNoPreview;
}

void
Action::setHasNoPreview(bool value)
{
    if (d->hasNoPreview == value)
        return;
    d->hasNoPreview = value;
    emit hasNoPreviewChanged(value);
}

bool
Action::requiresPreview() const
{
    return d->requiresPreview;
}

void
Action::setRequiresPreview(bool value)
{
    if (d->requiresPreview == value)
        return;
    d->requiresPreview = value;
    emit requiresPreviewChanged(value);
}

QList<Parameter *>
Action::parameters()
{
    return d->parameters;
}

void
Action::addParameter(Parameter *parameter)
{
    if (d->parameters.contains(parameter)) {
        qWarning("Trying to add Parameter that already exist in Action");
        return;
    }

    /*! @todo ownership */
    d->parameters.append(parameter);
    emit parameterAdded(parameter);
    emit parametersChanged();
}

void
Action::removeParameter(Parameter *parameter)
{
    if (!d->parameters.contains(parameter)) {
        qWarning("Trying to remove Parameter that does not exist in Action");
        return;
    }

    /*! @todo ownership */
    d->parameters.removeOne(parameter);
    emit parameterRemoved(parameter);
    emit parametersChanged();
}

/*! @private
 * get access to the internal HudActionDesctiption
 *
 * the pointer is valid as long as the instanse of this class is valid
 */
void
Action::_actionDescription(void **desc)
{
    *desc = d->desc;
}

/*!
 * @private
 *
 * handles the registration of the action group.
 *
 * This function is only meant to be used by Ubuntu::HUD::HUD and
 * anyone making modifications to either of the classes must take extra
 * care.
 */
void
Action::magic(void **desc, void *gactionlist)
{
    QList<GSimpleAction *> *gactions = (QList<GSimpleAction *> *)gactionlist;

    if (d->parameters.count() == 0) {
        d->simpleAction = g_simple_action_new(qPrintable(d->actionid), NULL);
    } else {
        d->simpleAction = g_simple_action_new(qPrintable(d->actionid), G_VARIANT_TYPE_STRING);
        hud_action_description_set_parameterized(d->desc, G_MENU_MODEL(d->paramMenu));
    }
    g_signal_connect(G_OBJECT(d->simpleAction), "activate", G_CALLBACK(Action::Private::action_activated), this);
    gactions->append(d->simpleAction);

    foreach(Parameter *param, d->parameters) {
        GMenuItem *menuitem;
        GSimpleAction *gaction;
        // magic should return the menuitem from parameter subclass.
        param->magic((void**)&menuitem, (void**)&gaction);
        g_menu_append_item(d->paramMenu, menuitem);
        gactions->append(gaction);
    }

    *desc = d->desc;
}


