/***************************** 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 "MvQFolderModel.h"

#include <QtGlobal>
#include <QApplication>
#include <QDateTime>
#include <QDebug>
#include <QFont>
#include <QFontMetrics>
#include <QMimeData>
#include <QPainter>
#include <QPixmap>

#include "IconClass.h"
#include "IconInfo.h"
#include "Path.h"

#include "Folder.h"
#include "FolderInfo.h"
#include "FolderSearchData.h"

#include "IconFactory.h"

#include "MvQFolderItemProperty.h"
#include "MvQIconProvider.h"
#include "MvQTheme.h"
#include "MvQFileInfo.h"

#include <cassert>
#include <map>

QHash<IconObject::IconStatus, QColor> MvQFolderModel::statusColour_;
QColor MvQFolderModel::fadedColour_;

MvQFolderModel::MvQFolderModel(QObject* parent) :
    QAbstractItemModel(parent)
{
    init();
}


MvQFolderModel::MvQFolderModel(int iconSize, QObject* parent) :
    QAbstractItemModel(parent)
{
    init();
    itemProp_->setIconSize(iconSize);
}

void MvQFolderModel::init()
{
    searchData_ = nullptr;
    folder_ = nullptr;
    lastArrived_ = nullptr;

    itemProp_ = new MvQFolderItemProperty();

    if (statusColour_.empty()) {
        statusColour_[IconObject::DefaultStatus] = MvQTheme::colour("folderview", "text");
        statusColour_[IconObject::ReadyStatus] = MvQTheme::colour("folderview", "ready_status");
        statusColour_[IconObject::WaitingStatus] = MvQTheme::colour("folderview", "waiting_status");
        statusColour_[IconObject::ErrorStatus] = MvQTheme::colour("folderview", "error_status");

        fadedColour_ = MvQTheme::colour("folderview", "faded_text");
    }
}

MvQFolderModel::~MvQFolderModel()
{
    saveFolderInfo();
    if (folder_) {
        folder_->removeObserver(this);
        ((IconObject*)folder_)->removeObserver((IconObserver*)this);
    }

    delete itemProp_;
}

bool MvQFolderModel::isDataSet() const
{
    return (folder_ == nullptr) ? false : true;
}

void MvQFolderModel::folderIsAboutToChange()
{
    beginResetModel();
}

bool MvQFolderModel::setFolder(Folder* folder, int iconSize)
{
    bool retVal = false;

    if (folder == folder_)
        return false;

    saveFolderInfo();

    beginResetModel();

    // Clear all the items!!!
    items_.clear();

    if (folder_) {
        folder_->removeObserver(this);
        ((IconObject*)folder_)->removeObserver((IconObserver*)this);
    }

    folder_ = folder;
    lastArrived_ = nullptr;

    if (folder_) {
        // We are not an observer at the moment so arrived() will
        // not be called!
        folder_->scan();

        // Load items_ from folder
        loadItems();

        // Now we become an observer. Any changes afterwards will end up
        // in arrived(), gone() etc. observer methods!
        folder_->addObserver(this);
        ((IconObject*)folder_)->addObserver((IconObserver*)this);

        if (iconSize != -1)
            itemProp_->setIconSize(iconSize);

        retVal = true;
    }

    // Update the search status
    updateSearchData(false);

    endResetModel();

    return retVal;
}

bool MvQFolderModel::setFolder(QString fullName, int iconSize)
{
    if (folder_ && QString::fromStdString(folder_->fullName()) == fullName)
        return false;

    return setFolder(Folder::folder(fullName.toStdString(), false), iconSize);
}

bool MvQFolderModel::setFolder(const QModelIndex& index, int iconSize)
{
    IconObject* obj = objectFromIndex(index);
    if (obj && obj->isFolder()) {
        setFolder(static_cast<Folder*>(obj), iconSize);
        return true;
    }

    return false;
}


bool MvQFolderModel::setFolderToParent(int iconSize)
{
    if (!folder_)
        return false;

    IconObject* obj = folder_->parent();
    if (obj && obj->isFolder()) {
        return setFolder(static_cast<Folder*>(obj), iconSize);
    }

    return false;
}

int MvQFolderModel::columnCount(const QModelIndex& parent) const
{
    if (!isDataSet())
        return 0;

    return parent.isValid() ? 0 : 6;
}


int MvQFolderModel::rowCount(const QModelIndex& index) const
{
    if (!isDataSet())
        return 0;

    if (!index.isValid()) {
        return items_.count();
    }
    else {
        return 0;
    }
}

QVariant MvQFolderModel::data(const QModelIndex& index, int role) const
{
    if (role != Qt::DisplayRole && role != Qt::DecorationRole &&
        role != Qt::ForegroundRole && role != Qt::FontRole &&
        role != ClassFilterRole && role != PositionRole && role != EditorRole &&
        role != Qt::EditRole && role != Qt::UserRole) {
        return {};
    }

    if (!index.isValid()) {
        return {};
    }

    IconObject* obj = objectFromIndex(index);
    if (!obj)
        return QString();

    // First column: icon + name
    if (index.column() == 0) {
        switch (role) {
            case Qt::DecorationRole: {
                if (!hasSearchData() || !searchData_->isSet() || searchData_->match(obj)) {
                    return MvQIconProvider::pixmap(obj, itemProp_->iconSize());
                }
                else {
                    return MvQIconProvider::greyedOutPixmap(obj, itemProp_->iconSize());
                }
            }

            case Qt::DisplayRole:
            case Qt::EditRole:
                return QString::fromStdString(obj->name());

            case Qt::ForegroundRole: {
                if (!hasSearchData() || !searchData_->isSet() || searchData_->match(obj)) {
                    QHash<IconObject::IconStatus, QColor>::const_iterator it = statusColour_.find(obj->status());
                    if (it != statusColour_.end())
                        return it.value();
                    else
                        return QColor();
                }
                else {
                    return fadedColour_;
                }


            } break;

            case ClassFilterRole:
                return isAccepted(obj);

            case PositionRole:
                // We get the reference position from the object
                return itemProp_->referencePosition(obj);

            case EditorRole:
                return (obj->editor()) ? true : false;

            case Qt::FontRole: {
                if (obj->isLink()) {
                    QFont font = itemProp_->font();
                    font.setItalic(true);
                    return font;
                }
                else
                    return itemProp_->font();
            }
            default:
                return QString();
        }
    }
    else if (index.column() == 1) {
        if (role == Qt::DisplayRole)
            return QString::fromStdString(obj->editorClass().name());
    }
    else if (index.column() == 2) {
        if (role == Qt::DisplayRole)
            return formatFileSize(obj->path().sizeInBytes());
        else if (role == Qt::UserRole) {
            qint64 qs = obj->path().sizeInBytes();
            return qs;
        }
    }
    else if (index.column() == 3) {
        if (role == Qt::DisplayRole)
            return formatFileDate(obj->path().lastModified());
    }
    else if (index.column() == 4) {
        if (role == Qt::DisplayRole)
            return QString::fromStdString(obj->path().owner());
    }
    else if (index.column() == 5) {
        if (role == Qt::DisplayRole)
            return QString::fromStdString(obj->path().permissions());
    }


    if (index.column() >= 1 && index.column() <= 5) {
        if (role == Qt::ForegroundRole) {
            return statusColour_[IconObject::DefaultStatus];
        }
        else if (role == Qt::FontRole)
            return itemProp_->font();
    }

    return {};
}

bool MvQFolderModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
    if (!index.isValid() || (role != Qt::EditRole && role != PositionRole) || index.column() != 0) {
        return false;
    }

    IconObject* obj = objectFromIndex(index);

    if (role == Qt::EditRole) {
        std::string new_name = value.toString().toStdString();

        if (obj && obj->name() != new_name) {
            // This will call the renamed() observer method!
            return obj->rename(new_name);
        }
    }
    else if (role == PositionRole) {
        QPoint viewRefPos = value.toPoint();

        // Compute the stored reference position from the view reference postion.
        QPoint refPos = itemProp_->storedPosition(viewRefPos);
        obj->info().position(refPos.x(), refPos.y());
        return true;
    }


    return false;
}

QVariant MvQFolderModel::headerData(const int section, const Qt::Orientation orient, const int role) const
{
    if (orient != Qt::Horizontal || role != Qt::DisplayRole)
        return {};

    if (section == 0)
        return "Name";
    else if (section == 1)
        return "Type";
    else if (section == 2)
        return "Size";
    else if (section == 3)
        return "Date";
    else if (section == 4)
        return "Owner";
    else if (section == 5)
        return "Permissions";

    return {};
}


QModelIndex MvQFolderModel::index(int row, int column, const QModelIndex& /*parent*/) const
{
    if (!isDataSet()) {
        return {};
    }

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


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

Qt::ItemFlags MvQFolderModel::flags(const QModelIndex& index) const
{
    Qt::ItemFlags defaultFlags;

    defaultFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;

    if (index.column() == 0) {
        IconObject* obj = objectFromIndex(index);
        if (obj && !obj->locked() && !obj->editor()) {
            return Qt::ItemIsEditable | defaultFlags;
        }
    }
    return defaultFlags;
}

IconObject* MvQFolderModel::objectFromIndex(const QModelIndex& index) const
{
    if (!index.isValid()) {
        return nullptr;
    }

    if (index.row() >= 0 && index.row() < items_.count())
        return items_.at(index.row());

    return nullptr;
}

QModelIndex MvQFolderModel::indexFromObject(IconObject* obj)
{
    if (!obj)
        return {};

    int i = items_.indexOf(obj);
    return (i >= 0) ? createIndex(i, 0, (void*)nullptr) : QModelIndex();
}

// Should be called between beginResetModel() and endResetModel() !!
void MvQFolderModel::loadItems()
{
    items_.clear();
    if (!folder_)
        return;

    const std::map<std::string, IconObjectH>& kids = folder_->kids();
    for (const auto& kid : kids)
        if (isAccepted(kid.second)) {
            items_ << kid.second;
        }
}

//------------------------------------
//
//  Filtering
//
//-------------------------------------

void MvQFolderModel::setAcceptedClasses(const std::vector<std::string>& c)
{
    beginResetModel();
    classes_ = c;
    loadItems();
    endResetModel();
}

bool MvQFolderModel::isAccepted(const IconClass& kind) const
{
    if (classes_.size() > 0) {
        for (const auto& classe : classes_) {
            if (kind.canBecome(IconClass::find(classe)))
                return true;
        }
        return false;
    }

    return true;
}

bool MvQFolderModel::isAccepted(IconObject* obj) const
{
    if (obj) {
        if (!obj->visible())
            return false;

        const IconClass& kind = obj->editorClass();
        return isAccepted(kind);
    }

    return false;
}

void MvQFolderModel::saveFolderInfo()
{
    if (folder_)
        folder_->saveFolderInfo();
}


QString MvQFolderModel::fullName()
{
    return (folder_) ? QString::fromStdString(folder_->fullName()) : QString();
}

QString MvQFolderModel::fullName(const QModelIndex& index)
{
    IconObject* obj = objectFromIndex(index);
    return (obj) ? QString::fromStdString(obj->fullName()) : QString();
}

bool MvQFolderModel::isFolder(const QModelIndex& index)
{
    IconObject* obj = objectFromIndex(index);
    return (obj && obj->isFolder()) ? true : false;
}

QModelIndex MvQFolderModel::lastArrived()
{
    if (lastArrived_) {
        return indexFromObject(lastArrived_);
    }

    return {};
}

//---------------------------------------
// Icon positions
//---------------------------------------

int MvQFolderModel::iconSize() const
{
    return itemProp_->iconSize();
}

void MvQFolderModel::setIconSize(int size)
{
    if (!itemProp_->setIconSize(size))
        return;

    emit iconSizeChanged();
}

void MvQFolderModel::setPositionHint(const std::string& cn, int x, int y)
{
    posHint_.className = cn;
    posHint_.x = x;
    posHint_.y = y;
}

//----------------------------------------------
//
//  FolderObserver methods
//
//----------------------------------------------

void MvQFolderModel::arrived(IconObject* obj)
{
    if (obj && isAccepted(obj)) {
        // beginInsertRows(), endInsertRows() were just resetting the whole view!
        // It is easier just reset the model because our icon view knows what to do with the
        // icon positions in this case
        beginResetModel();
        items_ << obj;
        lastArrived_ = obj;

        if ((posHint_.className.empty() || posHint_.className == obj->iconClass().name()) &&
            obj->info().x() == 0 && obj->info().y() == 0) {
            // positionHint contains the viewRefPos!!
            QPoint refPos = itemProp_->storedPosition(QPoint(posHint_.x, posHint_.y));
            obj->info().position(refPos.x(), refPos.y());
        }
        posHint_.clear();

        // Update saerch data
        updateSearchData(false);

        endResetModel();

        // emit objectArrived(indexFromObject(obj));
    }
}

void MvQFolderModel::gone(IconObject* obj)
{
    if (obj && items_.contains(obj)) {
        beginResetModel();
        items_.removeOne(obj);

        // Update saerch data
        updateSearchData(false);

        endResetModel();
    }
}

void MvQFolderModel::position(IconObject*, int, int)
{
}

void MvQFolderModel::renamed(IconObject* obj, const std::string& oriName)
{
    if (isAccepted(obj)) {
        // Update saerch data
        updateSearchData(false);

        // We notify the view about the renaming
        emit objectRenamed(indexFromObject(obj), QString::fromStdString(oriName));
    }
}

void MvQFolderModel::waiting(IconObject* obj)
{
    objectChanged(obj);
}

void MvQFolderModel::error(IconObject* obj)
{
    objectChanged(obj);
}

void MvQFolderModel::modified(IconObject* obj)
{
    objectChanged(obj);
}

void MvQFolderModel::ready(IconObject* obj)
{
    objectChanged(obj);
}

void MvQFolderModel::opened(IconObject* obj)
{
    objectChanged(obj);
}

void MvQFolderModel::closed(IconObject* obj)
{
    objectChanged(obj);
}

void MvQFolderModel::objectChanged(IconObject* obj)
{
    if (obj) {
        QModelIndex index = indexFromObject(obj);
        emit dataChanged(index, index);
    }
}

void MvQFolderModel::highlight(IconObject*)
{
}

// This is an iconobserver method. It is called when the folder itself (e.g. name)
// has been changed/
void MvQFolderModel::iconChanged(IconObject* obj)
{
    if (obj == folder_) {
        emit folderChanged(folder_);
    }
}


void MvQFolderModel::createNewFolder()
{
    // This will call in the end the arrived() observer method!
    IconClass::find("FOLDER").createOne(folder_);
}

QString MvQFolderModel::formatFileSize(qint64 size) const
{
    return MvQFileInfo::formatSize(size);
}

QString MvQFolderModel::formatFileDate(time_t t) const
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
    QDateTime dt;
    dt.setSecsSinceEpoch(t);
#else
    QDateTime dt = QDateTime::fromTime_t(t);
#endif
    return dt.toString("yyyy-MM-dd hh:mm");
}


