/***************************** LICENSE START ***********************************

 Copyright 2019 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include "VerbFunctionHelpWidget.h"
#include "VerbFunctionHelp.h"
#include "LanguageHelper.h"
#include "EditorTheme.h"
#include "IconClassHelp.h"
#include "MessageLabel.h"
#include "MvQColourWidget.h"
#include "MvQMethods.h"
#include "MvQTheme.h"
#include "MvQTreeView.h"
#include "MvMiscellaneous.h"

#include <QtGlobal>
#include <QItemSelectionModel>
#include <QTreeView>
#include <QVBoxLayout>
#include <QMessageBox>

#include "ui_VerbFunctionHelpWidget.h"

//================================================
//
// ParamValueEditor
//
//================================================

ParamValueEditor::ParamValueEditor(QString id, int index, QWidget* parent) :
    QObject(parent),
    id_(id),
    index_(index),
    param_(nullptr)
{
}

void ParamValueEditor::broadcastChange()
{
    emit valueChanged(value());
}

//================================================
//
// ParamColourValueEditor
//
//================================================

ParamColourValueEditor::ParamColourValueEditor(QString id, int index, QWidget* parent) :
    ParamValueEditor(id, index, parent)
{
    // selector area
    auto* vb = new QVBoxLayout();
    parent->setLayout(vb);

    colW_ = new MvQColourWidget(parent);
    vb->addWidget(colW_);
    vb->addStretch(1);

    connect(colW_, SIGNAL(valueChanged(QString)),
            this, SIGNAL(valueChanged(QString)));
}

QWidget* ParamColourValueEditor::realWidget()
{
    return colW_;
}

void ParamColourValueEditor::clear()
{
    param_ = nullptr;
    colW_->hide();
}

void ParamColourValueEditor::setParam(VerbFunctionHelpParam* param)
{
    param_ = param;

    if (param_) {
        colW_->show();
        QStringList lst = param_->defaultValues();
        if (!lst.isEmpty())
            colW_->initColour(lst[0]);
    }
}

QString ParamColourValueEditor::value()
{
    if (param_) {
        return colW_->currentValue();
    }

    return {};
}

//================================================
//
// ParamListValueEditor
//
//================================================

ParamListValueEditor::ParamListValueEditor(QString id, int index, QWidget* parent) :
    ParamValueEditor(id, index, parent)
{
    // selector area
    valueTree_ = new QTreeView(parent);
    valueTree_->setUniformRowHeights(true);
    valueTree_->setRootIsDecorated(false);
    valueTree_->setHeaderHidden(true);

    valueModel_ = new VerbFunctionHelpValueModel(parent);
    valueTree_->setModel(valueModel_);

    connect(valueTree_, SIGNAL(clicked(QModelIndex)),
            this, SLOT(slotValueSelected(QModelIndex)));
}

QWidget* ParamListValueEditor::realWidget()
{
    return valueTree_;
}

void ParamListValueEditor::clear()
{
    valueModel_->clear();
    param_ = nullptr;
}

void ParamListValueEditor::setParam(VerbFunctionHelpParam* param)
{
    valueModel_->clear();
    param_ = param;

    if (param_) {
        valueModel_->setHelpData(param_);
    }
}

QString ParamListValueEditor::value()
{
    if (param_) {
        QModelIndex idx = valueTree_->currentIndex();
        if (idx.isValid()) {
            return param_->valueName(idx.row());
        }
    }

    return {};
}

void ParamListValueEditor::slotValueSelected(const QModelIndex&)
{
    broadcastChange();
}

void ParamListValueEditor::fontSizeUp()
{
    MvQ::changeFontSize(valueTree_, 1);
}

void ParamListValueEditor::fontSizeDown()
{
    MvQ::changeFontSize(valueTree_, -1);
}

//================================================
//
// VerbFunctionHelpParamModel
//
//================================================

VerbFunctionHelpParamModel::VerbFunctionHelpParamModel(QObject* parent) :
    QAbstractItemModel(parent),
    data_(nullptr)
{
}

bool VerbFunctionHelpParamModel::hasData() const
{
    return (data_ != nullptr);
}

int VerbFunctionHelpParamModel::columnCount(const QModelIndex& /*parent*/) const
{
    return 2;
}

