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

 Copyright 2012 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 <QtGlobal>
#include <QAction>
#include <QApplication>
#include <QDebug>
#include <QHeaderView>
#include <QHBoxLayout>
#include <QInputDialog>
#include <QLabel>
#include <QMenu>
#include <QPushButton>
#include <QSettings>
#include <QToolButton>
#include <QTableView>
#include <QVBoxLayout>

#include "MvScm.h"
#include "MvQScmDataWidget.h"
#include "MvQScmModel.h"

#include <algorithm>

//===============================================================
//
// MvQScmDataPanel
//
//===============================================================

MvQScmDataPanel::MvQScmDataPanel(QWidget* parent) :
    QWidget(parent),
    model_(nullptr),
    master_(false)
{
    auto* layout = new QVBoxLayout;
    layout->setContentsMargins(0, 0, 0, 0);
    setLayout(layout);

    filterModel_ = new MvQScmProfileFilterModel(this);
    filterModel_->setDynamicSortFilter(true);

    view_ = new QTableView(this);
    view_->setObjectName("paramMlView");
    // view_->setProperty("mvStyle", 3);
    view_->setAlternatingRowColors(true);
    view_->setModel(filterModel_);
    layout->addWidget(view_);

    auto* mlDelegate = new MvQScmTableViewDelegate(this);
    view_->setItemDelegate(mlDelegate);

    connect(view_, SIGNAL(clicked(const QModelIndex&)),
            this, SLOT(slotParamSelected(const QModelIndex&)));

    connect(view_, SIGNAL(pressed(const QModelIndex&)),
            this, SLOT(slotParamSelected(const QModelIndex&)));
}


void MvQScmDataPanel::setMasterParam(MvScmVar* var, MvScmVar* var1)
{
    masterParams_[var] = var1;
}

void MvQScmDataPanel::setStep(int step)
{
    prevIndex_ = filterModel_->mapToSource(view_->currentIndex());
    model_->setStep(step);
}

void MvQScmDataPanel::reselectIndex(bool select)
{
    QModelIndex actIndex = model_->index(prevIndex_.row(), prevIndex_.column());
    view_->setCurrentIndex(filterModel_->mapFromSource(actIndex));
    if (select) {
        if (masterParams_.size() == 0) {
            slotParamSelected(actIndex);
        }
        else {
            if (actIndex.isValid()) {
                auto it = masterParams_.find(vars_.at(actIndex.column()));
                if (it != masterParams_.end()) {
                    slotParamSelected(actIndex);
                }
                else {
                    emit selectParamFromMasterPanel();
                }
            }
            else
                emit selectParamFromMasterPanel();
        }
    }
}

void MvQScmDataPanel::slotParamSelected(int index)
{
    if (index >= 0 && index < static_cast<int>(vars_.size())) {
        MvScmVar* var = vars_.at(index);
        int step = model_->step();

        auto it = masterParams_.find(var);
        if (it != masterParams_.end()) {
            emit paramSelected(it->second, step);
        }
        else if (masterParams_.size() == 0) {
            emit paramSelected(var, step);
        }
    }
}

void MvQScmDataPanel::slotParamSelected(const QModelIndex& index)
{
    if (!index.isValid()) {
        emit stepChanged(model_->step());
    }
    else {
        slotParamSelected(index.column());
    }
}

bool MvQScmDataPanel::selectParam(MvScmVar* var)
{
    if (!var)
        return false;

    int row = 0;
    QModelIndex current = view_->currentIndex();
    if (current.isValid())
        row = current.row();

    QModelIndex index = filterModel_->mapFromSource(model_->indexForVar(var, model_->step(), row));
    if (index.isValid()) {
        view_->setCurrentIndex(index);
        view_->scrollTo(index, QAbstractItemView::PositionAtCenter);
        return true;
    }

    return false;
}

void MvQScmDataPanel::showEditableOnly(bool b)
{
    filterModel_->setShowEditableOnly(b);
}

