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

 Copyright 2013 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 <QDataStream>
#include <QDebug>
#include <QDrag>
#include <QHeaderView>
#include <QMenu>
#include <QMouseEvent>
#include <QPainter>
#include <QScrollBar>
#include <QShortcut>

#include "MvQDetailedFolderView.h"

#include "MvQActionList.h"
#include "MvQContextMenu.h"
#include "MvQDropTarget.h"
#include "MvQFolderViewDelegate.h"
#include "MvQFolderModel.h"
#include "MvQDesktopSettings.h"
#include "MvQIconMimeData.h"
#include "MvQIconProvider.h"

#include "Folder.h"
#include "IconClass.h"

static QList<MvQDetailedFolderView*> views;

MvQDetailedFolderView::MvQDetailedFolderView(MvQFolderModel* folderModel,
                                             MvQActionList* iconActions,
                                             MvQActionList* desktopActions, QWidget* parent) :
    QTreeView(parent),
    MvQFolderViewBase(folderModel, parent),
    moveActionEnabled_(true),
    defaultShortCut_(0),
    appIconActions_(iconActions),
    appDesktopActions_(desktopActions)
{
    views << this;

    setRootIsDecorated(false);
    setSortingEnabled(true);
    sortByColumn(0, Qt::AscendingOrder);
    // setAlternatingRowColors(true);
    setAllColumnsShowFocus(true);
    setUniformRowHeights(true);

    //Drag and drop
    setDragEnabled(true);
    setAcceptDrops(true);
    setDropIndicatorShown(true);
    setDragDropMode(QAbstractItemView::DragDrop);

    //Delegate
    delegate_ = new MvQDetailedViewDelegate(this);
    setItemDelegate(delegate_);

    connect(delegate_, SIGNAL(repaintIt(const QModelIndex&)),
            this, SLOT(update(const QModelIndex&)));

    setMouseTracking(true);

    setSelectionMode(QAbstractItemView::ExtendedSelection);

    //Context menu
    setContextMenuPolicy(Qt::CustomContextMenu);

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

    connect(this, SIGNAL(doubleClicked(const QModelIndex&)),
            this, SLOT(slotDoubleClickItem(const QModelIndex)));

    connect(this, SIGNAL(entered(const QModelIndex&)),
            this, SLOT(slotEntered(const QModelIndex&)));

    //Selection form the list
    connect(this, SIGNAL(clicked(const QModelIndex&)),
            this, SLOT(slotSelectItem(const QModelIndex)));

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

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

    //Init header
    updateHeader();

    for (int i = 0; i < folderModel_->columnCount(QModelIndex()) - 1; i++)
        resizeColumnToContents(i);

    /*connect(header(),SIGNAL(sectionMoved(int,int,int)),
                this, SLOT(slotMessageTreeColumnMoved(int,int,int)));*/

    //We set up the shortcuts here!!
    setupShortCut();

    //We attach the model because by default the view is enabled. At this point the model is empty so
    //it is a cheap operation!!
    attachModel();
}

MvQDetailedFolderView::~MvQDetailedFolderView()
{
    views.removeOne(this);
}

//Connect the models signal to the view
void MvQDetailedFolderView::attachModel()
{
    //Standard signals from the model
    setModel(filterModel_);

    connect(folderModel_, SIGNAL(iconSizeChanged()),
            this, SLOT(slotIconSizeChanged()));

    connect(folderModel_, SIGNAL(objectRenamed(const QModelIndex&, QString)),
            this, SLOT(slotObjectRenamed(const QModelIndex&, QString)));
}

//Disconnect the model from the view
void MvQDetailedFolderView::detachModel()
{
    setModel(0);
    disconnect(folderModel_, 0, this, 0);
}

void MvQDetailedFolderView::changeEvent(QEvent* event)
{
    if (event->type() == QEvent::EnabledChange) {
        if (isEnabled()) {
            attachModel();
            blockSignals(false);
        }
        //When the view is diabled we do not want to receive any signals from the model
        else {
            detachModel();
            blockSignals(true);
        }
    }
}

void MvQDetailedFolderView::doReset()
{
    reset();
}