int VerbFunctionHelpParamModel::rowCount(const QModelIndex& parent) const
{
    if (!hasData() || parent.isValid())
        return 0;

    return data_->numParams();
}

QVariant VerbFunctionHelpParamModel::data(const QModelIndex& index, int role) const
{
    if (!hasData() || !index.isValid()) {
        return {};
    }

    if (role != Qt::DisplayRole)
        return {};

    if (index.column() == 0)
        return data_->paramName(index.row());
    else if (index.column() == 1) {
        auto p = data_->param(index.row());
        if (p) {
            // for icon parameters we display the iconclass macro function name
            if (p->interface() == "icon") {
                QString f = p->interfaceFunction();
                if (!f.isEmpty())
                    f += "()";
                return f;
            }

            QStringList lst = p->defaultValues();
            if (lst.count() == 1)
                return lst[0];
            else if (lst.count() > 1) {
                return "[" + lst.join(",") + "]";
            }
        }
    }

    return {};
}

QVariant VerbFunctionHelpParamModel::headerData(const int section, const Qt::Orientation orient,
                                                const int role) const
{
    if (role == Qt::DisplayRole && orient == Qt::Horizontal) {
        if (section == 0)
            return tr("Icon parameter");
        else if (section == 1)
            return tr("Default");
    }

    return {};
}

Qt::ItemFlags VerbFunctionHelpParamModel::flags(const QModelIndex& index) const
{
    if (!index.isValid())
        return QAbstractItemModel::flags(index) | Qt::ItemIsDropEnabled;

    return QAbstractItemModel::flags(index) | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
}

QModelIndex VerbFunctionHelpParamModel::index(int row, int column, const QModelIndex&) const
{
    if (!data_ || row < 0 || column < 0) {
        return {};
    }

    return createIndex(row, column, static_cast<void*>(nullptr));
}


QModelIndex VerbFunctionHelpParamModel::parent(const QModelIndex&) const
{
    return {};
}

void VerbFunctionHelpParamModel::setHelpData(VerbFunctionHelpFunction* data)
{
    beginResetModel();
    data_ = data;
    endResetModel();
}

QModelIndex VerbFunctionHelpParamModel::paramToIndex(VerbFunctionHelpParam* param) const
{
    if (data_ && param) {
        int i = data_->indexOfParam(param);
        if (i != -1)
            return index(i, 0);
    }
    return {};
}

void VerbFunctionHelpParamModel::clear()
{
    beginResetModel();
    data_ = nullptr;
    endResetModel();
}

//================================================
//
// VerbFunctionHelpValueModel
//
//================================================

VerbFunctionHelpValueModel::VerbFunctionHelpValueModel(QObject* parent) :
    QAbstractItemModel(parent),
    data_(nullptr)
{
}

bool VerbFunctionHelpValueModel::hasData() const
{
    return (data_ != nullptr);
}

int VerbFunctionHelpValueModel::columnCount(const QModelIndex& /*parent*/) const
{
    return 1;
}

int VerbFunctionHelpValueModel::rowCount(const QModelIndex& parent) const
{
    if (!hasData() || parent.isValid())
        return 0;

    return data_->numValues();
}

QVariant VerbFunctionHelpValueModel::data(const QModelIndex& index, int role) const
{
    if (!hasData() || !index.isValid()) {
        return {};
    }

    if (role != Qt::DisplayRole)
        return {};

    return data_->valueName(index.row());
}

QVariant VerbFunctionHelpValueModel::headerData(const int /*section*/, const Qt::Orientation /*orient*/,
                                                const int /*role*/) const
{
    return {};
}

Qt::ItemFlags VerbFunctionHelpValueModel::flags(const QModelIndex& index) const
{
    if (!index.isValid())
        return QAbstractItemModel::flags(index) | Qt::ItemIsDropEnabled;

    return QAbstractItemModel::flags(index) | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
}

QModelIndex VerbFunctionHelpValueModel::index(int row, int column, const QModelIndex&) const
{
    if (!data_ || row < 0 || column < 0) {
        return {};
    }

    return createIndex(row, column, static_cast<void*>(nullptr));
}

QModelIndex VerbFunctionHelpValueModel::parent(const QModelIndex&) const
{
    return {};
}

void VerbFunctionHelpValueModel::setHelpData(VerbFunctionHelpParam* data)
{
    beginResetModel();
    data_ = data;
    endResetModel();
}

