/*
 * Copyright 2015 Canonical Ltd.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; version 3.
 *
 * 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 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/>.
 */

#include "ucactionitem_p_p.h"

#define foreach Q_FOREACH
#include <QtQml/private/qqmlbinding_p.h>
#include <QtQuick/private/qquickitem_p.h>
#undef foreach

#include "ucaction_p.h"
#include "ucstyleditembase_p_p.h"

UT_NAMESPACE_BEGIN

UCActionItemPrivate::UCActionItemPrivate()
    : action(Q_NULLPTR)
    , flags(0)
{
}

/*!
 * \qmltype ActionItem
 * \instantiates UCActionItem
 * \inqmlmodule Lomiri.Components
 * \ingroup lomiri
 * \brief A visual representation of an Action. The API of ActionItem is a
 * copy of the API of \l Action, with additional properties to define
 * visual aspects of the ActionItem.
 *
 * If \l action is set, the values of the other properties will by default
 * be identical to the \l Action's property values. Setting the other properties
 * will override the properties copied from the \l Action.
 */

/*!
 * \qmlsignal ActionItem::triggered(var value)
 * Called when the actionItem is triggered.
 */
UCActionItem::UCActionItem(QQuickItem *parent)
    : UCStyledItemBase(*(new UCActionItemPrivate), parent)
{
}

UCActionItem::UCActionItem(UCActionItemPrivate &dd, QQuickItem *parent)
    : UCStyledItemBase(dd, parent)
{
}

// When "visible" or "enabled" are explicitly set to false, we set a flag.
// - If the property was changed by a user, this will stop
//   syncing the value from the associated action.
// - If this is called while syncing properties internally in
//   _q_visibleBinding()/_q_enabledBinding(), the flag is reset afterwards.
void UCActionItem::itemChange(ItemChange change, const ItemChangeData &data)
{
    Q_D(UCActionItem);
    UCStyledItemBase::itemChange(change, data);

    if (change == ItemVisibleHasChanged &&
            !QQuickItemPrivate::get(this)->explicitVisible) {
        d->flags |= UCActionItemPrivate::CustomVisible;
    } else if (change == ItemEnabledHasChanged &&
            !QQuickItemPrivate::get(this)->explicitEnable) {
        d->flags |= UCActionItemPrivate::CustomEnabled;
    }
}

bool UCActionItemPrivate::hasBindingOnProperty(const QString &name)
{
    Q_Q(UCActionItem);
    QQmlProperty property(q, name, qmlContext(q));
    return QQmlPropertyPrivate::binding(property) != Q_NULLPTR;
}

// update visible property
void UCActionItemPrivate::_q_visibleBinding()
{
    // If a custom value was set, don't sync the property
    if (flags & CustomVisible) {
        return;
    }
    // If a custom binding was set, don't sync the property
    if (hasBindingOnProperty(QStringLiteral("visible"))) {
        flags |= CustomVisible;
        return;
    }
    // Sync it, and reset the flag. See `itemChange()`.
    bool visible = action ? action->m_visible : true;
    q_func()->setVisible(visible);
    flags &= ~CustomVisible;
}

// update enabled property
void UCActionItemPrivate::_q_enabledBinding()
{
    // If a custom value was set, don't sync the property
    if (flags & CustomEnabled) {
        return;
    }
    // If a custom binding was set, don't sync the property
    if (hasBindingOnProperty(QStringLiteral("enabled"))) {
        flags |= CustomEnabled;
        return;
    }
    // Sync it, and reset the flag. See `itemChange()`.
    bool enabled = action ? action->m_enabled : true;
    q_func()->setEnabled(enabled);
    flags &= ~CustomEnabled;
}

// invoke actions' overridden triger() function
void UCActionItemPrivate::_q_invokeActionTrigger(const QVariant &value)
{
    invokeTrigger<UCAction>(action, value);
}

void UCActionItemPrivate::updateProperties()
{
    Q_Q(UCActionItem);
    if (!(flags & CustomText)) {
        Q_EMIT q->textChanged();
    }
    if (!(flags & CustomIconSource)) {
        Q_EMIT q->iconSourceChanged();
    }
    if (!(flags & CustomIconName)) {
        Q_EMIT q->iconNameChanged();
    }
}