QWidget* MvQDetailedFolderView::concreteWidget()
{
    return this;
}

MvQContextItemSet* MvQDetailedFolderView::cmSet()
{
    static MvQContextItemSet cmItems("DetailedFolderView");
    return &cmItems;
}

QModelIndexList MvQDetailedFolderView::selectedList()
{
    QModelIndexList lst;
    foreach (QModelIndex idx, selectedIndexes())
        if (idx.column() == 0)
            lst << idx;
    return lst;
}

void MvQDetailedFolderView::slotSelectItem(const QModelIndex& /*index*/)
{
    /*if(isFolder(index,FilterModelIndex))
	{   
		changeFolder(index,FilterModelIndex);
		emit currentFolderChanged(currentFolder());

	}*/
}

void MvQDetailedFolderView::slotDoubleClickItem(const QModelIndex& index)
{
    QModelIndexList lst = selectedList();
    if (lst.count() == 1 && lst.at(0) == index)
        handleDoubleClick(index);
}

void MvQDetailedFolderView::setupShortCut()
{
    if (shortCutInit_)
        return;
    else
        shortCutInit_ = true;

    if (MvQContextItemSet* cms = cmSet()) {
        foreach (MvQContextItem* cm, cms->icon()) {
            if (!appIconActions_ || !appIconActions_->shortCutDefined(cm->shortCut())) {
                if (QShortcut* sc = cm->makeShortCut(this)) {
                    connect(sc, SIGNAL(activated()),
                            this, SLOT(slotIconShortCut()));
                }
            }
        }

        foreach (MvQContextItem* cm, cms->desktop()) {
            if (!appDesktopActions_ || !appDesktopActions_->shortCutDefined(cm->shortCut())) {
                if (QShortcut* sc = cm->makeShortCut(this)) {
                    connect(sc, SIGNAL(activated()),
                            this, SLOT(slotDesktopShortCut()));
                }
            }
        }

        if (QShortcut* sc = cms->makeDefaultIconShortCut(this)) {
            defaultShortCut_ = sc;
            connect(defaultShortCut_, SIGNAL(activated()),
                    this, SLOT(slotDefaultShortCut()));
        }
    }
}

void MvQDetailedFolderView::slotIconShortCut()
{
    auto* sc = static_cast<QShortcut*>(QObject::sender());
    if (sc) {
        QModelIndexList lst = selectedList();
        if (lst.count() > 0)
            handleIconShortCut(sc, lst);
    }
}

void MvQDetailedFolderView::slotDefaultShortCut()
{
    if (defaultShortCut_) {
        QModelIndexList lst = selectedList();
        if (lst.count() == 1)
            handleDoubleClick(lst.at(0));
    }
}

void MvQDetailedFolderView::slotDesktopShortCut()
{
    auto* sc = static_cast<QShortcut*>(QObject::sender());
    if (sc) {
        //QPoint scrollOffset(horizontalScrollBar()->value(),verticalScrollBar()->value());
        //handleContextMenu(index,lst,mapToGlobal(position),position+scrollOffset,this);

        handleDesktopShortCut(sc, QPoint(0, 0), this);
    }
}

void MvQDetailedFolderView::slotContextMenu(const QPoint& position)
{
    QModelIndexList lst = selectedList();
    //QModelIndex index=indexAt(position);
    QPoint scrollOffset(horizontalScrollBar()->value(), verticalScrollBar()->value());

    handleContextMenu(indexAt(position), lst, mapToGlobal(position), position + scrollOffset, this);
}

void MvQDetailedFolderView::iconCommandFromMain(QString name)
{
    handleIconCommand(selectedList(), name);
}

void MvQDetailedFolderView::folderChanged()
{
    emit currentFolderChanged(currentFolder());
}

void MvQDetailedFolderView::iconCommand(QString name, IconObjectH obj)
{
    emit iconCommandRequested(name, obj);
}

void MvQDetailedFolderView::desktopCommand(QString name, QPoint pos)
{
    emit desktopCommandRequested(name, pos);
}