//---------------------------------------------------------
//
// Filter/search
//
//---------------------------------------------------------

void MvQFolderModel::setSearchData(FolderSearchData* data)
{
    searchData_ = data;

    // Register owner
    searchData_->setOwner(this);

    updateSearchData(true);
}

void MvQFolderModel::updateSearchData(bool doReset)
{
    if (!hasSearchData()) {
        if (doReset)
            // reset();  NOT in Qt 5
            beginResetModel();
        endResetModel();
        return;
    }

    // Clear search results
    searchData_->clearMatch();

    if (searchData_->isSet()) {
        for (int i = 0; i < rowCount(); i++) {
            QModelIndex idx = index(i, 0);
            searchData_->load(objectFromIndex(idx));
        }
    }

    // Notify changes
    searchData_->matchChanged();

    if (doReset)
        // reset();  NOT in Qt 5
        beginResetModel();
    endResetModel();
}

bool MvQFolderModel::hasSearchData() const
{
    if (!searchData_) {
        return false;
    }

    if (searchData_->owner() != this) {
        searchData_ = nullptr;
        return false;
    }

    return true;
}


/*

    bool found=false;

    if(filterName_ == name.toStdString() && filterType_ == type.toStdString())
        return found;

    filterName_=name.toStdString();
    filterType_=type.toStdString();

    filterMatchLst_.clear();

    if(!filterName_.empty() || !filterType_.empty())
    {
        for(int i=0; i< rowCount(); i++)
        {
            QModelIndex idx=index(i,0);
            if(IconObject *obj=objectFromIndex(idx))
            {
                if(obj->match(filterName_,filterType_))
                {
                    filterMatchLst_ << obj;
                }
            }
        }
    }

    reset();
    return found;
}*/


