/**
 * UGENE - Integrated Bioinformatics Tools.
 * Copyright (C) 2008-2013 UniPro <ugene@unipro.ru>
 * http://ugene.unipro.ru
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 */

#include <QVBoxLayout>
#include <QLabel>
#include <QSizePolicy>

#include <U2Core/AppContext.h>
#include <U2Core/U2OpStatusUtils.h>
#include <U2Core/U2SafePoints.h>

#include <U2Gui/MainWindow.h>

#include <U2Lang/WizardPage.h>
#include <U2Lang/WorkflowUtils.h>

#include "ElementSelectorController.h"
#include "PairedDatasetsController.h"
#include "PropertyWizardController.h"
#include "RadioController.h"
#include "WDWizardPage.h"
#include "WizardPageController.h"

#include "WizardController.h"

#define WIZARD_SAFE_POINT_EXT(condition, message, extraOp, result)  \
    if (!(condition)) { \
    coreLog.error(QString("Wizard error: %1").arg(message)); \
    extraOp; \
    return result; \
    } \

#define WIZARD_SAFE_POINT_OP_EXT(os, extraOp, result)  \
    WIZARD_SAFE_POINT_EXT(!os.hasError(), os.getError(), extraOp, result) \

#define WIZARD_SAFE_POINT(condition, message, result)  \
    WIZARD_SAFE_POINT_EXT(condition, message, setBroken(), result) \

#define WIZARD_SAFE_POINT_OP(os, result)  \
    WIZARD_SAFE_POINT_OP_EXT(os, setBroken(), result) \