void VerbFunctionHelpValueModel::clear()
{
    beginResetModel();
    data_ = nullptr;
    endResetModel();
}


//================================================
//
// VerbFunctionHelpWidget
//
//================================================

VerbFunctionHelpWidget::VerbFunctionHelpWidget(QWidget* parent) :
    QWidget(parent),
    ui_(new Ui::VerbFunctionHelpWidget),
    editorTheme_(nullptr),
    language_(nullptr),
    currentEditor_(nullptr),
    extendSelection_(true)
{
    ui_->setupUi(this);

    connect(ui_->showFunctionListTb, SIGNAL(clicked()),
            this, SIGNAL(functionListRequested()));

    ui_->descriptionLabel->setWordWrap(true);
    ui_->descriptionLabel->setProperty("type", "info");

    connect(ui_->fontSizeDownTb, SIGNAL(clicked()),
            this, SLOT(slotFontSizeDown()));

    connect(ui_->fontSizeUpTb, SIGNAL(clicked()),
            this, SLOT(slotFontSizeUp()));

    //-------------------------------
    // Param list tab
    //-------------------------------

    // we have a tab widget with one tab only - it is a leftover from
    // a version with multiple tabs!
    // we simply hide the tabbar pretenting we do not have tabs at all
#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
    ui_->tab->setTabBarAutoHide(true);
#endif

    paramModel_ = new VerbFunctionHelpParamModel(this);
    ui_->paramTree->setModel(paramModel_);
    ui_->paramTree->setAlternatingRowColors(true);

    connect(ui_->paramTree->selectionModel(), SIGNAL(currentChanged(QModelIndex, QModelIndex)),
            this, SLOT(slotParamSelected(QModelIndex, QModelIndex)));

    //-----------------------------------
    // value selector/editor area
    //-----------------------------------

    int cnt = 0;
    ParamValueEditor* e = nullptr;
    e = new ParamListValueEditor("list", cnt, this);
    editors_["list"] = e;
    ui_->editorStackW->addWidget(e->realWidget());
    cnt++;

    e = new ParamColourValueEditor("colour", cnt, this);
    editors_["colour"] = e;
    ui_->editorStackW->addWidget(e->realWidget());
    cnt++;

    foreach (ParamValueEditor* e, editors_) {
        connect(e, SIGNAL(valueChanged(QString)),
                this, SLOT(slotEditorValueChanged(QString)));
    }


    connect(ui_->extendSelectionCheckBox, SIGNAL(stateChanged(int)),
            this, SLOT(slotExtendSelection(int)));

    extendSelection_ = true;
    ui_->extendSelectionCheckBox->setChecked(extendSelection_);

    connect(ui_->insertPb, SIGNAL(clicked()),
            this, SLOT(slotInsert()));

    connect(ui_->copyPb, SIGNAL(clicked()),
            this, SLOT(slotCopy()));

    connect(ui_->webDocPb, SIGNAL(clicked()),
            this, SLOT(slotWebDoc()));

    updateButtonStatus();

    ui_->tab->setCurrentIndex(0);

    // Adjust  tree columns
    QFont f;
    QFontMetrics fm(f);
    ui_->paramTree->setColumnWidth(0, MvQ::textWidth(fm, "grib_interpolation_method_missing_fill_count"));
}

void VerbFunctionHelpWidget::setLanguage(LanguageHelper* language)
{
    Q_ASSERT(language);

    language_ = language;
    paramModel_->clear();
}

void VerbFunctionHelpWidget::setCodeEditorTheme(EditorTheme* theme, QFont editorFont)
{
    editorTheme_ = theme;
    if (editorTheme_) {
        QString sh = "QLabel{background: " + editorTheme_->colour("background").name() +
                     "; color: " + editorTheme_->colour("text").name() + ";}";
        QFont f(editorFont.family());
        // f.setPointSize(f.pointSize()+1);
        f.setFixedPitch(true);
        f.setPointSize(currentTreeFontSize());
        ui_->selectionLabel->setStyleSheet(sh);
        ui_->selectionLabel->setFont(f);
    }
}

VerbFunctionHelpFunction* VerbFunctionHelpWidget::function(QString name) const
{
    return (language_) ? language_->functionHelp()->function(name) : nullptr;
}