void MvQDetailedFolderView::slotObjectRenamed(const QModelIndex& sourceIndex, QString /*oriName*/)
{
    QModelIndex index = filterModel_->mapFromSource(sourceIndex);
    dataChanged(index, index);
}

//------------------------------------------
// Item info. See also mousemove event!
//------------------------------------------

void MvQDetailedFolderView::slotEntered(const QModelIndex& index)
{
    itemInfo_ = itemInfo(index, FilterModelIndex);
    emit itemEntered(itemInfo_);
}

void MvQDetailedFolderView::leaveEvent(QEvent* event)
{
    itemInfo_ = nullptr;
    emit itemEntered(itemInfo_);
    QWidget::leaveEvent(event);
}


//=================================================
//
// Icon positions
//
//==================================================

QRect MvQDetailedFolderView::itemRect(QList<IconObject*> objLst)
{
    QRect bbox;
    foreach (IconObject* obj, objLst) {
        bbox = bbox.united(visualRect(filterModel_->mapFromSource(folderModel_->indexFromObject(obj))));
    }

    return bbox;
}

QRect MvQDetailedFolderView::itemRect(IconObject* obj)
{
    return visualRect(filterModel_->mapFromSource(folderModel_->indexFromObject(obj)));
}


QRect MvQDetailedFolderView::pixmapRect(QList<IconObject*> objLst)
{
    QRect bbox;
    foreach (IconObject* obj, objLst) {
        bbox = bbox.united(pixmapRect(obj));
    }

    return bbox;
}


QRect MvQDetailedFolderView::pixmapRect(IconObject* obj)
{
    QRect r = visualRect(filterModel_->mapFromSource(folderModel_->indexFromObject(obj)));
    r.setSize(QSize(getIconSize(), getIconSize()));
    return r;
}

//=================================================
//
//  Drag and drop. We need this cutom implementation
//  because the solution offered by the model-view
//  framework was not satisfactory.
//
//=================================================

//===========================
// Drag
//===========================

void MvQDetailedFolderView::mousePressEvent(QMouseEvent* event)
{
    if (event->button() == Qt::LeftButton || event->button() == Qt::MiddleButton) {
        startPos_ = event->pos();
    }

    QTreeView::mousePressEvent(event);
}

void MvQDetailedFolderView::mouseMoveEvent(QMouseEvent* event)
{
    if (itemInfo_ && !indexAt(event->pos()).isValid()) {
        itemInfo_ = nullptr;
        emit itemEntered(itemInfo_);
    }

    if (event->buttons() & (Qt::LeftButton | Qt::MiddleButton)) {
        int distance = (event->pos() - startPos_).manhattanLength();
        if (distance >= QApplication::startDragDistance()) {
            if (moveActionEnabled_) {
                if (event->buttons() & Qt::LeftButton)
                    performDrag(Qt::MoveAction, event->pos());
                else if (event->buttons() & Qt::MiddleButton)
                    performDrag(Qt::CopyAction, event->pos());
            }
            else {
                performDrag(Qt::CopyAction, event->pos());
            }
        }
    }

    QTreeView::mouseMoveEvent(event);
}

void MvQDetailedFolderView::performDrag(Qt::DropAction dropAction, QPoint pos)
{
    QModelIndex viewIndex = indexAt(pos);
    QModelIndex index     = filterModel_->mapToSource(viewIndex);

    //The object that was dragged
    IconObject* dragObj = folderModel_->objectFromIndex(index);

    //List of objects to drag
    //We need only one index (the first column) for each row
    QList<IconObject*> objLst;
    QSet<int> rows;
    foreach (QModelIndex index, selectedIndexes()) {
        if (!rows.contains(index.row())) {
            rows << index.row();
            QModelIndex idx = filterModel_->index(index.row(), 0);
            objLst << folderModel_->objectFromIndex(filterModel_->mapToSource(idx));
        }
    }

    //It cannot be a helper!
    bool fromHelper = false;

    QDrag* drag = buildDrag(dragObj, objLst, fromHelper, this);
    if (drag) {
        drag->exec(Qt::CopyAction | Qt::MoveAction, dropAction);
    }
}

//===========================
// Drop
//===========================

