/***************************** 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 <QApplication>
//#include <QDebug>
#include <QIcon>
#include <QItemSelectionModel>
#include <QListView>
#include <QPainter>
#include <QStyle>
#include <QVBoxLayout>

#include "MgQLayoutItem.h"
#include "MgQPlotScene.h"
#include "MgQSceneItem.h"

#include "MvQZoomStackWidget.h"
#include "MvQTheme.h"

#include "MagPlusService.h"


const QSize MvQZoomStackLevel::pixSize_{96, 48};

//=========================
//   MvQZoomStackLevel
//=========================

void MvQZoomStackLevel::setImage(QImage& img)
{
    float w = img.width();
    float h = img.height();

    // We copy the image into a pixmap with the maximum size of 92x46
    float hMax = pixSize_.height() - 6;
    float wMax = 2 * hMax;
    QPixmap pix;
    if (h * wMax / w <= hMax) {
        pix = QPixmap::fromImage(img).scaledToWidth(wMax, Qt::SmoothTransformation);
    }
    else {
        pix = QPixmap::fromImage(img).scaledToHeight(hMax, Qt::SmoothTransformation);
    }

    // Define the pixmap rectangle
    int pw = pix.width();
    int ph = pix.height();
    int pdy = (pixSize_.height() - ph) / 2;
    // int pdx = (pixSize_.width()  - pw) / 2;
    int pdx = 3;
    QRect pixRect(pdx, pdy, pw, ph);

    // We create a 60x60 pixmap with white background
    pixmap_ = QPixmap(pixSize());
    pixmap_.fill(MvQTheme::colour("uplot", "zoomstack_bg"));

    // Paint the image pixmap into the middle
    QPainter painter(&pixmap_);
    painter.drawPixmap(pixRect, pix);
}

//=========================
//   MvQZoomStackData
//=========================


MvQZoomStackData::MvQZoomStackData(QString grLayoutId) :
    grLayoutId_(grLayoutId)
{
    // Initially we do not have levels
    levelNum_ = 0;
    actLevel_ = -1;
    selectedLevel_ = -1;

    // levelPreviewWidth_=180;
    levelPreviewWidth_ = 100;
}


MvQZoomStackData::~MvQZoomStackData()
{
    clear();
}

void MvQZoomStackData::clear()
{
    foreach (MvQZoomStackLevel* level, levels_) {
        delete level;
    }
    levels_.clear();

    levelNum_ = 0;
    actLevel_ = -1;
    selectedLevel_ = -1;
}


int MvQZoomStackData::update(int levelNum, int actLevel, MgQLayoutItem* item)
{
    // Get the new number of levels
    int levelNumOri = levelNum_;
    int actLevelOri = actLevel_;

    // int selectedLevel=-1;

    levelNum_ = levelNum;
    actLevel_ = actLevel;

    if (levelNum_ > 30) {
        levelNum_ = 0;
        actLevel_ = 0;
    }

    // By default levelNum = 0 and actLevel = -1
    // and there are no zoom levels.

    if (levelNum_ <= 0) {
        clear();

        // The gui is not enabled yet
        // enabled_=false;

        // Clear the preview textures
        // clear();

        // Save the preview map into a texture
        if (levelNum_ == 0 && actLevel_ == -1) {
            if (levels_.count() == 0)  // && currentLayout_ != 0)
            {
                auto* level = new MvQZoomStackLevel;
                generatePreview(level, item);
                // level->tb()->setChecked(true);
                levels_.push_back(level);
                selectedLevel_ = 0;
            }
            else {
                generatePreview(levels_[0], item);
                selectedLevel_ = 0;
            }
        }
        return selectedLevel_;
    }
    else {
        // enabled_=true;
    }

    // If the levelNum or the current level has changed.
    if (levelNumOri != levelNum_ || actLevelOri != actLevel_) {
        // focusedLevel_=-1;

        // If there are less levels
        if (levelNumOri > levelNum_) {
            for (int i = levelNumOri - 1; i >= levelNum_; i--) {
                MvQZoomStackLevel* level = levels_.back();
                delete level;
                levels_.pop_back();
            }

            generatePreview(levels_.back(), item);
            selectedLevel_ = levels_.count() - 1;
        }

        // If there are more levels
        else if (levelNumOri < levelNum_) {
            auto* level = new MvQZoomStackLevel;
            generatePreview(level, item);
            levels_.push_back(level);
            selectedLevel_ = levels_.count() - 1;
        }

        // If the level num is the same. If the actLevel is the last one we
        // regenerate the preview
        else {
            selectedLevel_ = actLevel_;
            if (actLevel_ == levelNum_ - 1) {
                generatePreview(levels_[actLevel_], item);
            }
        }
    }

    return selectedLevel_;
}

void MvQZoomStackData::generatePreview(MvQZoomStackLevel* level, MgQLayoutItem* qln)
{
    if (qln == nullptr)
        return;

    // Get the preview layout's scene rectangle. We increase the width and
    // height a bit so that the rendered image could contain the whole frame
    QRectF sourceRect = qln->sceneBoundingRect();
    sourceRect.adjust(0., 0., 5., 5.);

    // Define the size of the device where the preview plot will be rendered
    float tw = levelPreviewWidth_;

    float w = sourceRect.width();
    float h = sourceRect.height();
    float r = tw / w;
    float th = h * r;

    if (th > levelPreviewWidth_) {
        th = levelPreviewWidth_;
        r = th / h;
        tw = w * r;
    }

    // Create the device. We double the needed size for later smooth recaling!
    QImage device = QImage(tw * 2, th * 2, QImage::Format_RGB888);

    // Set the target rect
    QRectF targetRect(0, 0, device.width(), device.height());

    // Create the painter
    auto* painter = new QPainter(&device);

    // Fill the target device with with colour
    painter->fillRect(0, 0, device.width(), device.height(),
                      QBrush(Qt::white));

    // Render the item into the device
    qln->scene()->render(painter, targetRect, sourceRect);

    // Rescale the device to the needed side with smooth transformation. Then mirror it horizontally.
    QImage img = device.scaledToWidth(tw, Qt::SmoothTransformation).mirrored(false, true);

    // Now we can set the preview!
    level->setImage(img);
}

bool MvQZoomStackData::setActLevel(int level)
{
    if (level >= 0 && level < levelNum_ && level != actLevel_) {
        actLevel_ = level;
        selectedLevel_ = actLevel_;
        return true;
    }
    return false;
}

void MvQZoomStackData::stepUp()
{
    slotStepTo(actLevel_ - 1);
}

void MvQZoomStackData::stepDown()
{
    slotStepTo(actLevel_ + 1);
}

void MvQZoomStackData::slotStepTo(int level)
{
    if (setActLevel(level)) {
        emit actLevelChanged(grLayoutId_, actLevel_);
    }
}


//==================================================================
//
// MvQZoomStackModel
//
//==================================================================

MvQZoomStackModel::MvQZoomStackModel(QObject* parent) :
    QAbstractItemModel(parent)
{
}

void MvQZoomStackModel::reload(MvQZoomStackData* data)
{
    beginResetModel();
    data_ = data;
    endResetModel();
}
void MvQZoomStackModel::clearData()
{
    beginResetModel();
    data_ = nullptr;
    endResetModel();
}

int MvQZoomStackModel::columnCount(const QModelIndex& parent) const
{
    return parent.isValid() ? 0 : 1;
}

int MvQZoomStackModel::rowCount(const QModelIndex& index) const
{
    if (!index.isValid() && data_)
        return data_->levels().count();
    else
        return 0;
}

QVariant MvQZoomStackModel::data(const QModelIndex& index, int role) const
{
    if (!data_ || index.row() < 0 || index.row() >= data_->levels().count())
        return {};

    return role == Qt::DecorationRole ? data_->levels()[index.row()]->pixmap() : QVariant();
}

QModelIndex MvQZoomStackModel::index(int row, int column, const QModelIndex& /*parent */) const
{
    return createIndex(row, column);
}