namespace U2 {

WizardController::WizardController(Schema *s, const Wizard *w)
: QObject(), schema(s), wizard(w), runAfterApply(false)
{
    broken = false;
    currentActors = s->getProcesses();
    vars = w->getVariables();
}

WizardController::~WizardController() {
    qDeleteAll(pageControllers);
}

QWizard * WizardController::createGui() {
    QWizard *result = new QWizard((QWidget*)AppContext::getMainWindow()->getQMainWindow());
    setupRunButton(result);

    int idx = 0;
    foreach (WizardPage *page, wizard->getPages()) {
        result->setPage(idx, createPage(page));
        pageIdMap[page->getId()] = idx;
        idx++;
    }
    result->setWizardStyle(QWizard::ClassicStyle);
    result->setModal(true);
    result->setAutoFillBackground(true);
    result->setWindowTitle(wizard->getName());

    QString finishLabel = wizard->getFinishLabel();
    if (finishLabel.isEmpty()) {
        finishLabel = tr("Apply");
    }
    result->setButtonText(QWizard::FinishButton, finishLabel);
    result->setOption(QWizard::NoBackButtonOnStartPage);

    return result;
}

void WizardController::setupRunButton(QWizard *gui) {
    CHECK(wizard->hasRunButton(), );
    gui->setOption(QWizard::HaveCustomButton1);
    gui->setButtonText(QWizard::CustomButton1, tr("Run"));
    QAbstractButton *runButton = gui->button(QWizard::CustomButton1);
    connect(runButton, SIGNAL(clicked()), SLOT(sl_run()));
    connect(runButton, SIGNAL(clicked()), gui, SLOT(accept()));
    QList<QWizard::WizardButton> order;
    order << QWizard::Stretch << QWizard::BackButton << QWizard::NextButton << QWizard::FinishButton << QWizard::CustomButton1 << QWizard::CancelButton;
    gui->setButtonLayout(order);
}

bool WizardController::isRunAfterApply() const {
    return runAfterApply;
}

void WizardController::sl_run() {
    runAfterApply = true;
}

void WizardController::assignParameters() {
    foreach (const QString &attrId, values.keys()) {
        U2OpStatus2Log os;
        AttributeInfo info = AttributeInfo::fromString(attrId, os);
        Attribute *attr = getAttribute(info);
        if (NULL == attr) {
            continue;
        }
        attr->setAttributeValue(values[attrId]);
    }
}

void WizardController::saveDelegateTags() {
    foreach (const QString &attrId, propertyControllers.keys()) {
        U2OpStatus2Log os;
        AttributeInfo info = AttributeInfo::fromString(attrId, os);
        DelegateTags *tags = propertyControllers[attrId]->tags();
        if (NULL == tags) {
            continue;
        }
        Actor *actor = WorkflowUtils::actorById(currentActors, info.actorId);
        PropertyDelegate *delegate = actor->getEditor()->getDelegate(info.attrId);
        if (NULL == delegate) {
            continue;
        }
        delegate->tags()->set(*tags);
    }
}

const QList<Actor*> & WizardController::getCurrentActors() const {
    return currentActors;
}

QVariant WizardController::getAttributeValue(const AttributeInfo &info) const {
    if (values.contains(info.toString())) {
        return values[info.toString()];
    }
    QString attrId;
    Attribute *attr = getAttribute(info);
    CHECK(NULL != attr, QVariant());

    return attr->getAttributePureValue();
}

DelegateTags * WizardController::getTags(const AttributeInfo &info) {
    CHECK(propertyControllers.contains(info.toString()), NULL);
    return propertyControllers[info.toString()]->tags();
}

void WizardController::setAttributeValue(const AttributeInfo &info, const QVariant &value) {
    values[info.toString()] = value;

    // Check attribute relations
    Attribute *attr = getAttribute(info);
    CHECK(NULL != attr, );
    foreach (const AttributeRelation *relation, attr->getRelations()) {
        if (!relation->valueChangingRelation()) {
            continue;
        }
        AttributeInfo related(info.actorId, relation->getRelatedAttrId());
        QVariant newValue = relation->getAffectResult(value, getAttributeValue(related), getTags(info), getTags(related));
        setAttributeValue(related, newValue);
        if (propertyControllers.contains(related.toString())) {
            propertyControllers[related.toString()]->updateGUI(newValue);
        }
    }
}

Attribute * WizardController::getAttribute(const AttributeInfo &info) const {
    U2OpStatusImpl os;
    info.validate(currentActors, os);
    CHECK_OP(os, NULL);
    Actor *actor = WorkflowUtils::actorById(currentActors, info.actorId);
    return actor->getParameter(info.attrId);
}

QWizardPage * WizardController::createPage(WizardPage *page) {
    WizardPageController *controller = new WizardPageController(this, page);
    WDWizardPage *result = new WDWizardPage(controller);

    pageControllers << controller;

    return result;
}

int WizardController::getQtPageId(const QString &hrId) const {
    return pageIdMap[hrId];
}

const QMap<QString, Variable> & WizardController::getVariables() const {
    return vars;
}

QVariant WizardController::getVariableValue(const QString &var) {
    WIZARD_SAFE_POINT(vars.contains(var),
        QObject::tr("Undefined variable: %1").arg(var), QVariant());
    return vars[var].getValue();
}

void WizardController::setVariableValue(const QString &var, const QString &value) {
    WIZARD_SAFE_POINT(vars.contains(var),
        QObject::tr("Undefined variable: %1").arg(var), );
    vars[var].setValue(value);
}

QVariant WizardController::getSelectorValue(ElementSelectorWidget *widget) {
    WIZARD_SAFE_POINT(vars.contains(widget->getActorId()),
        QObject::tr("Undefined variable: %1").arg(widget->getActorId()), QVariant());
    Variable &v = vars[widget->getActorId()];
    if (v.isAssigned()) {
        return v.getValue();
    }

    // variable is not assigned yet => selector is not registered
    registerSelector(widget);
    SelectorValue sv = widget->getValues().first();
    v.setValue(sv.getValue());
    // now variable is assigned, selector is registered
    setSelectorValue(widget, sv.getValue());
    return sv.getValue();
}

void WizardController::setSelectorValue(ElementSelectorWidget *widget, const QVariant &value) {
    WIZARD_SAFE_POINT(vars.contains(widget->getActorId()),
        QObject::tr("Undefined variable: %1").arg(widget->getActorId()), );
    Variable &v = vars[widget->getActorId()];
    v.setValue(value.toString());
    replaceCurrentActor(widget->getActorId(), value.toString());
}

void WizardController::registerSelector(ElementSelectorWidget *widget) {
    if (selectors.contains(widget->getActorId())) {
        WIZARD_SAFE_POINT(false, QObject::tr("Actors selector is already defined: %1").arg(widget->getActorId()), );
    }
    U2OpStatusImpl os;
    SelectorActors actors(widget, currentActors, os);
    WIZARD_SAFE_POINT_OP(os, );
    selectors[widget->getActorId()] = actors;
}

void WizardController::replaceCurrentActor(const QString &actorId, const QString &selectorValue) {
    if (!selectors.contains(actorId)) {
        WIZARD_SAFE_POINT(false, QObject::tr("Unknown actors selector: %1").arg(actorId), );
    }
    Actor *currentA = WorkflowUtils::actorById(currentActors, actorId);
    WIZARD_SAFE_POINT(NULL != currentA, QObject::tr("Unknown actor id: %1").arg(actorId), );
    Actor *newA = selectors[actorId].getActor(selectorValue);
    WIZARD_SAFE_POINT(NULL != newA, QObject::tr("Unknown actors selector value id: %1").arg(selectorValue), );

    int idx = currentActors.indexOf(currentA);
    currentActors.replace(idx, newA);
}

void WizardController::setBroken() {
    broken = true;
}

bool WizardController::isBroken() const {
    return broken;
}

WizardController::ApplyResult WizardController::applyChanges(Metadata &meta) {
    if (isBroken()) {
        return BROKEN;
    }
    assignParameters();
    saveDelegateTags();
    if (selectors.isEmpty()) {
        return OK;
    }

    ApplyResult result = OK;
    foreach (const QString &varName, selectors.keys()) {
        WIZARD_SAFE_POINT(vars.contains(varName),
            QObject::tr("Undefined variable: %1").arg(varName), BROKEN);
        Variable &v = vars[varName];
        SelectorActors &s = selectors[varName];
        Actor *newActor = s.getActor(v.getValue());
        Actor *oldActor = s.getSourceActor();
        if (newActor != oldActor) {
            result = ACTORS_REPLACED;
            schema->replaceProcess(oldActor, newActor, s.getMappings(v.getValue()));
            meta.replaceProcess(oldActor->getId(), newActor->getId(), s.getMappings(v.getValue()));
        }
    }
    return result;
}

void WizardController::addPropertyController(const AttributeInfo &info, PropertyWizardController *ctrl) {
    propertyControllers[info.toString()] = ctrl;
}

/************************************************************************/
/* WidgetCreator */
/************************************************************************/
WidgetCreator::WidgetCreator(WizardController *_wc)
: wc(_wc), labelSize(0), result(NULL), layout(NULL), fullWidth(false)
{

}

WidgetCreator::WidgetCreator(WizardController *_wc, int _labelSize)
: wc(_wc), labelSize(_labelSize), result(NULL), layout(NULL), fullWidth(false)
{

}

bool WidgetCreator::hasFullWidth() {
    return fullWidth;
}

void WidgetCreator::visit(AttributeWidget *aw) {
    QString type = aw->getProperty(AttributeInfo::TYPE);
    PropertyWizardController *controller = NULL;

    if (AttributeInfo::DEFAULT == type) {
        controller = new DefaultPropertyController(wc, aw, labelSize);
    } else if (AttributeInfo::DATASETS == type) {
        controller = new InUrlDatasetsController(wc, aw);
        fullWidth = true;
    } else {
        WIZARD_SAFE_POINT_EXT(false, QString("Unknown widget type: %1").arg(type), wc->setBroken(), );
    }

    controllers << controller;
    U2OpStatusImpl os;
    result = controller->createGUI(os);
    WIZARD_SAFE_POINT_OP_EXT(os, wc->setBroken(), );
}

void WidgetCreator::visit(WidgetsArea *wa) {
    QWidget *scrollContent = new QWidget();
    layout = new QVBoxLayout();
    layout->setContentsMargins(0, 0, 0, 0);
    scrollContent->setLayout(layout);
    foreach (WizardWidget *w, wa->getWidgets()) {
        int labelSize = wa->getLabelSize();
#if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
        labelSize *= 1.3;
#elif defined(Q_OS_LINUX)
        labelSize *= 1.4;
#endif
        WidgetCreator wcr(wc, labelSize);
        w->accept(&wcr);
        if (NULL != wcr.getResult()) {
            if (!wcr.hasFullWidth()) {
                wcr.getResult()->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Maximum);
            } else {
                fullWidth = true;
            }
            layout->addWidget(wcr.getResult());
            controllers << wcr.getControllers();
        }
    }
    if (!fullWidth) {
        QSpacerItem *spacer = new QSpacerItem(0, 0, QSizePolicy::Maximum, QSizePolicy::Minimum);
        layout->addSpacerItem(spacer);
    }
    setupScrollArea(scrollContent);
}