void UCActionItemPrivate::attachAction(bool attach)
{
    Q_Q(UCActionItem);
    if (attach) {
        action->addOwningItem(q);
        QObject::connect(q, SIGNAL(triggered(QVariant)),
                q, SLOT(_q_invokeActionTrigger(QVariant)), Qt::DirectConnection);
        if (!(flags & CustomVisible)) {
            QObject::connect(action, SIGNAL(visibleChanged()),
                    q, SLOT(_q_visibleBinding()), Qt::DirectConnection);
        }
        if (!(flags & CustomEnabled)) {
            QObject::connect(action, SIGNAL(enabledChanged()),
                    q, SLOT(_q_enabledBinding()), Qt::DirectConnection);
        }
        if (!(flags & CustomText)) {
            QObject::connect(action, &UCAction::textChanged,
                    q, &UCActionItem::textChanged, Qt::DirectConnection);
        }
        if (!(flags & CustomIconSource)) {
            QObject::connect(action, &UCAction::iconSourceChanged,
                    q, &UCActionItem::iconSourceChanged, Qt::DirectConnection);
        }
        if (!(flags & CustomIconName)) {
            QObject::connect(action, &UCAction::iconNameChanged,
                    q, &UCActionItem::iconNameChanged, Qt::DirectConnection);
        }
    } else {
        action->removeOwningItem(q);
        QObject::disconnect(q, SIGNAL(triggered(QVariant)),
                   q, SLOT(_q_invokeActionTrigger(QVariant)));
        if (!(flags & CustomVisible)) {
            QObject::disconnect(action, SIGNAL(visibleChanged()),
                       q, SLOT(_q_visibleBinding()));
        }
        if (!(flags & CustomEnabled)) {
            QObject::disconnect(action, SIGNAL(enabledChanged()),
                       q, SLOT(_q_enabledBinding()));
        }
        if (!(flags & CustomText)) {
            QObject::disconnect(action, &UCAction::textChanged,
                       q, &UCActionItem::textChanged);
        }
        if (!(flags & CustomIconSource)) {
            QObject::disconnect(action, &UCAction::iconSourceChanged,
                       q, &UCActionItem::iconSourceChanged);
        }
        if (!(flags & CustomIconName)) {
            QObject::disconnect(action, &UCAction::iconNameChanged,
                       q, &UCActionItem::iconNameChanged);
        }
    }
}

/*!
 * \qmlproperty Action ActionItem::action
 * The \l Action associated with this ActionItem. If action is set, the values
 * of the \l Action properties are copied to the values of the ActionItem
 * properties, unless those were previously overridden.
 */
UCAction* UCActionItem::action() const
{
    Q_D(const UCActionItem);
    return d->action;
}
void UCActionItem::setAction(UCAction *action)
{
    Q_D(UCActionItem);
    if (d->action == action) {
        return;
    }
    if (d->action) {
        d->attachAction(false);
    }
    d->action = action;
    Q_EMIT actionChanged();

    if (d->action) {
        d->attachAction(true);
    }
    d->_q_visibleBinding();
    d->_q_enabledBinding();
    d->updateProperties();
}

/*!
 * \qmlproperty string ActionItem::text
 * The title of the actionItem. Defaults to the \c action.text.
 */
QString UCActionItem::text()
{
    Q_D(UCActionItem);
    if (d->flags & UCActionItemPrivate::CustomText) {
        return d->text;
    }
    return d->action ? d->action->text() : QString();
}
void UCActionItem::setText(const QString &text)
{
    Q_D(UCActionItem);

    if (d->action && !(d->flags & UCActionItemPrivate::CustomText)) {
        // disconnect change signal from Action
        disconnect(d->action, &UCAction::textChanged,
                   this, &UCActionItem::textChanged);
    }
    d->flags |= UCActionItemPrivate::CustomText;

    if (d->text == text) {
        return;
    }
    d->text = text;
    Q_EMIT textChanged();
}
void UCActionItem::resetText()
{
    Q_D(UCActionItem);
    d->text.clear();
    d->flags &= ~UCActionItemPrivate::CustomText;
    if (d->action) {
        // re-connect change signal from Action
        connect(d->action, &UCAction::textChanged,
                this, &UCActionItem::textChanged, Qt::DirectConnection);
    }
    Q_EMIT textChanged();
}