//=======================================
//
// MvQFolderFilterModel:
//
//=======================================

MvQFolderFilterModel::MvQFolderFilterModel(QObject* parent) :
    QSortFilterProxyModel(parent)
{
}

void MvQFolderFilterModel::setSourceModel(QAbstractItemModel* sourceModel)
{
    QSortFilterProxyModel::setSourceModel(sourceModel);
}

bool MvQFolderFilterModel::filterAcceptsRow(int sourceRow,
                                            const QModelIndex& /*sourceParent*/) const
{
    QModelIndex index = sourceModel()->index(sourceRow, 0);
    return sourceModel()->data(index, MvQFolderModel::ClassFilterRole).toBool();
}


bool MvQFolderFilterModel::lessThan(const QModelIndex& left,
                                    const QModelIndex& right) const
{
    QVariant leftData, rightData;
    if (left.column() == 2) {
        leftData = sourceModel()->data(left, Qt::UserRole);
        rightData = sourceModel()->data(right, Qt::UserRole);
        return leftData.toLongLong() < rightData.toLongLong();
    }
    else
        return QSortFilterProxyModel::lessThan(left, right);
}

/*QVariant MvQFolderFilterModel::data(const QModelIndex& index, int role ) const
{
    if(role == Qt::DecorationRole && matchLst_.count() > 0 )
    {
        if(MvQFolderModel *md=static_cast<MvQFolderModel*>(sourceModel()))
        {
            if(IconObject *obj=md->objectFromIndex(mapToSource(index)))
            {
                if(!matchLst_.contains(obj))
                {
                    return md->data(mapToSource(index),MvQFolderModel::GreyedOutRole);
                }
            }
        }
    }

    return sourceModel()->data(mapToSource(index),role);
}  */
/*
bool MvQFolderFilterModel::setFilter(QString name,QString type)
{
    bool found=false;

    if(name_ == name.toStdString() && type_ == type.toStdString())
        return found;

    name_=name.toStdString();
    type_=type.toStdString();

    matchLst_.clear();

    MvQFolderModel *md=static_cast<MvQFolderModel*>(sourceModel());
    if(!md)
        return found;

    if(!name_.empty() || !type_.empty())
    {
        for(int i=0; i< rowCount(); i++)
        {
            QModelIndex idx=index(i,0);
            if(IconObject *obj=md->objectFromIndex(mapToSource(idx)))
            {
                if(obj->match(name_,type_))
                    matchLst_ << obj;
            }
        }
    }

    reset();

    return found;
}
*/
