/*
    SPDX-FileCopyrightText: 2019 Alexander Potashev <aspotashev@gmail.com>

    SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/

#include "tasksmodel.h"

#include <QMovie>
#include <QPixmap>
#include <QStack>

#include <KLocalizedString>

#include "task.h"

TasksModel::TasksModel()
    : m_rootItem(new TasksModelItem(this, nullptr))
    , m_headerLabels{i18nc("@title:column", "Task Name"),
                     i18nc("@title:column", "Session Time"),
                     i18nc("@title:column", "Time"),
                     i18nc("@title:column", "Total Session Time"),
                     i18nc("@title:column", "Total Time"),
                     i18nc("@title:column", "Priority"),
                     i18nc("@title:column", "Percent Complete")}
    , m_clockAnimation(nullptr)
    , m_dragCutTaskId()
{
    Q_INIT_RESOURCE(pics);

    m_clockAnimation = new QMovie(QStringLiteral(":/pics/watch.gif"), QByteArray(), this);

    // Prepare animated icon
    connect(m_clockAnimation, &QMovie::frameChanged, this, &TasksModel::setActiveIcon);

    // TODO: stop animation when no tasks are active
    m_clockAnimation->start();
}

void TasksModel::clear()
{
    beginResetModel();

    // Empty "m_children", move it to "children".
    QList<TasksModelItem *> children;
    children.swap(m_rootItem->m_children);

    for (int i = 0; i < children.count(); ++i) {
        TasksModelItem *item = children.at(i);
        item->m_parent = nullptr;
        delete item;
    }

    endResetModel();
}

int TasksModel::topLevelItemCount() const
{
    return m_rootItem->childCount();
}

int TasksModel::indexOfTopLevelItem(TasksModelItem *item) const
{
    return m_rootItem->m_children.indexOf(item);
}

TasksModelItem *TasksModel::topLevelItem(int index) const
{
    return m_rootItem->child(index);
}

TasksModelItem *TasksModel::takeTopLevelItem(int index)
{
    return m_rootItem->takeChild(index);
}

TasksModelItem *TasksModel::item(const QModelIndex &index) const
{
    if (!index.isValid()) {
        return nullptr;
    }

    return static_cast<TasksModelItem *>(index.internalPointer());
}

QModelIndex TasksModel::index(TasksModelItem *item, int column) const
{
    if (!item || item == m_rootItem) {
        return {};
    }

    const TasksModelItem *par = item->parent();
    auto *itm = const_cast<TasksModelItem *>(item);
    if (!par) {
        par = m_rootItem;
    }
    int row = par->m_children.lastIndexOf(itm);
    return createIndex(row, column, itm);
}

QModelIndex TasksModel::index(int row, int column, const QModelIndex &parent) const
{
    int c = columnCount(parent);
    if (row < 0 || column < 0 || column >= c) {
        return {};
    }

    TasksModelItem *parentItem = parent.isValid() ? item(parent) : m_rootItem;
    if (parentItem && row < parentItem->childCount()) {
        TasksModelItem *itm = parentItem->child(row);
        if (itm) {
            return createIndex(row, column, itm);
        }

        return {};
    }

    return {};
}

QModelIndex TasksModel::parent(const QModelIndex &child) const
{
    if (!child.isValid()) {
        return {};
    }

    auto *item = static_cast<TasksModelItem *>(child.internalPointer());
    if (!item || item == m_rootItem) {
        return {};
    }

    TasksModelItem *parent = item->parent();
    return index(parent, 0);
}

int TasksModel::rowCount(const QModelIndex &parent) const
{
    if (!parent.isValid()) {
        return m_rootItem->childCount();
    }

    TasksModelItem *parentItem = item(parent);
    if (parentItem) {
        return parentItem->childCount();
    }

    return 0;
}

int TasksModel::columnCount(const QModelIndex & /*parent*/) const
{
    return m_headerLabels.size();
}

QVariant TasksModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid()) {
        return {};
    }

    switch (role) {
    case Qt::TextAlignmentRole:
        // Align task name: left
        // Align priority: center
        // Align HH:MM: right
        if (index.column() == 5) {
            return Qt::AlignCenter;
        } else if (index.column() >= 1) {
            return {Qt::AlignRight | Qt::AlignVCenter};
        }
        break;
    case Qt::WhatsThisRole:
        if (index.column() == 0) {
            return i18nc("@info:whatsthis",
                         "The task name is what you call the task, it can be "
                         "chosen freely.");
        } else if (index.column() == 1) {
            return i18nc("@info:whatsthis",
                         "The session time is the time since you last chose "
                         "\"Start New Session\".");
        }
        break;
    case Qt::DecorationRole: {
        auto *task = dynamic_cast<Task *>(item(index));
        if (!task) {
            return {};
        }

        if (index.column() == 0) {
            return QPixmap(task->isComplete() ? QStringLiteral(":/pics/task-complete.xpm") : QStringLiteral(":/pics/task-incomplete.xpm"));
        } else if (index.column() == 1) {
            return task->isRunning() ? m_clockAnimation->currentPixmap() : QPixmap(QStringLiteral(":/pics/empty-watch.xpm"));
        }
        break;
    }
    default: {
        TasksModelItem *itm = item(index);
        if (itm) {
            return itm->data(index.column(), role);
        }
        break;
    }
    }

    return {};
}

