/***************************************************************************
 *   Copyright (C) 2015-2016 by Savoir-faire Linux                              *
 *   Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com>*
 *                                                                         *
 *   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 3 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, see <http://www.gnu.org/licenses/>. *
 **************************************************************************/
#include "accountserializationadapter.h"

#include <QtWidgets/QWidget>
#include <QtWidgets/QLayout>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QSpinBox>
#include <QtWidgets/QGroupBox>
#include <QtWidgets/QFormLayout>
#include <QtWidgets/QAbstractButton>
#include <QtWidgets/QLabel>

#include <account.h>
#include <accountmodel.h>

constexpr static const char LRC_CFG[]   = "lrcfg_";
constexpr static const int  LRC_CFG_LEN = 6       ;

struct ConnHolder {
    QMetaObject::Connection c;
    ~ConnHolder();
};
Q_DECLARE_METATYPE(ConnHolder*);

ConnHolder::~ConnHolder()
{
    QObject::disconnect(c);
}

static void avoidDuplicate(QWidget* w)
{
    if (qvariant_cast<ConnHolder*>(w->property("lrcfgConn")))
        delete qvariant_cast<ConnHolder*>(w->property("lrcfgConn"));
}

/**
 * This check for some supported widgets and bind the widgets and property
 */
static void setupWidget(QWidget* w, Account* a, const QHash<QByteArray, int>& roles)
{
    if (w->objectName().left(LRC_CFG_LEN) == LRC_CFG) {
        const QByteArray prop = w->objectName().mid(LRC_CFG_LEN, 999).toLatin1();
        if (roles.contains(prop)) {
            const int role = roles[prop];
            if (qobject_cast<QLineEdit*>(w)) {
                QLineEdit* le = qobject_cast<QLineEdit*>(w);
                avoidDuplicate(le);
                le->setText(a->roleData(role).toString());
                ConnHolder* c = new ConnHolder {
                        QObject::connect(le, &QLineEdit::textChanged, [a,role](const QString& text) {
                    if (a->roleData(role) != text)
                        a->setRoleData(role, text);
                })
            };
                le->setProperty("lrcfgConn",QVariant::fromValue(c));
            }
            else if (qobject_cast<QSpinBox*>(w)) {
                QSpinBox* sb = qobject_cast<QSpinBox*>(w);
                avoidDuplicate(sb);
                sb->setValue(a->roleData(role).toInt());
                ConnHolder* c = new ConnHolder {
                        QObject::connect(sb, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [a,role](int value) {
                    if (a->roleData(role).toInt() != value)
                        a->setRoleData(role, value);
                })
            };
                sb->setProperty("lrcfgConn",QVariant::fromValue(c));
            }
            else if  (qobject_cast<QAbstractButton*>(w)) {
                //QCheckBox, QRadioButton, QToolButton, QPushButton
                QAbstractButton* b = qobject_cast<QAbstractButton*>(w);
                avoidDuplicate(b);
                b->setChecked(a->roleData(role).toBool());
                ConnHolder* c = new ConnHolder {
                        QObject::connect(b, &QAbstractButton::toggled,[a,role](bool c) {
                    if (a->roleData(role).toBool() != c)
                        a->setRoleData(role, c);
                })
            };
                b->setProperty("lrcfgConn",QVariant::fromValue(c));
            }
            else if  (qobject_cast<QGroupBox*>(w)) {
                QGroupBox* b = qobject_cast<QGroupBox*>(w);
                avoidDuplicate(b);
                b->setChecked(a->roleData(role).toBool());
                ConnHolder* c = new ConnHolder {
                        QObject::connect(b, &QGroupBox::toggled,[a,role](bool c) {
                    if (a->roleData(role).toBool() != c)
                        a->setRoleData(role, c);
                })
            };
                b->setProperty("lrcfgConn",QVariant::fromValue(c));
            }
            else {
                qDebug() << "Unsupported widget type" << w;
            }

            //Check if the field is required for this account type
            if (a->roleState((Account::Role)role) == Account::RoleState::UNAVAILABLE) {

                w->setProperty("lrcfgVisible", w->isVisible() ? 2 : 1);
                w->setVisible(false);

                QFormLayout* fm = qobject_cast<QFormLayout*>(w->parentWidget()->layout());

                //There is many of corner case here, this only handle the one that's
                //created by Qt Designer
                if (!fm) {
                    QLayoutItem* il = w->parentWidget()->layout()->itemAt(0);
                    if (il && il->layout())
                        fm = qobject_cast<QFormLayout*>(il->layout());
                }

                if (fm) {
                    QWidget* buddy = fm->labelForField(w);
                    qDebug() << "BUDDY " << buddy;
                    if (buddy)
                        buddy->setVisible(false);

                }
            }
            else {
                //0 = unset, 1=invisible, 2=visible
                const int oldVisibleState = w->property("lrcfgVisible").toInt();
                if (oldVisibleState)
                    w->setVisible(oldVisibleState-1);
            }
        }
        else {
            qWarning() << "Unknown config properties" << w->objectName();
        }
    }
}

static void clearConnections(QWidget* w)
{
    if (w->objectName().left(LRC_CFG_LEN) == LRC_CFG) {
        avoidDuplicate(w);
    }
}

static void drill(QWidget* w, Account* a, const QHash<QByteArray, int>& roles, bool clear = false)
{
    for (QObject *object : w->children()) {
        if (!object->isWidgetType())
            continue;

        QWidget* w2 = static_cast<QWidget*>(object);
        if (clear)
            clearConnections(w2);
        else
            setupWidget(w2, a, roles);

        drill(w2, a, roles);
    }
}

static void hideLabel(QWidget* w) {
    for (QObject *object : w->children()) {
        if (!object->isWidgetType())
            continue;
        QWidget* w2 = qobject_cast<QWidget*>(object);
        if (w2) {
            QLabel* l =  qobject_cast<QLabel*>(w2);
            if (l && l->buddy()) {
                l->setVisible(!l->buddy()->isHidden());
            }
        }
        hideLabel(w2);
    }
}

AccountSerializationAdapter::AccountSerializationAdapter(Account* a, QWidget* w) : QObject(w)
{
    static QHash<QByteArray, int> reverse;
    if (reverse.isEmpty()) {
        const QHash<int, QByteArray> a = AccountModel::instance().roleNames();
        for (QHash<int, QByteArray>::const_iterator i = a.begin(); i != a.end(); ++i) {
            reverse[i.value()] = i.key();
        }
    }

    drill(w, a, reverse);
    hideLabel(w);
}

AccountSerializationAdapter::~AccountSerializationAdapter()
{

}