#include "patternlistmodel.h"
using Xybrid::UI::PatternListModel;

using Xybrid::Data::Project;
using Xybrid::Data::Pattern;

#include "editing/projectcommands.h"
#include "editing/patterncommands.h"
using namespace Xybrid::Editing;

#include <QDebug>
#include <QMimeData>

#include "mainwindow.h"

PatternListModel::PatternListModel(QObject *parent, MainWindow* window) : QAbstractListModel (parent) {
    this->window = window;
}

int PatternListModel::rowCount(const QModelIndex &parent [[maybe_unused]]) const {
    auto* project = window->getProject().get();
    if (!project) return 0;
    return static_cast<int>(project->patterns.size());
}

int PatternListModel::columnCount(const QModelIndex &parent [[maybe_unused]]) const {
    return 1;
}

QVariant PatternListModel::data(const QModelIndex &index, int role) const {
    auto* project = window->getProject().get();
    if (!project) return QVariant();
    if (role == Qt::DisplayRole) {
        auto pattern = project->patterns[static_cast<size_t>(index.row())];
        return QString("%1: %2").arg(pattern->index, 1, 10, QChar('0')).arg(pattern->name.isEmpty() ? "(unnamed)" : pattern->name);
    }
    if (role == Qt::EditRole) {
        return project->patterns[static_cast<size_t>(index.row())]->name;
    }
    return QVariant();
}

bool PatternListModel::setData(const QModelIndex &index, const QVariant &value, int role) {
    if (role == Qt::EditRole) {
        auto* project = window->getProject().get();
        if (!project) return true;
        auto pattern = project->patterns[static_cast<size_t>(index.row())];
        //pattern->name = value.toString().toStdString();
        return (new PatternRenameCommand(pattern, value.toString()))->commit();
    }
    return true;
}

Qt::ItemFlags PatternListModel::flags(const QModelIndex &index) const {
    return Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | QAbstractListModel::flags(index);
}

Qt::DropActions PatternListModel::supportedDropActions() const {
    return Qt::CopyAction | Qt::MoveAction;
}

QStringList PatternListModel::mimeTypes() const {
    QStringList types;
    types << "xybrid-internal/x-pattern-index";
    return types;
}

QMimeData *PatternListModel::mimeData(const QModelIndexList &indexes) const {
    auto d = new QMimeData();
    QByteArray dd;
    QDataStream stream(&dd, QIODevice::WriteOnly);
    size_t idx = static_cast<size_t>(indexes[0].row());
    Project* prj = window->getProject().get();
    if (!prj) return d; // if somehow nullptr, just return a blank
    stream.writeRawData(reinterpret_cast<char*>(&idx), sizeof(size_t));
    stream.writeRawData(reinterpret_cast<char*>(&prj), sizeof(void*));
    d->setData("xybrid-internal/x-pattern-index", dd);
    return d;
}

bool PatternListModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column [[maybe_unused]], const QModelIndex &parent [[maybe_unused]]) {
    if (!data->hasFormat("xybrid-internal/x-pattern-index")) return false;
    if (action == Qt::IgnoreAction) return true; // can accept type

    std::shared_ptr<Pattern> p;
    {
        QByteArray dd = data->data("xybrid-internal/x-pattern-index");
        QDataStream stream(&dd, QIODevice::ReadOnly);
        size_t idx;
        Project* prj;
        stream.readRawData(reinterpret_cast<char*>(&idx), sizeof(size_t));
        stream.readRawData(reinterpret_cast<char*>(&prj), sizeof(void*));
        if (prj != window->getProject().get()) return false; // wrong or invalid project
        if (idx >= prj->patterns.size()) return false; // index out of range
        p = prj->patterns[idx];
    }

    if (parent.isValid()) { // if dropped onto an item and not between, place on opposite side
        row = parent.row();
        if (row > static_cast<int>(p->index)) row += 1;
    }
    if (row < 0) row = static_cast<int>(p->project->patterns.size()); // if dropped on empty space, snap to end
    if (row > static_cast<int>(p->index)) row -= 1; // compensate ahead of time for snap-out

    return (new ProjectPatternMoveCommand(window->getProject(), static_cast<int>(p->index), row))->commit();
}