void WidgetCreator::setupScrollArea(QWidget *scrollContent) {
    scrollContent->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
    widgetsArea = new QScrollArea();
    widgetsArea->setWidget(scrollContent);
    widgetsArea->setWidgetResizable(true);
    widgetsArea->setFrameShape(QFrame::NoFrame);
    widgetsArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    result = widgetsArea;
}

void WidgetCreator::visit(GroupWidget *gw) {
    visit((WidgetsArea*)gw);
    result = widgetsArea->takeWidget();
    delete widgetsArea;
    widgetsArea = NULL;

    bool collapsible = (gw->getType() == GroupWidget::HIDEABLE);
    GroupBox *gb = new GroupBox(collapsible, gw->getTitle(), fullWidth);
    setGroupBoxLayout(gb);
}

void WidgetCreator::visit(LogoWidget *lw) {
    result = new QWidget();
    layout = new QVBoxLayout();
    layout->setContentsMargins(0, 0, 0, 0);
    result->setLayout(layout);

    QLabel *label = new QLabel(result);
    QPixmap pix;
    if (lw->isDefault()) {
        pix = QPixmap(QString(":U2Designer/images/logo.png"));
    } else {
        pix = QPixmap(lw->getLogoPath());
    }
    pix = pix.scaled(255, 450, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
    label->setPixmap(pix);
    layout->addWidget(label);
    result->setFixedSize(pix.size());
}

void WidgetCreator::visit(ElementSelectorWidget *esw) {
    ElementSelectorController *controller = new ElementSelectorController(wc, esw, labelSize);
    controllers << controller;
    U2OpStatusImpl os;
    result = controller->createGUI(os);
}

void WidgetCreator::visit(PairedReadsWidget *dsw) {
    PairedDatasetsController *controller = new PairedDatasetsController(wc, dsw);
    controllers << controller;
    U2OpStatusImpl os;
    result = controller->createGUI(os);
    fullWidth = true;
}

void WidgetCreator::visit(RadioWidget *rw) {
    RadioController *controller = new RadioController(wc, rw);
    controllers << controller;
    U2OpStatusImpl os;
    result = controller->createGUI(os);
}

QWidget * WidgetCreator::getResult() {
    return result;
}

QList<WidgetController*> & WidgetCreator::getControllers() {
    return controllers;
}

QBoxLayout * WidgetCreator::getLayout() {
    return layout;
}

void WidgetCreator::setGroupBoxLayout(GroupBox *gb) {
    gb->setLayout(layout);
    delete result;
    result = gb;
}

/************************************************************************/
/* PageContentCreator */
/************************************************************************/
PageContentCreator::PageContentCreator(WizardController *_wc)
    : wc(_wc), result(NULL), pageTitle(NULL)
{

}

void PageContentCreator::visit(DefaultPageContent *content) {
    QHBoxLayout *layout = new QHBoxLayout();
    layout->setContentsMargins(0, 0, 0, 0);
    QVBoxLayout *contentLayout = new QVBoxLayout();
    contentLayout->setContentsMargins(0, 0, 0, 0);
    int paramsHeight = content->getPageDefaultHeight();
    int paramsWidth = content->getPageWidth();
    { // create logo
        WidgetCreator logoWC(wc);
        content->getLogoArea()->accept(&logoWC);
        if (NULL != logoWC.getResult()) {
            paramsHeight = logoWC.getResult()->height();
            paramsWidth -= logoWC.getResult()->width();
            layout->addWidget(logoWC.getResult());
            controllers << logoWC.getControllers();
        }
    }
    createTitle(contentLayout);
    createSubTitle(contentLayout);
    { //TODO: compute real title and subtitle height
        paramsHeight = 0;
    }
    { // create parameters
        WidgetCreator paramsWC(wc);
        content->getParamsArea()->accept(&paramsWC);
        if (NULL != paramsWC.getResult()) {
            if (NULL != paramsWC.getLayout() && !paramsWC.hasFullWidth()) {
                QSpacerItem *spacer = new QSpacerItem(0, 0, QSizePolicy::Maximum, QSizePolicy::MinimumExpanding);
                paramsWC.getLayout()->addSpacerItem(spacer);
            }
            contentLayout->addWidget(paramsWC.getResult());
            paramsWC.getResult()->setMinimumSize(paramsWidth, paramsHeight);
            controllers << paramsWC.getControllers();
        }
    }
    layout->addLayout(contentLayout);
    layout->setAlignment(Qt::AlignBottom);
    result = layout;
}

void PageContentCreator::setPageTitle(const QString& title) {
    if (NULL != pageTitle && false == title.isEmpty()) {
        pageTitle->setText(title);
        pageTitle->show();
    }
}

void PageContentCreator::setPageSubtitle(const QString& subtitle) {
    if (NULL != pageSubtitle && false == subtitle.isEmpty()) {
        pageSubtitle->setText(subtitle);
        pageSubtitle->show();
    }
}

QLayout * PageContentCreator::getResult() {
    return result;
}

QList<WidgetController*> & PageContentCreator::getControllers() {
    return controllers;
}

void PageContentCreator::createTitle(QVBoxLayout *contentLayout) {
    pageTitle = new QLabel();
    pageTitle->setWordWrap(true);
#ifdef Q_OS_MAC
    pageTitle->setStyleSheet("QLabel {font-size: 20pt; padding-bottom: 10px; color: #0c3762}");
#else
    pageTitle->setStyleSheet("QLabel {font-size: 16pt; padding-bottom: 10px; color: #0c3762}");
#endif
    pageTitle->resize(0, 0);
    pageTitle->hide();
    contentLayout->addWidget(pageTitle);
}

void PageContentCreator::createSubTitle(QVBoxLayout *contentLayout) {
    pageSubtitle = new QLabel();
    pageSubtitle->setWordWrap(true);
    pageSubtitle->resize(0, 0);
    pageSubtitle->hide();
    contentLayout->addWidget(pageSubtitle);
}

/************************************************************************/
/* GroupBox */
/************************************************************************/
const int GroupBox::MARGIN = 5;

GroupBox::GroupBox(bool collapsible, const QString &title, bool fullWidth)
: QGroupBox(title), hLayout(NULL), tip(NULL), showHideButton(NULL)
{
    ui = new QWidget(this);
    QSizePolicy::Policy vPolicy = fullWidth ? QSizePolicy::MinimumExpanding : QSizePolicy::Maximum;
    ui->setSizePolicy(QSizePolicy::Minimum, vPolicy);
    QVBoxLayout *layout = new QVBoxLayout();
    QGroupBox::setLayout(layout);

#ifdef Q_OS_MAC
    layout->setContentsMargins(0, 0, 0, 0);
    QString style = "QGroupBox  {"
                    "margin-top: 30px;" // leave space at the top for the title
                    "padding: 5 5 5 5px;"
                    "}"

                    "QGroupBox::title  {"
                    "subcontrol-origin: margin;"
                    "padding-top: 12px;"
                    "padding-left: 15px;"
                    "}";
    setStyleSheet(style);
#else
    layout->setContentsMargins(MARGIN, MARGIN, MARGIN, MARGIN);
#endif

    if (collapsible) {
        hLayout = new QHBoxLayout();
        tip = new QLabel(this);
        hLayout->addWidget(tip);
        hLayout->setContentsMargins(0, 0, 0, 0);
        showHideButton = new QToolButton(this);
        showHideButton->setFixedSize(19, 19);
        hLayout->addWidget(showHideButton);
        layout->addLayout(hLayout);
    }

    layout->addWidget(ui);

    if (collapsible) {
        sl_collapse();
        connect(showHideButton, SIGNAL(clicked()), this, SLOT(sl_onCheck()));
    }
}

void GroupBox::sl_collapse() {
    ui->hide();
    changeView("+", tr("Show"));
}

void GroupBox::sl_expand() {
    ui->show();
    changeView("-", tr("Hide"));
}

void GroupBox::setLayout(QLayout *l) {
    ui->setLayout(l);
}

void GroupBox::sl_onCheck() {
    if (ui->isVisible()) {
        sl_collapse();
    } else {
        sl_expand();
    }
}

void GroupBox::changeView(const QString &buttonText, const QString &showHide) {
    CHECK(NULL != showHideButton, );
    showHideButton->setText(buttonText);

    CHECK(NULL != tip, );
    QString parametersStr = tr("additional");
    if (!title().isEmpty()) {
        parametersStr = title().toLower();
    }
    tip->setText(showHide + " " + parametersStr + tr(" parameters"));
    showHideButton->setToolTip(tip->text());

    CHECK(NULL != hLayout, );
    if (!title().isEmpty()) {
        QMargins m = layout()->contentsMargins();
        m.setTop(0);
        layout()->setContentsMargins(m);
    }
}

} // U2
