#include "reporttable.hpp"

#include <algorithm>
#include <utility>

#include <QAction>
#include <QContextMenuEvent>
#include <QHeaderView>
#include <QMenu>
#include <QMouseEvent>
#include <QPainter>
#include <QSortFilterProxyModel>
#include <QStyledItemDelegate>
#include <QTextDocument>

#include <apps/opencs/model/doc/document.hpp>
#include <apps/opencs/model/prefs/category.hpp>
#include <apps/opencs/model/prefs/setting.hpp>
#include <apps/opencs/view/world/dragrecordtable.hpp>
#include <apps/opencs/view/world/util.hpp>

#include "../../model/tools/reportmodel.hpp"

#include "../../model/prefs/shortcut.hpp"
#include "../../model/prefs/state.hpp"

#include "../../view/world/idtypedelegate.hpp"

namespace CSVTools
{
    class RichTextDelegate : public QStyledItemDelegate
    {
    public:
        RichTextDelegate(QObject* parent = nullptr);

        void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
    };
}

CSVTools::RichTextDelegate::RichTextDelegate(QObject* parent)
    : QStyledItemDelegate(parent)
{
}

void CSVTools::RichTextDelegate::paint(
    QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    QTextDocument document;
    QVariant value = index.data(Qt::DisplayRole);
    if (value.isValid() && !value.isNull())
    {
        document.setHtml(value.toString());
        painter->translate(option.rect.topLeft());
        document.drawContents(painter);
        painter->translate(-option.rect.topLeft());
    }
}

void CSVTools::ReportTable::contextMenuEvent(QContextMenuEvent* event)
{
    QModelIndexList selectedRows = selectionModel()->selectedRows();

    // create context menu
    QMenu menu(this);

    if (!selectedRows.empty())
    {
        menu.addAction(mShowAction);
        menu.addAction(mRemoveAction);

        bool found = false;
        for (QModelIndexList::const_iterator iter(selectedRows.begin()); iter != selectedRows.end(); ++iter)
        {
            QString hint = mProxyModel->data(mProxyModel->index(iter->row(), 2)).toString();

            if (!hint.isEmpty() && hint[0] == 'R')
            {
                found = true;
                break;
            }
        }

        if (found)
            menu.addAction(mReplaceAction);
    }

    if (mRefreshAction)
        menu.addAction(mRefreshAction);

    menu.exec(event->globalPos());
}

void CSVTools::ReportTable::mouseMoveEvent(QMouseEvent* event)
{
    if (event->buttons() & Qt::LeftButton)
        startDragFromTable(*this, indexAt(event->pos()));
}

void CSVTools::ReportTable::mouseDoubleClickEvent(QMouseEvent* event)
{
    Qt::KeyboardModifiers modifiers = event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier);

    QModelIndex index = currentIndex();

    selectionModel()->select(
        index, QItemSelectionModel::Clear | QItemSelectionModel::Select | QItemSelectionModel::Rows);

    std::map<Qt::KeyboardModifiers, DoubleClickAction>::iterator iter = mDoubleClickActions.find(modifiers);

    if (iter == mDoubleClickActions.end())
    {
        event->accept();
        return;
    }

    switch (iter->second)
    {
        case Action_None:

            event->accept();
            break;

        case Action_Edit:

            event->accept();
            showSelection();
            break;

        case Action_Remove:

            event->accept();
            removeSelection();
            break;

        case Action_EditAndRemove:

            event->accept();
            showSelection();
            removeSelection();
            break;
    }
}

CSVTools::ReportTable::ReportTable(CSMDoc::Document& document, const CSMWorld::UniversalId& id,
    bool richTextDescription, int refreshState, QWidget* parent)
    : CSVWorld::DragRecordTable(document, parent)
    , mModel(document.getReport(id))
    , mRefreshAction(nullptr)
    , mRefreshState(refreshState)
{
    horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
    horizontalHeader()->setStretchLastSection(true);
    verticalHeader()->hide();
    setSortingEnabled(true);
    setSelectionBehavior(QAbstractItemView::SelectRows);
    setSelectionMode(QAbstractItemView::ExtendedSelection);

    mProxyModel = new QSortFilterProxyModel(this);
    mProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
    mProxyModel->setSourceModel(mModel);
    mProxyModel->setSortRole(Qt::UserRole);

    setModel(mProxyModel);
    setColumnHidden(2, true);

    mIdTypeDelegate = CSVWorld::IdTypeDelegateFactory().makeDelegate(nullptr, mDocument, this);

    setItemDelegateForColumn(0, mIdTypeDelegate);

    if (richTextDescription)
        setItemDelegateForColumn(mModel->columnCount() - 1, new RichTextDelegate(this));

    mShowAction = new QAction(tr("Show"), this);
    connect(mShowAction, &QAction::triggered, this, &ReportTable::showSelection);
    addAction(mShowAction);
    CSMPrefs::Shortcut* showShortcut = new CSMPrefs::Shortcut("reporttable-show", this);
    showShortcut->associateAction(mShowAction);

    mRemoveAction = new QAction(tr("Remove from list"), this);
    connect(mRemoveAction, &QAction::triggered, this, &ReportTable::removeSelection);
    addAction(mRemoveAction);
    CSMPrefs::Shortcut* removeShortcut = new CSMPrefs::Shortcut("reporttable-remove", this);
    removeShortcut->associateAction(mRemoveAction);

    mReplaceAction = new QAction(tr("Replace"), this);
    connect(mReplaceAction, &QAction::triggered, this, &ReportTable::replaceRequest);
    addAction(mReplaceAction);
    CSMPrefs::Shortcut* replaceShortcut = new CSMPrefs::Shortcut("reporttable-replace", this);
    replaceShortcut->associateAction(mReplaceAction);

    if (mRefreshState)
    {
        mRefreshAction = new QAction(tr("Refresh"), this);
        mRefreshAction->setEnabled(!(mDocument.getState() & mRefreshState));
        connect(mRefreshAction, &QAction::triggered, this, &ReportTable::refreshRequest);
        addAction(mRefreshAction);
        CSMPrefs::Shortcut* refreshShortcut = new CSMPrefs::Shortcut("reporttable-refresh", this);
        refreshShortcut->associateAction(mRefreshAction);
    }

    mDoubleClickActions.insert(std::make_pair(Qt::NoModifier, Action_Edit));
    mDoubleClickActions.insert(std::make_pair(Qt::ShiftModifier, Action_Remove));
    mDoubleClickActions.insert(std::make_pair(Qt::ControlModifier, Action_EditAndRemove));

    connect(&CSMPrefs::State::get(), &CSMPrefs::State::settingChanged, this, &ReportTable::settingChanged);
    CSMPrefs::get()["Reports"].update();
}