bool MvQScmDataPanel::update(const MvScmProfileChange& item)
{
    QModelIndex index = filterModel_->mapFromSource(model_->indexForChange(item));
    if (index.isValid()) {
        view_->update(index);
        view_->scrollTo(index, QAbstractItemView::PositionAtCenter);

        if (item.dependantVar()) {
            index = model_->indexForVar(item.dependantVar(), item.step(), item.level());
            view_->update(filterModel_->mapFromSource(index));
        }
        return true;
    }

    return false;
}

void MvQScmDataPanel::reload()
{
    model_->reload();
}

//===============================================================
//
// MvQScmProfileDataPanel
//
//===============================================================

MvQScmProfileDataPanel::MvQScmProfileDataPanel(QWidget* parent) :
    MvQScmDataPanel(parent),
    header_(nullptr)
{
    // Column tree
    model_ = new MvQScmProfileModel();
    filterModel_->setSourceModel(model_);

    connect(model_, SIGNAL(dataEdited(const MvScmProfileChange&)),
            this, SIGNAL(dataEdited(const MvScmProfileChange&)));

    header_ = view_->horizontalHeader();
    header_->setContextMenuPolicy(Qt::CustomContextMenu);

    connect(header_, SIGNAL(sectionClicked(int)),
            this, SLOT(slotParamSelected(int)));

    connect(header_, SIGNAL(customContextMenuRequested(const QPoint&)),
            this, SLOT(slotHeaderContextMenu(const QPoint&)));

    // Dymanic filter did not work somehow, so we need to update the filter model in this way
    filterModel_->setShowEditableOnly(filterModel_->showEditableOnly());
}

void MvQScmProfileDataPanel::init(const std::vector<MvScmVar*>& vars, int step, const std::vector<float>& levels, const std::string& levelName,
                                  const std::vector<MvScmVar*>& editableVars)
{
    vars_ = vars;
    editableVars_ = editableVars;
    model_->setData(vars, step, levels, levelName);
    model_->setEditableVars(editableVars);
}


void MvQScmProfileDataPanel::slotHeaderContextMenu(const QPoint& position)
{
    int section = header_->logicalIndexAt(position);

    if (section < 0 || section >= header_->count())
        return;

    bool editable = (std::find(editableVars_.begin(), editableVars_.end(), vars_.at(section)) != editableVars_.end()) ? true : false;

    if (!editable)
        return;

    QList<QAction*> actions;
    QAction* action = nullptr;

    action = new QAction(this);
    action->setObjectName(QString::fromUtf8("actionAssign"));
    action->setText(tr("Set values"));
    action->setData("assign");
    actions << action;


    action = new QAction(this);
    action->setObjectName(QString::fromUtf8("actionAssign"));
    action->setText(tr("Set values to 0"));
    action->setData("assignToZero");
    actions << action;

    if (actions.count() > 0 &&
        (action = QMenu::exec(actions, header_->mapToGlobal(position))) != nullptr) {
        QString key = action->data().toString();
        MvScmVar* var = vars_.at(section);
        if (key == "assign") {
            assignValue(var);
        }

        if (key == "assignToZero") {
            assignValue(var, 0.);
        }
    }

    foreach (QAction* action, actions) {
        delete action;
    }
}

void MvQScmProfileDataPanel::assignValue(MvScmVar* var)
{
    bool ok;
    double val = QInputDialog::getDouble(nullptr,
                                         tr("Assign value"),
                                         tr("New value:"),
                                         0., -10000000, 10000000, 10, &ok);

    if (ok) {
        assignValue(var, val);
    }
}

void MvQScmProfileDataPanel::assignValue(MvScmVar* var, double val)
{
    std::vector<MvScmProfileChange> ch;
    var->setValues(model_->step(), val, ch);
    for (auto& i : ch)
        emit dataEdited(i);
}


//===============================================================
//
// MvQScmProfileDataPanel
//
//===============================================================