/*!
 * \qmlproperty url ActionItem::iconSource
 * The image associated with the ActionItem. Defaults to \c action.iconSource.
 *
 * This is the URL of any image file. If both iconSource and \l iconName are defined,
 * \l iconName will be ignored.
 */
QUrl UCActionItem::iconSource()
{
    Q_D(UCActionItem);
    if (d->flags & UCActionItemPrivate::CustomIconSource) {
        return d->iconSource;
    }
    if (d->action) {
        return d->action->m_iconSource;
    }
    return !iconName().isEmpty() ?
        QUrl(QStringLiteral("image://theme/%1").arg(iconName())) : QUrl();
}
void UCActionItem::setIconSource(const QUrl &iconSource)
{
    Q_D(UCActionItem);

    if (d->action && !(d->flags & UCActionItemPrivate::CustomIconSource)) {
        // disconnect change signal from Action
        disconnect(d->action, &UCAction::iconSourceChanged,
                   this, &UCActionItem::iconSourceChanged);
    }
    d->flags |= UCActionItemPrivate::CustomIconSource;

    if (d->iconSource == iconSource) {
        return;
    }
    d->iconSource = iconSource;
    Q_EMIT iconSourceChanged();
}
void UCActionItem::resetIconSource()
{
    Q_D(UCActionItem);
    d->iconSource.clear();
    d->flags &= ~UCActionItemPrivate::CustomIconSource;
    if (d->action) {
        // re-connect change signal from Action
        connect(d->action, &UCAction::iconSourceChanged,
                this, &UCActionItem::iconSourceChanged, Qt::DirectConnection);
    }
    Q_EMIT iconSourceChanged();
}

/*!
 * \qmlproperty string ActionItem::iconName
 * The icon associated with the actionItem in the suru icon theme. Defaults to
 * \c action.iconName.
 *
 * \note The complete list of icons available in Lomiri is not published yet.
 * For now please refer to the folders where the icon themes are installed:
 * \list
 *  \li Lomiri Touch: \l file:/usr/share/icons/suru
 *  \li Lomiri Desktop: \l file:/usr/share/icons/lomiri-mono-dark
 * \endlist
 * These 2 separate icon themes will be merged soon.
 *
 * If both \l iconSource and iconName are defined, iconName will be ignored.
 */
QString UCActionItem::iconName()
{
    Q_D(UCActionItem);
    if (d->flags & UCActionItemPrivate::CustomIconName) {
        return d->iconName;
    }
    return d->action ? d->action->m_iconName : QString();
}
void UCActionItem::setIconName(const QString &iconName)
{
    Q_D(UCActionItem);

    if (d->action && !(d->flags & UCActionItemPrivate::CustomIconName)) {
        // disconnect change signal from Action
        disconnect(d->action, &UCAction::iconNameChanged,
                   this, &UCActionItem::iconNameChanged);
    }
    d->flags |= UCActionItemPrivate::CustomIconName;

    if (d->iconName == iconName) {
        return;
    }
    d->iconName = iconName;
    Q_EMIT iconNameChanged();
    // also sync iconSource if that is not a custom one or taken from action
    if (!d->action || (d->flags & UCActionItemPrivate::CustomIconSource)) {
        Q_EMIT iconSourceChanged();
    }
}
void UCActionItem::resetIconName()
{
    Q_D(UCActionItem);
    d->iconName.clear();
    d->flags &= ~UCActionItemPrivate::CustomIconName;
    if (d->action) {
        // re-connect change signal from Action
        connect(d->action, &UCAction::iconNameChanged,
                this, &UCActionItem::iconNameChanged, Qt::DirectConnection);
    }
    Q_EMIT iconNameChanged();
}

/*!
 * \qmlmethod void ActionItem::trigger(var value)
 * Trigger this action item if it is enabled.
 */
void UCActionItem::trigger(const QVariant &value)
{
    if (isEnabled()) {
        Q_EMIT triggered(value);
    }
}

UT_NAMESPACE_END

#include "moc_ucactionitem_p.cpp"