QList<TasksModelItem *> TasksModel::getAllItems()
{
    QList<TasksModelItem *> res;

    QStack<TasksModelItem *> stack;
    stack.push(m_rootItem);
    while (!stack.isEmpty()) {
        TasksModelItem *item = stack.pop();
        if (item != m_rootItem) {
            res.append(item);
        }

        for (int c = 0; c < item->m_children.count(); ++c) {
            stack.push(item->m_children.at(c));
        }
    }

    return res;
}

QList<Task *> TasksModel::getAllTasks()
{
    QList<Task *> tasks;
    for (TasksModelItem *item : getAllItems()) {
        // If "item" is not a Task, then we are probably in the middle
        // of class Task or TasksModelItem destructor.
        Task *task = dynamic_cast<Task *>(item);
        if (task) {
            tasks.append(task);
        }
    }

    return tasks;
}

QList<Task *> TasksModel::getActiveTasks()
{
    QList<Task *> activeTasks;
    for (Task *task : getAllTasks()) {
        if (task->isRunning()) {
            activeTasks.append(task);
        }
    }

    return activeTasks;
}

QVariant TasksModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (section < 0 || orientation != Qt::Horizontal || section >= m_headerLabels.size()) {
        return {};
    }

    switch (role) {
    case Qt::DisplayRole:
        return m_headerLabels[section];
    case Qt::WhatsThisRole:
        switch (section) {
        case 0:
            return i18nc("@info:whatsthis",
                         "The task name is what you call the task, it can be "
                         "chosen freely.");
        case 1:
            return i18nc("@info:whatsthis",
                         "The session time is the time since you last chose "
                         "\"Start New Session\".");
        case 3:
            return i18nc("@info:whatsthis",
                         "The total session time is the session time of this "
                         "task and all its subtasks.");
        case 4:
            return i18nc("@info:whatsthis",
                         "The total time is the time of this task and all its "
                         "subtasks.");
        default:
            break;
        }
        break;
    default:
        break;
    }

    return QAbstractItemModel::headerData(section, orientation, role);
}

void TasksModel::setActiveIcon(int /*unused*/)
{
    // TODO: only touch those tasks that are currently running
    for (auto *task : getAllItems()) {
        task->invalidateRunningState();
    }
}

void TasksModel::addChild(TasksModelItem *item)
{
    m_rootItem->addChild(item);
}

Qt::ItemFlags TasksModel::flags(const QModelIndex &index) const
{
    if (!index.isValid()) {
        // Allow move to top-level
        return Qt::ItemIsDropEnabled;
    }

    return QAbstractItemModel::flags(index) | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
}

Qt::DropActions TasksModel::supportedDropActions() const
{
    return Qt::MoveAction;
}

bool TasksModel::canDropMimeData(const QMimeData *data,
                                 Qt::DropAction action,
                                 int row,
                                 int column,
                                 const QModelIndex &parent) const
{
    if (m_dragCutTaskId.isEmpty()) {
        return false;
    }

    return QAbstractItemModel::canDropMimeData(data, action, row, column, parent);
}

bool TasksModel::dropMimeData(const QMimeData *data,
                              Qt::DropAction action,
                              int row,
                              int column,
                              const QModelIndex &parent)
{
    if (!canDropMimeData(data, action, row, column, parent)) {
        return false;
    }

    if (action == Qt::IgnoreAction) {
        return true;
    }

    auto *task = taskByUID(m_dragCutTaskId);
    if (!task) {
        return false;
    }

    // If have parent, move to subtasks of the parent task.
    // If don't have parent, move to top-level tasks.
    TasksModelItem *newParent = parent.isValid() ? item(parent) : m_rootItem;
    if (!newParent) {
        return false;
    }

    task->move(newParent);
    Q_EMIT taskDropped();
    return true;
}

QMimeData *TasksModel::mimeData(const QModelIndexList &indexes) const
{
    auto *task = dynamic_cast<Task *>(item(indexes[0]));
    if (!task) {
        return nullptr;
    }

    m_dragCutTaskId = task->uid();
    return QAbstractItemModel::mimeData(indexes);
}

Task *TasksModel::taskByUID(const QString &uid)
{
    for (auto *item : getAllItems()) {
        auto *task = dynamic_cast<Task *>(item);
        if (task->uid() == uid) {
            return task;
        }
    }

    return nullptr;
}

void TasksModel::startNewSession()
{
    for (Task *task : getAllTasks()) {
        task->startNewSession();
    }
}

void TasksModel::addTimeToActiveTasks(int64_t minutes)
{
    for (Task *task : getActiveTasks()) {
        task->changeTime(minutes, nullptr);
    }
}

#include "moc_tasksmodel.cpp"