std::vector<CSMWorld::UniversalId> CSVTools::ReportTable::getDraggedRecords() const
{
    std::vector<CSMWorld::UniversalId> ids;

    QModelIndexList selectedRows = selectionModel()->selectedRows();

    for (QModelIndexList::const_iterator iter(selectedRows.begin()); iter != selectedRows.end(); ++iter)
    {
        ids.push_back(mModel->getUniversalId(mProxyModel->mapToSource(*iter).row()));
    }

    return ids;
}

std::vector<int> CSVTools::ReportTable::getReplaceIndices(bool selection) const
{
    std::vector<int> indices;

    if (selection)
    {
        QModelIndexList selectedRows = selectionModel()->selectedRows();

        std::vector<int> rows;

        for (QModelIndexList::const_iterator iter(selectedRows.begin()); iter != selectedRows.end(); ++iter)
        {
            rows.push_back(mProxyModel->mapToSource(*iter).row());
        }

        std::sort(rows.begin(), rows.end());

        for (std::vector<int>::const_iterator iter(rows.begin()); iter != rows.end(); ++iter)
        {
            QString hint = mModel->data(mModel->index(*iter, 2)).toString();

            if (!hint.isEmpty() && hint[0] == 'R')
                indices.push_back(*iter);
        }
    }
    else
    {
        for (int i = 0; i < mModel->rowCount(); ++i)
        {
            QString hint = mModel->data(mModel->index(i, 2)).toString();

            if (!hint.isEmpty() && hint[0] == 'R')
                indices.push_back(i);
        }
    }

    return indices;
}

void CSVTools::ReportTable::flagAsReplaced(int index)
{
    mModel->flagAsReplaced(index);
}

void CSVTools::ReportTable::settingChanged(const CSMPrefs::Setting* setting)
{
    if (setting->getParent()->getKey() == "Reports")
    {
        QString base("double");
        QString key = setting->getKey().c_str();
        if (key.startsWith(base))
        {
            QString modifierString = key.mid(base.size());
            Qt::KeyboardModifiers modifiers;

            if (modifierString == "-s")
                modifiers = Qt::ShiftModifier;
            else if (modifierString == "-c")
                modifiers = Qt::ControlModifier;
            else if (modifierString == "-sc")
                modifiers = Qt::ShiftModifier | Qt::ControlModifier;

            DoubleClickAction action = Action_None;

            std::string value = setting->toString();

            if (value == "Edit")
                action = Action_Edit;
            else if (value == "Remove")
                action = Action_Remove;
            else if (value == "Edit And Remove")
                action = Action_EditAndRemove;

            mDoubleClickActions[modifiers] = action;

            return;
        }
    }
    else if (*setting == "Records/type-format")
        mIdTypeDelegate->settingChanged(setting);
}

void CSVTools::ReportTable::showSelection()
{
    QModelIndexList selectedRows = selectionModel()->selectedRows();

    for (QModelIndexList::const_iterator iter(selectedRows.begin()); iter != selectedRows.end(); ++iter)
    {
        int row = mProxyModel->mapToSource(*iter).row();
        emit editRequest(mModel->getUniversalId(row), mModel->getHint(row));
    }
}

void CSVTools::ReportTable::removeSelection()
{
    QModelIndexList selectedRows = selectionModel()->selectedRows();

    std::vector<int> rows;

    for (QModelIndexList::iterator iter(selectedRows.begin()); iter != selectedRows.end(); ++iter)
    {
        rows.push_back(mProxyModel->mapToSource(*iter).row());
    }

    std::sort(rows.begin(), rows.end());

    for (std::vector<int>::const_reverse_iterator iter(rows.rbegin()); iter != rows.rend(); ++iter)
        mProxyModel->removeRows(*iter, 1);

    selectionModel()->clear();
}

void CSVTools::ReportTable::clear()
{
    mModel->clear();
}

void CSVTools::ReportTable::stateChanged(int state, CSMDoc::Document* document)
{
    if (mRefreshAction)
        mRefreshAction->setEnabled(!(state & mRefreshState));
}