MvQScmSurfaceDataPanel::MvQScmSurfaceDataPanel(QWidget* parent) :
    MvQScmDataPanel(parent)
{
    // Column tree
    model_ = new MvQScmSurfaceModel();
    filterModel_->setSourceModel(model_);

    connect(model_, SIGNAL(dataEdited(const MvScmProfileChange&)),
            this, SIGNAL(dataEdited(const MvScmProfileChange&)));

    QHeaderView* header = view_->verticalHeader();

    connect(header, SIGNAL(sectionClicked(int)),
            this, SLOT(slotParamSelected(int)));

    // Dymanic filter did not work somehow, so we need to update the filter model in this way
    filterModel_->setShowEditableOnly(filterModel_->showEditableOnly());
}

void MvQScmSurfaceDataPanel::init(const std::vector<MvScmVar*>& vars, int step,
                                  const std::vector<MvScmVar*>& editableVars)
{
    vars_ = vars;
    model_->setData(vars, step);
    model_->setEditableVars(editableVars);
}

//===============================================================
//
// MvQScmDataWidget
//
//===============================================================

MvQScmTab::MvQScmTab(QWidget* parent) :
    QTabWidget(parent)
{
}

MvQScmDataWidget::MvQScmDataWidget(QWidget* parent) :
    QWidget(parent),
    data_(nullptr),
    masterPanel_(nullptr)
{
    //-----------------------
    // The main layout
    //-----------------------

    auto* mainLayout = new QVBoxLayout;
    mainLayout->setObjectName(QString::fromUtf8("vboxLayout"));
    mainLayout->setContentsMargins(0, 0, 0, 1);
    mainLayout->setSpacing(1);
    setLayout(mainLayout);

    //-------------------
    // Central tab
    //-------------------

    tab_ = new MvQScmTab(this);

    mainLayout->addWidget(tab_, 1);

    connect(tab_, SIGNAL(currentChanged(int)),
            this, SLOT(slotTabIndexChanged(int)));
}

MvQScmDataWidget::~MvQScmDataWidget()
{
    writeSettings();
}

void MvQScmDataWidget::clearPanels()
{
    tab_->clear();
    for (auto& panel : panels_) {
        delete panel.second;
    }

    masterPanel_ = nullptr;
    tabToPanel_.clear();
    panelToTab_.clear();
}

void MvQScmDataWidget::addPanelToTab(MvQScmDataPanel* panel, PanelType type, QString title)
{
    tab_->addTab(panel, title);
    int tabIndex = tab_->count() - 1;
    panelToTab_[type] = tabIndex;
    tabToPanel_[tabIndex] = type;
}