bool VerbFunctionHelpWidget::showFunction(QString functionName)
{
    if (!hasData())
        return false;


    VerbFunctionHelpFunction* fn = language_->functionHelp()->function(functionName);
    if (fn && fn->iconClass()) {
        showFunction(language_->functionHelp()->function(functionName));
        if (fn->numParams() > 0) {
            ui_->paramTree->setCurrentIndex(paramModel_->index(0, 0));
        }
        return true;
    }

    return false;
}

void VerbFunctionHelpWidget::showFunction(VerbFunctionHelpFunction* fn)
{
    if (fn) {
        fn->loadParams();
        // populate the parameter tree with the available parameters
        paramModel_->setHelpData(fn);
    }

    updateFunctionLabel(fn);
    updateSelectionLabel();
    updateButtonStatus();
}

void VerbFunctionHelpWidget::slotParamSelected(const QModelIndex& idx, const QModelIndex& /*idxPrev*/)
{
    if (!idx.isValid()) {
        return;
    }

    if (VerbFunctionHelpFunction* function = paramModel_->helpData())
        showParamCore(function, function->param(idx.row()));
}

void VerbFunctionHelpWidget::showParam(VerbFunctionHelpFunction* function,
                                       VerbFunctionHelpParam* param)
{
    if (function && param) {
        if (function != paramModel_->helpData()) {
            showFunction(function);
        }
        ui_->paramTree->setCurrentIndex(paramModel_->paramToIndex(param));
        ui_->paramTree->scrollTo(ui_->paramTree->currentIndex(),
                                 QAbstractItemView::EnsureVisible);
    }
}

void VerbFunctionHelpWidget::showParamCore(VerbFunctionHelpFunction* function,
                                           VerbFunctionHelpParam* param)
{
    ui_->selectionLabel->clear();
    if (currentEditor_)
        currentEditor_->clear();

    if (!function)
        return;

    if (function && function != paramModel_->helpData()) {
        showFunction(function);
    }

    Q_ASSERT(function == paramModel_->helpData());

    if (param) {
        QString editorType = param->interface();
        if (editorType == "menu" || editorType == "option_menu" || editorType == "on_off")
            editorType = "list";

        if (!editorType.isEmpty()) {
            ParamValueEditor* e = editors_.value(editorType, nullptr);
            if (!e) {
                if (currentEditor_)
                    currentEditor_->clear();

                currentEditor_ = nullptr;
                ui_->valuesLabel->hide();
                ui_->editorStackW->hide();
            }
            else {
                if (currentEditor_ != e) {
                    currentEditor_ = e;
                    ui_->editorStackW->setCurrentWidget(e->realWidget());
                    ui_->valuesLabel->setVisible(true);
                    ui_->editorStackW->setVisible(true);
                }
                currentEditor_->setParam(param);
            }
        }
        else {
            if (currentEditor_) {
                currentEditor_->clear();
                currentEditor_ = nullptr;
            }

            ui_->valuesLabel->hide();
            ui_->editorStackW->hide();
        }
    }

    updateSelectionLabel();
    updateButtonStatus();
}


void VerbFunctionHelpWidget::slotEditorValueChanged(QString)
{
    updateSelectionLabel();
    updateButtonStatus();
}

QString VerbFunctionHelpWidget::selection(bool highlightValue) const
{
    QString s;
    if (!hasData())
        return s;

    bool hasVal = false;
    if (VerbFunctionHelpFunction* function = paramModel_->helpData()) {
        QModelIndex idx = ui_->paramTree->currentIndex();
        if (VerbFunctionHelpParam* param = function->param(idx.row())) {
            s += param->name() + " " + language_->functionParameterAssignChar() + " ";
            if (currentEditor_) {
                QString val = currentEditor_->value();
                if (!val.isEmpty()) {
                    if (highlightValue) {
                        s += "<font color=\'" + editorTheme_->colour("string").name() +
                             "\'>\'" + val + "\'<font>";
                    }
                    else {
                        s += "\'" + val + "\'";
                    }
                    hasVal = true;
                }
            }
        }
    }

    if (!s.isEmpty() && hasVal && extendSelection_) {
        s += ",";
    }

    return s;
}

void VerbFunctionHelpWidget::updateSelectionLabel()
{
    ui_->selectionLabel->setText(selection(true));
}