QModelIndex MvQZoomStackModel::parent(const QModelIndex& /*index*/) const
{
    return {};
}

//=========================
//   MvQZoomStackWidget
//=========================

MvQZoomStackWidget::MvQZoomStackWidget(MgQPlotScene* scene, QWidget* parent) :
    QWidget(parent)
{
    setObjectName(QString::fromUtf8("zoomStackWidget"));
    plotScene_ = scene;

    auto vb = new QVBoxLayout(this);
    vb->setContentsMargins(margin_, margin_, margin_, margin_);
    model_ = new MvQZoomStackModel(this);

    view_ = new QListView(this);
    view_->setSpacing(2);
    view_->setSelectionMode(QAbstractItemView::NoSelection);
    view_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    // view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    view_->setModel(model_);
    vb->addWidget(view_, 1);

    setFixedWidth(MvQZoomStackLevel::pixSize().width() + 2 * margin_ + 10);

    connect(view_, SIGNAL(clicked(QModelIndex)),
            this, SLOT(itemSelected(QModelIndex)));
}

MvQZoomStackWidget::~MvQZoomStackWidget()
{
    QMapIterator<QString, MvQZoomStackData*> it(page_);
    while (it.hasNext()) {
        it.next();
        delete it.value();
    }
    page_.clear();
}