void MvQScmDataWidget::init(MvScm* data, QList<MvScmVar*> editVars)
{
    // Delete all the panels
    clearPanels();

    if (data->stepNum() <= 0) {
        data_ = 0;
        return;
    }

    data_ = data;

    // Get editable variables
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
    auto qv = editVars.toVector();
    std::vector<MvScmVar*> editVec(qv.begin(), qv.end());
#else
    std::vector<MvScmVar*> editVec = editVars.toVector().toStdVector();
#endif

    int step = 0;
    MvQScmDataPanel* panel = nullptr;

    //-------------------------
    // Model levels
    //-------------------------

    // At the moment masterPanel_ has to be the model level panel !!!

    if (!data->modelLevelDim().isEmpty()) {
        panel = new MvQScmProfileDataPanel(this);
        panels_[ModelLevPanel] = panel;

        // This will be the master panel
        masterPanel_ = panel;

        // Add to tab
        addPanelToTab(panel, ModelLevPanel, tr("Model levels"));

        // init
        panel->init(data_->modelLevel(), step, data_->modelLevelDim().values(), data_->modelLevelDim().longName(), editVec);
    }

    //-------------------------
    // Pressure levels
    //-------------------------

    // if(!data->pressureLevelDim().isEmpty())
    if (false) {
        panel = new MvQScmProfileDataPanel(this);
        panels_[PressureLevPanel] = panel;

        // Add to tab
        addPanelToTab(panel, PressureLevPanel, tr("Pressure levels"));

        // init
        panel->init(data_->pressureLevel(), step, data_->pressureLevelDim().values(), data_->pressureLevelDim().longName(), editVec);
    }

    //-------------------------
    // Soil levels
    //-------------------------

    if (!data->soilLevelDim().isEmpty()) {
        panel = new MvQScmProfileDataPanel(this);
        panels_[SoilLevPanel] = panel;

        // Add to tab
        addPanelToTab(panel, SoilLevPanel, tr("Soil levels"));

        panel->init(data_->soilLevel(), step, data_->soilLevelDim().values(), data_->soilLevelDim().longName(), editVec);

        if (masterPanel_) {
            panel->setMasterParam(data_->soilVar(MvScm::TempSoil), data_->mlVar(MvScm::TempML));

            connect(panel, SIGNAL(selectMasterPanel()),
                    masterPanel_, SLOT(slotSelectCurrent()));
        }
    }

    //-------------------------
    // Surface - always exists
    //------------------------

    panel = new MvQScmSurfaceDataPanel(this);
    panels_[SurfaceLevPanel] = panel;

    // Add to tab
    addPanelToTab(panel, SurfaceLevPanel, tr("Surface and other 2D"));

    panel->init(data_->surfaceLevel(), step, editVec);

    if (masterPanel_) {
        panel->setMasterParam(data_->surfVar(MvScm::TempSurf), data_->mlVar(MvScm::TempML));

        connect(panel, SIGNAL(selectMasterPanel()),
                masterPanel_, SLOT(slotSelectCurrent()));
    }

    //-----------------------------------
    // Relaying signals from the panels
    //-----------------------------------

    for (auto& panel : panels_) {
        connect(panel.second, SIGNAL(dataEdited(const MvScmProfileChange&)),
                this, SIGNAL(dataEdited(const MvScmProfileChange&)));

        connect(panel.second, SIGNAL(paramSelected(MvScmVar*, int)),
                this, SIGNAL(paramSelected(MvScmVar*, int)));

        connect(panel.second, SIGNAL(stepChanged(int)),
                this, SIGNAL(stepChanged(int)));
    }

    // Initial profile
    if (masterPanel_) {
        //!!!!!!!!!!!!!!!!!!
        masterPanel_->slotParamSelected(data_->mlVarIndex(MvScm::TempML));
    }
}

void MvQScmDataWidget::slotStepChanged(int value)
{
    int step = value;

    // Save and set steps
    for (auto& panel : panels_) {
        panel.second->setStep(step);
    }

    // Update profile view

    int actTab = tab_->currentIndex();

    // Model levels
    if (panels_.find(ModelLevPanel) != panels_.end()) {
        panels_[ModelLevPanel]->reselectIndex(tabToPanel_[actTab] == ModelLevPanel || tabToPanel_[actTab] == PressureLevPanel);
    }

    // Pressure
    if (panels_.find(PressureLevPanel) != panels_.end()) {
        panels_[PressureLevPanel]->reselectIndex(tabToPanel_[actTab] == PressureLevPanel);
    }

    // Soil
    if (panels_.find(SoilLevPanel) != panels_.end()) {
        panels_[SoilLevPanel]->reselectIndex(tabToPanel_[actTab] == SoilLevPanel);
    }

    // Surf
    panels_[SurfaceLevPanel]->reselectIndex(tabToPanel_[actTab] == SurfaceLevPanel);
}


void MvQScmDataWidget::slotTabIndexChanged(int /*index*/)
{
}

void MvQScmDataWidget::showEditableParams(bool b)
{
    for (auto& panel : panels_) {
        panel.second->showEditableOnly(b);
    }
}

void MvQScmDataWidget::selectMlParam(MvScmVar* var)
{
    if (panels_.find(ModelLevPanel) != panels_.end()) {
        if (panels_[ModelLevPanel]->selectParam(var)) {
            tab_->setCurrentIndex(panelToTab_[ModelLevPanel]);
        }
    }
}

void MvQScmDataWidget::update(const MvScmProfileChange& item)
{
    if (!item.var())
        return;

    for (auto& panel : panels_) {
        if (panel.second->update(item)) {
            tab_->setCurrentIndex(panelToTab_[panel.first]);
            break;
        }
    }
}

void MvQScmDataWidget::reload()
{
    for (auto& panel : panels_) {
        panel.second->reload();
    }
}

void MvQScmDataWidget::writeSettings()
{
}

void MvQScmDataWidget::readSettings()
{
}