void VerbFunctionHelpWidget::updateFunctionLabel(VerbFunctionHelpFunction* fn)
{
    if (fn) {
        ui_->functionIconLabel->setPixmap(fn->icon().pixmap(20, 20));
        ui_->functionNameLabel->setText("<b>" + fn->name() + "()</b>");

        if (IconClassHelp* ic = fn->iconClass()) {
            ui_->descriptionLabel->show();
            ui_->descriptionLabel->setText(QString::fromStdString(ic->defaultName()));
        }
        else {
            ui_->descriptionLabel->clear();
            ui_->descriptionLabel->hide();
        }
    }
    else {
        ui_->functionIconLabel->setPixmap(QPixmap());
        ui_->functionNameLabel->setText("");
        ui_->descriptionLabel->clear();
        ui_->descriptionLabel->hide();
    }
}

bool VerbFunctionHelpWidget::hasData() const
{
    return (language_ && language_->hasVerbFunctionHelp() && !language_->functionHelp()->isEmpty());
}

void VerbFunctionHelpWidget::slotExtendSelection(int st)
{
    extendSelection_ = (st == Qt::Checked);
    updateSelectionLabel();
    updateButtonStatus();
}

void VerbFunctionHelpWidget::slotInsert()
{
    QString s = selection();
    /*if (extendSelection_) {
        s += QChar(QChar::LineSeparator);
        s += QChar(QChar::Tabulation);
     }*/
    emit insertRequested(s);
}

void VerbFunctionHelpWidget::slotCopy()
{
    QString s = selection();
    if (extendSelection_) {
        s += QChar(QChar::LineSeparator);
        s += QChar(QChar::Tabulation);
    }

    if (!s.isEmpty()) {
        MvQ::toClipboard(s);
    }
}

void VerbFunctionHelpWidget::updateButtonStatus()
{
    bool st = !(ui_->selectionLabel->text().isEmpty());
    ui_->insertPb->setEnabled(st);
    ui_->copyPb->setEnabled(st);
}

void VerbFunctionHelpWidget::slotWebDoc()
{
    if (VerbFunctionHelpFunction* fn = paramModel_->helpData()) {
        QString web = language_->functionHelp()->url(fn);
        std::string err;
        if (!metview::openInBrowser(web.toStdString(), err)) {
            QMessageBox::warning(this, "Code editor - Metview",
                                 "Failed to show documentation in web browser!<br><br>" + QString::fromStdString(err));
        }
    }
}

void VerbFunctionHelpWidget::slotFontSizeDown()
{
    MvQ::changeFontSize(ui_->paramTree, -1);

    int fs = ui_->paramTree->font().pointSize();
    MvQ::setFontSize(ui_->selectionLabel, fs);
    foreach (ParamValueEditor* e, editors_.values()) {
        if (e)
            MvQ::setFontSize(e->realWidget(), fs);
    }
}

void VerbFunctionHelpWidget::slotFontSizeUp()
{
    MvQ::changeFontSize(ui_->paramTree, 1);

    int fs = ui_->paramTree->font().pointSize();
    MvQ::setFontSize(ui_->selectionLabel, fs);
    foreach (ParamValueEditor* e, editors_.values()) {
        if (e)
            MvQ::setFontSize(e->realWidget(), fs);
    }
}

int VerbFunctionHelpWidget::currentTreeFontSize() const
{
    return ui_->paramTree->font().pointSize();
}

void VerbFunctionHelpWidget::writeSettings(QSettings& settings)
{
    settings.beginGroup("VerbFunctionHelpWidget");
    MvQ::saveTreeColumnWidth(settings, "paramTreeWidth", ui_->paramTree);
    settings.setValue("fontSize", ui_->paramTree->font().pointSize());
    settings.endGroup();
}

void VerbFunctionHelpWidget::readSettings(QSettings& settings)
{
    settings.beginGroup("VerbFunctionHelpWidget");
    MvQ::initTreeColumnWidth(settings, "paramTreeWidth", ui_->paramTree);

    int fs = settings.value("fontSize").toInt();
    MvQ::setFontSize(ui_->paramTree, fs);

    fs = ui_->paramTree->font().pointSize();
    MvQ::setFontSize(ui_->selectionLabel, fs);
    foreach (ParamValueEditor* e, editors_.values()) {
        if (e)
            MvQ::setFontSize(e->realWidget(), fs);
    }
    settings.endGroup();
}