void MvQZoomStackWidget::reset(MgQSceneItem* activeScene, bool changeActiveScene)
{
    if (skipUpdate_) {
        return;
    }

    activeScene_ = activeScene;
    model_->clearData();

    if (!changeActiveScene) {
        QStringList idLst;
        foreach (MgQSceneItem* item, plotScene_->sceneItems()) {
            MgQLayoutItem* zoomLayout = item->firstProjectorItem();
            if (zoomLayout) {
                idLst << QString::fromStdString(zoomLayout->layout().id());
            }
        }

        // Remove unused scenes from the zoomstack
        foreach (QString id, page_.keys()) {
            if (idLst.indexOf(id) == -1) {
                delete page_[id];
                page_.remove(id);
            }
        }
    }

    if (activeScene_) {
        if (MgQLayoutItem* zoomLayout = activeScene_->firstProjectorItem()) {
            int levelNum = zoomLayout->layout().zoomLevels();
            int actLevel = zoomLayout->layout().zoomCurrentLevel();
            QString id = QString::fromStdString(zoomLayout->layout().id());

            if (changeActiveScene) {
                if (!page_.contains(id)) {
                    page_[id] = new MvQZoomStackData(id);

                    connect(page_[id], SIGNAL(actLevelChanged(QString, int)),
                            this, SIGNAL(actLevelChanged(QString, int)));

                    managePreviewLayout(page_[id], levelNum, actLevel);
                }
            }
            else {
                if (!page_.contains(id)) {
                    page_[id] = new MvQZoomStackData(id);
                    connect(page_[id], SIGNAL(actLevelChanged(QString, int)),
                            this, SIGNAL(actLevelChanged(QString, int)));
                }

                managePreviewLayout(page_[id], levelNum, actLevel);
            }

            Q_ASSERT(page_[id]);
            model_->reload(page_[id]);

            if (currentPage_ != page_[id]) {
                currentPage_ = page_[id];
            }
        }
    }

    adjustViewSize();
}

void MvQZoomStackWidget::managePreviewLayout(MvQZoomStackData* page, int levelNum, int actLevel)
{
    if (page) {
        // Find preview layout in the scene and remove it
        MgQPreviewLayoutItem* previewLayout = activeScene_->previewLayoutItem();
        if (previewLayout) {
            plotScene_->removeItem(previewLayout);

            // Create a new scene and add the previewLayout to it!
            auto* previewScene = new QGraphicsScene;
            previewScene->addItem(previewLayout);
            previewLayout->setVisible(true);
            page->update(levelNum, actLevel, previewLayout);
            delete previewScene;
        }
    }
}

void MvQZoomStackWidget::adjustViewSize()
{
    auto px = MvQZoomStackLevel::pixSize();
    int row = model_->rowCount();
    int maxRow = 5;
    int viewH = px.height() * row + 2 * row * view_->spacing();
    int maxViewH = px.height() * maxRow + 2 * maxRow * view_->spacing();

    if (viewH > maxViewH) {
        viewH = maxViewH;
    }

    setFixedHeight(viewH + 2 * margin_ + 10);
}

MvQZoomStackData* MvQZoomStackWidget::pageData(MgQSceneItem* item)
{
    if (!item)
        return nullptr;

    MgQLayoutItem* zoomLayout = item->firstProjectorItem();
    if (zoomLayout) {
        QString id = QString::fromStdString(zoomLayout->layout().id());
        if (page_.find(id) != page_.end()) {
            return page_[id];
        }
    }

    return nullptr;
}

void MvQZoomStackWidget::slotStepUp()
{
    if (!currentPage_)
        return;

    if (currentLevel() >= 1) {
        int level = currentLevel() - 1;
        skipUpdate_ = true;
        slotStepTo(level);
        skipUpdate_ = false;
    }
}

void MvQZoomStackWidget::slotStepDown()
{
    if (!currentPage_)
        return;

    skipUpdate_ = true;
    if (currentLevel() >= 0 && currentLevel() < levelNum() - 1) {
        int level = currentLevel() + 1;
        skipUpdate_ = true;
        slotStepTo(level);
        skipUpdate_ = false;
    }
}

void MvQZoomStackWidget::slotStepTo(int level)
{
    if (!currentPage_)
        return;

    if (level != -1) {
        skipUpdate_ = true;
        currentPage_->slotStepTo(level);
        skipUpdate_ = false;
    }
}

int MvQZoomStackWidget::levelNum()
{
    if (!currentPage_)
        return 0;

    return currentPage_->levels().count();
}

int MvQZoomStackWidget::currentLevel()
{
    if (!currentPage_)
        return 0;

    return currentPage_->selectedLevel();
}

void MvQZoomStackWidget::itemSelected(const QModelIndex& current)
{
    if (current.isValid()) {
        slotStepTo(current.row());
    }
    if (parentWidget()) {
        parentWidget()->close();
    }
}
