/***************************** 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 <QAction>
#include <QApplication>
#include <QDebug>
#include <QFontMetrics>
#include <QHeaderView>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
#include <QSortFilterProxyModel>
#include <QToolButton>
#include <QTreeView>
#include <QVBoxLayout>

#include <cstdlib>

#include "MvOdb.h"


#include "MvQOdbDataWidget.h"
#include "MvQOdbModel.h"


MvQOdbDataWidget::MvQOdbDataWidget(QWidget* parent) :
    QWidget(parent)
{
    data_ = 0;

    maxChunkSizeInMb_ = 60;
    chunkSize_ = 0;
    chunkNum_ = 0;
    currentChunk_ = -1;
    dataInitialised_ = false;

    auto* dataLayout = new QVBoxLayout;
    dataLayout->setContentsMargins(0, 0, 0, 0);
    setLayout(dataLayout);

    // Data tree
    dataModel_ = new MvQOdbDataModel();

    dataSortModel_ = new QSortFilterProxyModel;
    dataSortModel_->setSourceModel(dataModel_);
    dataSortModel_->setDynamicSortFilter(true);

    dataTree_ = new QTreeView;
    dataTree_->setAlternatingRowColors(true);
    dataTree_->setAllColumnsShowFocus(true);
    dataTree_->setModel(dataSortModel_);
    dataTree_->setUniformRowHeights(true);
    dataTree_->setRootIsDecorated(false);
    dataTree_->setSortingEnabled(false);  //!

    // Set header ContextMenuPolicy
    dataTree_->header()->setContextMenuPolicy(Qt::CustomContextMenu);

    connect(dataTree_->header(), SIGNAL(customContextMenuRequested(const QPoint&)),
            this, SLOT(slotHeaderContextMenu(const QPoint&)));

    connect(dataTree_, SIGNAL(clicked(const QModelIndex&)),
            this, SLOT(slotRowSelected(const QModelIndex&)));


    dataLayout->addWidget(dataTree_);

    // Control at the bottom
    auto* hb = new QHBoxLayout;
    dataLayout->addLayout(hb);

    hb->addSpacing(5);

    rowNumLabel_ = new QLabel;
    hb->addWidget(rowNumLabel_);

    hb->addStretch(1);

    chunkSizeLabel_ = new QLabel;
    hb->addWidget(chunkSizeLabel_);
    hb->addSpacing(12);

    dataBlockLabel_ = new QLabel(tr("<b>Data block:</b>"));
    hb->addWidget(dataBlockLabel_);

    chunkPrevTb_ = new QToolButton(this);
    chunkPrevTb_->setToolTip(tr("Previous data block"));
    chunkPrevTb_->setIcon(QPixmap(QString::fromUtf8(":/examiner/triangle_left.svg")));
    chunkPrevTb_->setAutoRaise(true);
    hb->addWidget(chunkPrevTb_);

    chunkEdit_ = new QLineEdit(this);
    chunkEdit_->setToolTip(tr("Current data block"));
    QSizePolicy sp = chunkEdit_->sizePolicy();
    sp.setHorizontalPolicy(QSizePolicy::Fixed);
    chunkEdit_->setSizePolicy(sp);
    QSize size = chunkEdit_->fontMetrics().size(Qt::TextSingleLine, "333");
    chunkEdit_->setFixedWidth(size.width() + 10);
    chunkEditValidator_ = new QIntValidator(this);

    connect(chunkEdit_, SIGNAL(editingFinished()),
            this, SLOT(slotChunkEdited()));

    chunkNumLabel_ = new QLabel(this);

    hb->addWidget(chunkEdit_);
    hb->addWidget(chunkNumLabel_);

    chunkNextTb_ = new QToolButton(this);
    chunkNextTb_->setToolTip(tr("Next data block"));
    chunkNextTb_->setIcon(QPixmap(QString::fromUtf8(":/examiner/triangle_right.svg")));
    chunkNextTb_->setAutoRaise(true);
    hb->addWidget(chunkNextTb_);

    hb->addSpacing(5);

    connect(chunkPrevTb_, SIGNAL(clicked()),
            this, SLOT(slotToPrevChunk()));

    connect(chunkNextTb_, SIGNAL(clicked()),
            this, SLOT(slotToNextChunk()));

    chunkPrevTb_->setEnabled(false);
    chunkNextTb_->setEnabled(false);
}

MvQOdbDataWidget::~MvQOdbDataWidget()
{
    if (data_)
        delete data_;
}

// Should be called only once
bool MvQOdbDataWidget::init(QString path)
{
    std::vector<int> v;
    return init(path, v);
}

bool MvQOdbDataWidget::init(QString path, const std::vector<int>& filter)
{
    if (dataInitialised_) {
        if (data_->path() == path.toStdString()) {
            dataModel_->setFilter(filter);
            setTotalNumDisplay();
            return true;
        }
        else {
            delete data_;
            data_ = nullptr;
        }
    }

    dataInitialised_ = true;

    // Create a data object!!
    data_ = MvOdbFactory::make(path.toStdString());
    if (data_ && data_->version() != MvAbstractOdb::VersionNew) {
        delete data_;
        data_ = nullptr;
        return false;
    }

    if (!data_) {
        // metaTab_->setTabEnabled(4,false);
        return false;
    }
    else {
        dataModel_->setFilter(filter);
        loadData();
        return true;
    }

    return true;
}

void MvQOdbDataWidget::loadData()
{
    if (!data_)
        return;

    loadStarted();
    // statusMessageLabel_->setText(tr("Counting number of rows in ODB data ..."));

    int rowNum = data_->rowNum();
    int totSizeInMb = -1;
    if (maxChunkSizeInMb_ > 0) {
        totSizeInMb = data_->totalSizeInMb();
    }

    // statusMessageLabel_->clear();
    chunkSize_ = 0;
    chunkNum_ = 0;

    if (totSizeInMb > maxChunkSizeInMb_ && maxChunkSizeInMb_ > 0) {
        int r = static_cast<float>(rowNum) * (static_cast<float>(maxChunkSizeInMb_) / static_cast<float>(totSizeInMb));
        int chunk = 0;

        if (r < 1000)
            chunk = 500;
        else if (r < 5000)
            chunk = 1000;
        else if (r < 10000)
            chunk = 5000;
        else if (r < 25000)
            chunk = 10000;
        else if (r < 50000)
            chunk = 25000;
        else if (r < 100000)
            chunk = 50000;
        else if (r < 300000)
            chunk = 100000;
        else
            chunk = 200000;

        chunkSize_ = chunk;
        chunkNum_ = rowNum / chunkSize_ + ((rowNum % chunkSize_ == 0) ? 0 : 1);

        // Disable sorting in tree
        dataTree_->setSortingEnabled(false);

        dataModel_->dataIsAboutToChange();
        initCurrentChunk();  // set currentChunk_ to 0
        data_->setChunkSize(chunkSize_);
        data_->setCurrentChunk(currentChunk_);
        dataModel_->setBaseData(data_);

        chunkEditValidator_->setRange(1, chunkNum_);
        chunkEdit_->setValidator(chunkEditValidator_);
        chunkNumLabel_->setText("/ " + QString::number(chunkNum_));
        chunkSizeLabel_->setText(tr("<b>Data block size:</b> ") + QString::number(chunkSize_) + tr(" rows"));

        chunkSizeLabel_->show();
        dataBlockLabel_->show();
        chunkEdit_->show();
        chunkNextTb_->show();
        chunkPrevTb_->show();
        chunkNumLabel_->show();
    }
    else {
        chunkSizeLabel_->hide();
        dataBlockLabel_->hide();
        chunkEdit_->hide();
        chunkNextTb_->hide();
        chunkPrevTb_->hide();
        chunkNumLabel_->hide();

        dataModel_->dataIsAboutToChange();
        data_->setChunkSize(0);
        dataModel_->setBaseData(data_);

        dataTree_->setSortingEnabled(true);
        dataTree_->sortByColumn(0, Qt::AscendingOrder);
    }


    for (int i = 0; i < dataModel_->columnCount() - 1; i++) {
        dataTree_->resizeColumnToContents(i);
    }

    setTotalNumDisplay();

    loadFinished();
}


void MvQOdbDataWidget::slotToPrevChunk()
{
    setCurrentChunk(currentChunk_ - 1);
}

void MvQOdbDataWidget::slotToNextChunk()
{
    setCurrentChunk(currentChunk_ + 1);
}

void MvQOdbDataWidget::slotChunkEdited()
{
    setCurrentChunk(chunkEdit_->text().toInt() - 1);
}

void MvQOdbDataWidget::setCurrentChunk(int chunk)
{
    if (chunkSize_ == 0)
        return;

    int oriVal = currentChunk_;
    if (chunk < 0)
        currentChunk_ = 0;
    else if (chunk >= chunkNum_)
        currentChunk_ = chunkNum_ - 1;
    else
        currentChunk_ = chunk;

    if (currentChunk_ <= 0) {
        chunkPrevTb_->setEnabled(false);
        chunkNextTb_->setEnabled(true);
    }
    else if (currentChunk_ >= chunkNum_ - 1) {
        chunkPrevTb_->setEnabled(true);
        chunkNextTb_->setEnabled(false);
    }
    else {
        chunkPrevTb_->setEnabled(true);
        chunkNextTb_->setEnabled(true);
    }

    if (oriVal != currentChunk_) {
        loadStarted();
        // statusMessageLabel_->setText(tr("Reading ODB data ..."));

        dataModel_->dataIsAboutToChange();
        data_->setCurrentChunk(currentChunk_);
        dataModel_->setBaseData(data_);

        if (chunkEdit_->text().toInt() != currentChunk_ + 1) {
            chunkEdit_->setText(QString::number(currentChunk_ + 1));
        }

        loadFinished();
        // statusMessageLabel_->setText(tr("Reading ODB data ..."));
        // statusMessageLabel_->clear();
    }
}

void MvQOdbDataWidget::initCurrentChunk()
{
    currentChunk_ = 0;

    chunkPrevTb_->setEnabled(false);
    chunkNextTb_->setEnabled(true);

    if (chunkEdit_->text().toInt() != currentChunk_ + 1) {
        chunkEdit_->setText(QString::number(currentChunk_ + 1));
    }
}


void MvQOdbDataWidget::setMaxChunkSizeInMb(int maxNum)
{
    if (maxNum < 0 || maxNum > 2000)
        return;

    maxChunkSizeInMb_ = maxNum;

    if (dataInitialised_) {
        loadData();
    }
}

void MvQOdbDataWidget::loadStarted()
{
    QApplication::setOverrideCursor(QCursor(Qt::BusyCursor));
}

void MvQOdbDataWidget::loadFinished()
{
    QApplication::restoreOverrideCursor();
}

void MvQOdbDataWidget::setFilter(const std::vector<int>& filter)
{
    dataModel_->setFilter(filter);
}

void MvQOdbDataWidget::setTmpFilter(const std::vector<int>& filter)
{
    dataModel_->setTmpFilter(filter);
}

void MvQOdbDataWidget::clearTmpFilter()
{
    dataModel_->clearTmpFilter();
}

void MvQOdbDataWidget::highlightRow(int row)
{
    QModelIndex index = dataSortModel_->mapFromSource(dataModel_->index(row, 0));
    dataTree_->setCurrentIndex(index);
    // dataTree_->scrollTo()
}

void MvQOdbDataWidget::slotRowSelected(const QModelIndex& index)
{
    QModelIndex dataIndex = dataSortModel_->mapToSource(index);
    if (dataIndex.isValid()) {
        if (dataModel_->hasTmpFilter() &&
            dataIndex.row() >= 0 && dataIndex.row() < static_cast<int>(dataModel_->tmpFilter().size())) {
            emit dataRowSelected(dataModel_->tmpFilter()[dataIndex.row()]);
            return;
        }

        emit dataRowSelected(dataIndex.row());
    }
}


void MvQOdbDataWidget::setTotalNumDisplay()
{
    int displayedRowNum = data_->rowNum();
    if (dataModel_->filtered()) {
        displayedRowNum = dataModel_->rowCount();
    }

    QString txt = tr("<b>Total number of rows:</b> ") + QString::number(displayedRowNum);

    if (dataModel_->hasTmpFilter()) {
        txt += " (of " + QString::number(dataModel_->filterCount()) + ")";
    }

    rowNumLabel_->setText(txt);
}

void MvQOdbDataWidget::slotHeaderContextMenu(const QPoint& position)
{
    int section = dataTree_->header()->logicalIndexAt(position);

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

    QMenu menu;
    QMenu subMenu(tr("Show other columns"));


    int numOfShown = 0;
    for (int i = 0; i < dataModel_->columnCount(); i++) {
        if (!dataTree_->isColumnHidden(i)) {
            numOfShown++;
        }
    }

    QAction* actionHide = nullptr;

    // Thic column can be hidden only if there are other visible columns as well
    if (!dataTree_->isColumnHidden(section) && numOfShown > 1) {
        actionHide = new QAction(this);
        actionHide->setText(tr("Hide ") + "\'" + dataModel_->headerData(section, Qt::Horizontal, Qt::DisplayRole).toString() + "\'");
        menu.addAction(actionHide);

        menu.addSeparator();
    }

    // Show columns
    QMap<QAction*, int> actionShow;
    for (int i = 0; i < dataModel_->columnCount(); i++) {
        if (dataTree_->isColumnHidden(i)) {
            // SubMenu if there are too amny items
            if (actionShow.count() == 6) {
                menu.addMenu(&subMenu);
            }

            auto* ac = new QAction(this);
            QString acText = dataModel_->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString();
            if (actionShow.count() >= 6) {
                ac->setText(acText);
                subMenu.addAction(ac);
            }
            else {
                ac->setText(tr("Show ") + "\'" + acText + "\'");
                menu.addAction(ac);
            }
            actionShow[ac] = i;
        }
    }

    menu.addSeparator();

    auto* actionShowAll = new QAction(this);
    actionShowAll->setText(tr("Show all columns"));
    menu.addAction(actionShowAll);

    auto* actionHideAll = new QAction(this);
    actionHideAll->setText(tr("Hide all other columns"));
    menu.addAction(actionHideAll);

    QAction* action = menu.exec(dataTree_->header()->mapToGlobal(position));

    bool actionSelected = false;

    if (action == actionHide) {
        dataTree_->hideColumn(section);
        actionSelected = true;
    }
    else if (action == actionShowAll) {
        for (int i = 0; i < dataModel_->columnCount(); i++) {
            dataTree_->showColumn(i);
        }
        actionSelected = true;
    }
    else if (action == actionHideAll) {
        for (int i = 0; i < dataModel_->columnCount(); i++) {
            if (!dataTree_->isColumnHidden(i) && i != section)
                dataTree_->hideColumn(i);
        }
        actionSelected = true;
    }
    else if (action) {
        QMap<QAction*, int>::iterator it = actionShow.find(action);
        if (it != actionShow.end()) {
            dataTree_->showColumn(it.value());
        }
        actionSelected = true;
    }

    if (actionSelected) {
        for (int i = 0; i < dataModel_->columnCount() - 1; i++) {
            dataTree_->resizeColumnToContents(i);
        }
    }

    subMenu.clear();
    menu.clear();
}

QStringList MvQOdbDataWidget::visibleColumns()
{
    QStringList lst;
    for (int i = 0; i < dataModel_->columnCount(); i++) {
        if (!dataTree_->isColumnHidden(i)) {
            lst << dataModel_->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString();
        }
    }

    return lst;
}

void MvQOdbDataWidget::setVisibleColumns(QStringList lst)
{
    for (int i = 0; i < dataModel_->columnCount(); i++) {
        if (lst.indexOf(dataModel_->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString()) != -1) {
            dataTree_->showColumn(i);
        }
        else {
            dataTree_->hideColumn(i);
        }
    }
}
