/* This file is part of the KDE project
 * SPDX-FileCopyrightText: 2009 Pierre Stirnweiss <pstirnweiss@googlemail.com>
 *
 * SPDX-License-Identifier: LGPL-2.0-or-later
 */

#include "TrackedChangeModel.h"

#include <KoGenChange.h>
#include <KoInlineTextObjectManager.h>
#include <KoTextDocument.h>
#include <KoTextDocumentLayout.h>
#include <changetracker/KoChangeTracker.h>
#include <changetracker/KoChangeTrackerElement.h>
#include <styles/KoCharacterStyle.h>

#include <kundo2magicstring.h>

#include <QHash>
#include <QModelIndex>
#include <QStack>
#include <QTextBlock>
#include <QTextCharFormat>
#include <QTextCursor>
#include <QTextDocument>
#include <QTextFragment>

////ModelItem

ModelItem::ModelItem(ModelItem *parent)
{
    m_parentItem = parent;
    m_data.changeId = 0;
}

ModelItem::~ModelItem()
{
    qDeleteAll(m_childItems);
}

void ModelItem::setChangeId(int changeId)
{
    m_data.changeId = changeId;
}

void ModelItem::setChangeType(KoGenChange::Type type)
{
    m_data.changeType = type;
}

void ModelItem::setChangeTitle(const QString &title)
{
    m_data.title = title;
}

void ModelItem::setChangeAuthor(const QString &author)
{
    m_data.author = author;
}

void ModelItem::appendChild(ModelItem *child)
{
    m_childItems.append(child);
}

ModelItem *ModelItem::child(int row)
{
    return m_childItems.value(row);
}

QList<ModelItem *> ModelItem::children()
{
    return m_childItems;
}

int ModelItem::childCount() const
{
    return m_childItems.count();
}

ModelItem *ModelItem::parent()
{
    return m_parentItem;
}

int ModelItem::row() const
{
    if (m_parentItem)
        return m_parentItem->m_childItems.indexOf(const_cast<ModelItem *>(this));
    return 0;
}

void ModelItem::setChangeRange(int start, int end)
{
    m_data.changeRanges.append(QPair<int, int>(start, end));
}

ItemData ModelItem::itemData()
{
    return m_data;
}

void ModelItem::removeChildren()
{
    qDeleteAll(m_childItems);
    m_childItems.clear();
}

////TrackedChangeModel

TrackedChangeModel::TrackedChangeModel(QTextDocument *document, QObject *parent)
    : QAbstractItemModel(parent)
    , m_document(document)
{
    m_rootItem = new ModelItem(nullptr);
    setupModelData(m_document, m_rootItem);
}

TrackedChangeModel::~TrackedChangeModel()
{
    delete m_rootItem;
}

QModelIndex TrackedChangeModel::index(int row, int column, const QModelIndex &parent) const
{
    if (!hasIndex(row, column, parent))
        return QModelIndex();

    ModelItem *parentItem;

    if (!parent.isValid())
        parentItem = m_rootItem;
    else
        parentItem = static_cast<ModelItem *>(parent.internalPointer());

    ModelItem *childItem = parentItem->child(row);
    if (childItem)
        return createIndex(row, column, childItem);
    else
        return QModelIndex();
}

QModelIndex TrackedChangeModel::indexForChangeId(int changeId)
{
    ModelItem *item = m_changeItems.value(changeId);
    if (!item)
        return QModelIndex();
    return createIndex(item->row(), 0, item);
}

QModelIndex TrackedChangeModel::parent(const QModelIndex &index) const
{
    if (!index.isValid())
        return QModelIndex();

    ModelItem *childItem = static_cast<ModelItem *>(index.internalPointer());
    ModelItem *parentItem = childItem->parent();

    if (parentItem == m_rootItem)
        return QModelIndex();

    return createIndex(parentItem->row(), 0, parentItem);
}