void MvQDetailedFolderView::checkDropTarget(QDropEvent* event)
{
    IconObject* dragObj = 0;
    if (event->mimeData()->hasFormat("metview/icon")) {
        const auto* mimeData = qobject_cast<const MvQIconMimeData*>(event->mimeData());

        if (mimeData)
            dragObj = mimeData->dragObject();
    }


    if (!dragObj) {
        removeDropTarget();
        return;
    }

    Qt::DropAction dropAction = event->proposedAction();

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    QModelIndex index = indexAt(event->position().toPoint());
#else
    QModelIndex index = indexAt(event->pos());
#endif
    IconObject* obj   = folderModel_->objectFromIndex(filterModel_->mapToSource(index));

    if (obj != dragObj && obj && obj->isFolder() && !obj->locked()) {
        MvQDropTarget::Instance()->reset(QString::fromStdString(obj->name()), (dropAction == Qt::MoveAction));
        if (window()) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
            MvQDropTarget::Instance()->move(mapToGlobal(event->position().toPoint()) + QPoint(20, 20));
#else
            MvQDropTarget::Instance()->move(mapToGlobal(event->pos()) + QPoint(20, 20));
#endif
        }
        return;
    }
    else {
        removeDropTarget();
    }
}

void MvQDetailedFolderView::removeDropTarget()
{
    MvQDropTarget::Instance()->hide();
}

void MvQDetailedFolderView::dragEnterEvent(QDragEnterEvent* event)
{
    //qDebug() << event->mimeData()->formats();
    //qDebug() << event->mimeData()->text();
    //qDebug() << event->proposedAction();

    if (event->source() &&
        (event->proposedAction() == Qt::CopyAction ||
         event->proposedAction() == Qt::MoveAction)) {
        event->accept();
    }
    else
        event->ignore();
}

void MvQDetailedFolderView::dragMoveEvent(QDragMoveEvent* event)
{
    if (event->source() &&
        (event->proposedAction() == Qt::CopyAction ||
         event->proposedAction() == Qt::MoveAction)) {
        checkDropTarget(event);
        event->accept();
    }
    else {
        removeDropTarget();
        event->ignore();
    }
}

void MvQDetailedFolderView::dragLeaveEvent(QDragLeaveEvent* event)
{
    removeDropTarget();
    event->accept();
}

void MvQDetailedFolderView::dropEvent(QDropEvent* event)
{
    removeDropTarget();

    if (folderModel_->folder()->locked()) {
        event->ignore();
        return;
    }

    if (!event->source()) {
        event->ignore();
        return;
    }

    if (event->proposedAction() != Qt::CopyAction &&
        event->proposedAction() != Qt::MoveAction) {
        event->ignore();
        return;
    }

    //--------------------------------------
    // Drag and drop from another folder
    //--------------------------------------

    if (event->mimeData()->hasFormat("metview/icon")) {
        const auto* mimeData = qobject_cast<const MvQIconMimeData*>(event->mimeData());

        if (!mimeData) {
            event->ignore();
            return;
        }

        IconObject* dragObj   = mimeData->dragObject();
        MvQFolderModel* model = mimeData->model();

        if (dragObj && (model || mimeData->fromEditor())) {
            bool fromSameView     = false;
            MvQFolderModel* model = mimeData->model();
            if (model) {
                fromSameView = (model == folderModel_ || model->folder() == folderModel_->folder());
            }
            else if (mimeData->fromEditor()) {
                fromSameView = (folderModel_->folder() == dragObj->parent());
            }

            //We cannot drop icons into a locked folder!
            if (!fromSameView && folderModel_->folder()->locked()) {
                event->ignore();
                return;
            }
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
            performDrop(event->proposedAction(), mimeData, event->position().toPoint(), fromSameView);
#else
            performDrop(event->proposedAction(), mimeData, event->pos(), fromSameView);
#endif

            event->accept();
            return;
        }
    }

    //--------------------------------------
    // Drag and drop from create icon panel
    //--------------------------------------

    else if (event->mimeData()->hasFormat("metview/new_icon")) {
        const auto* mimeData = qobject_cast<const MvQNewIconMimeData*>(event->mimeData());

        if (!mimeData) {
            event->ignore();
            return;
        }

        if (mimeData->iconDefType() == MvQNewIconMimeData::UserDef) {
            const IconClass& kind = IconClass::find(mimeData->className().toStdString());
            kind.createOne(folderModel_->folder());

            event->accept();
            return;
        }
    }

    event->ignore();
}