int TrackedChangeModel::rowCount(const QModelIndex &parent) const
{
    ModelItem *parentItem;
    if (parent.column() > 0)
        return 0;

    if (!parent.isValid())
        parentItem = m_rootItem;
    else
        parentItem = static_cast<ModelItem *>(parent.internalPointer());

    return parentItem->childCount();
}

int TrackedChangeModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return 3;
}

ItemData TrackedChangeModel::changeItemData(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return ItemData();

    if (role != Qt::DisplayRole)
        return ItemData();

    ModelItem *item = static_cast<ModelItem *>(index.internalPointer());

    return item->itemData();
}

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

    if (role != Qt::DisplayRole)
        return QVariant();

    ModelItem *item = static_cast<ModelItem *>(index.internalPointer());

    ItemData data = item->itemData();

    switch (index.column()) {
    case 0:
        return QVariant(item->itemData().changeId);
    case 1:
        return QVariant(item->itemData().title);
    case 2:
        return QVariant(item->itemData().author);
    default:
        break;
    }
    return QVariant();
}

Qt::ItemFlags TrackedChangeModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return {};

    return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

QVariant TrackedChangeModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    Q_UNUSED(section);

    if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
        switch (section) {
        case 0:
            return QVariant(QString("changeId"));
            break;
        case 1:
            return QVariant(QString("title"));
            break;
        case 2:
            return QVariant(QString("author"));
            break;
        }
    }

    return QVariant();
}

void TrackedChangeModel::setupModel()
{
    beginRemoveRows(QModelIndex(), 0, rowCount() - 1);
    m_rootItem->removeChildren();
    endRemoveRows();
    setupModelData(m_document, m_rootItem);
    beginInsertRows(QModelIndex(), 0, m_rootItem->childCount() - 1);
    endInsertRows();
}

void TrackedChangeModel::setupModelData(QTextDocument *document, ModelItem *parent)
{
    m_changeTracker = KoTextDocument(document).changeTracker();
    m_layout = dynamic_cast<KoTextDocumentLayout *>(document->documentLayout());

    QStack<ModelItem *> itemStack;
    itemStack.push(parent);
    m_changeItems.clear();

    QTextBlock block = document->begin();
    while (block.isValid()) {
        QTextBlock::iterator it;
        for (it = block.begin(); !(it.atEnd()); ++it) {
            QTextFragment fragment = it.fragment();
            QTextCharFormat format = fragment.charFormat();
            int changeId = format.property(KoCharacterStyle::ChangeTrackerId).toInt();
            //            if (m_changeTracker->elementById(changeId) && m_changeTracker->elementById(changeId)->getChangeType() == KoGenChange::deleteChange)
            //                continue;
            if (changeId) {
                if (changeId != itemStack.top()->itemData().changeId) {
                    while (itemStack.top() != parent) {
                        if (!m_changeTracker->isParent(itemStack.top()->itemData().changeId, changeId))
                            itemStack.pop();
                        else
                            break;
                    }
                }
                ModelItem *item = m_changeItems.value(changeId);
                if (!item) {
                    item = new ModelItem(itemStack.top());
                    item->setChangeId(changeId);
                    item->setChangeType(m_changeTracker->elementById(changeId)->getChangeType());
                    item->setChangeTitle(m_changeTracker->elementById(changeId)->getChangeTitle().toString());
                    item->setChangeAuthor(m_changeTracker->elementById(changeId)->getCreator());
                    itemStack.top()->appendChild(item);
                    m_changeItems.insert(changeId, item);
                }
                item->setChangeRange(fragment.position(), fragment.position() + fragment.length());
                ModelItem *parentItem = item->parent();
                while (parentItem->itemData().changeId) {
                    parentItem->setChangeRange(fragment.position(), fragment.position() + fragment.length());
                    parentItem = parentItem->parent();
                }
                itemStack.push(item);

            } else {
                itemStack.push(parent);
            }
        }
        block = block.next();
    }
}