void MvQDetailedFolderView::performDrop(Qt::DropAction dropAction, const MvQIconMimeData* data,
                                        QPoint pos, bool fromSameView)
{
    bool toSameFolder = true;
    Folder* folder    = folderModel_->folder();
    IconObject* objAt = folderModel_->objectFromIndex(filterModel_->mapToSource(indexAt(pos)));

    //See if we drop the icons onto a folder icons. We do not allow dropping a folder into itself!!
    if (objAt && objAt->isFolder() && !data->objects().contains(objAt)) {
        folder       = static_cast<Folder*>(objAt);
        toSameFolder = false;
    }

    foreach (IconObject* obj, data->objects()) {
        if (!isAccepted(obj))
            continue;

        //We set the object position to zero. This will instuct the icon folder view
        //(when the folder is next open in it) to find meaningful positions to the
        //icons.

        if (dropAction == Qt::CopyAction || moveActionEnabled_ == false) {
            obj->clone(folder, false);
        }
        else if ((!fromSameView || !toSameFolder) &&
                 dropAction == Qt::MoveAction && folder) {
            obj->position(0, 0);
            folder->adopt(obj);
        }
    }
}


void MvQDetailedFolderView::keyPressEvent(QKeyEvent* event)
{
    QTreeView::keyPressEvent(event);
}

//=========================================
// Icons
//=========================================

void MvQDetailedFolderView::rename(IconObject* obj)
{
    if (!obj)
        return;
    QModelIndex index = filterModel_->mapFromSource(folderModel_->indexFromObject(obj));
    edit(index);
}


void MvQDetailedFolderView::slotIconSizeChanged()
{
    reset();
    for (int i = 0; i < folderModel_->columnCount(QModelIndex()) - 1; i++)
        resizeColumnToContents(i);
}

void MvQDetailedFolderView::blink(const QModelIndex& index)
{
    scrollTo(index);
    delegate_->blink(index);
}


void MvQDetailedFolderView::showIcon(const QModelIndex& index)
{
    scrollTo(index);
    setCurrentIndex(index);
}

//=========================================
// Header
//=========================================

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

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

    QList<QAction*> lst;
    auto* menu = new QMenu(this);
    QAction* ac;

    for (int i = 0; i < header()->count(); i++) {
        QString name = header()->model()->headerData(i, Qt::Horizontal).toString();
        ac           = new QAction(menu);
        ac->setText(name);
        ac->setCheckable(true);
        ac->setData(i);

        if (i == 0) {
            ac->setChecked(true);
            ac->setEnabled(false);
        }
        else {
            ac->setChecked(!(header()->isSectionHidden(i)));
        }

        menu->addAction(ac);
    }

    ac = menu->exec(header()->mapToGlobal(position));
    if (ac && ac->isEnabled() && ac->isCheckable()) {
        int i = ac->data().toInt();
        header()->setSectionHidden(i, !ac->isChecked());
        MvQDesktopSettings::headerVisible_[i] = ac->isChecked();
        broadcastHeaderChange();
    }
    delete menu;
}

void MvQDetailedFolderView::updateHeader()
{
    if (header()->count() != MvQDesktopSettings::headerVisible_.count()) {
        MvQDesktopSettings::headerVisible_.clear();

        for (int i = 0; i < header()->count(); i++)
            MvQDesktopSettings::headerVisible_ << !header()->isSectionHidden(i);
    }
    else {
        for (int i = 0; i < MvQDesktopSettings::headerVisible_.count(); i++) {
            if (i != 0)
                header()->setSectionHidden(i, !MvQDesktopSettings::headerVisible_[i]);
        }
    }
}

void MvQDetailedFolderView::broadcastHeaderChange()
{
    foreach (MvQDetailedFolderView* v, views)
        v->updateHeader();
}
