#include "patternsequencermodel.h"
using Xybrid::UI::PatternSequencerModel;

#include <QMimeData>

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

#include "mainwindow.h"

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

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

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

int PatternSequencerModel::columnCount(const QModelIndex &parent [[maybe_unused]]) const {
    auto* project = window->getProject().get();
    if (!project) return 0;
    return static_cast<int>(window->getProject()->sequence.size() + 1);
}

QVariant PatternSequencerModel::data(const QModelIndex &index, int role) const {
    auto* project = window->getProject().get();
    if (!project) return QVariant();
    if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
        bool toolTip = (role == Qt::ToolTipRole);
        if (static_cast<size_t>(index.column()) >= project->sequence.size()) return !toolTip ? QString("+") : QString("Add new");
        auto& e = project->sequence[static_cast<size_t>(index.column())];
        return toolTip ? e.toolTip() : e.symbol();
        /*auto& pattern = project->sequence[static_cast<size_t>(index.column())];
        if (!pattern) return !toolTip ? QString("-") : QString("(separator)");
        if (!toolTip) return QString("%1").arg(pattern->index, 1, 10, QChar('0'));
        if (pattern->name.isEmpty()) return QVariant(); // no tool tip without name
        return QString("(%1) %2").arg(pattern->index, 1, 10, QChar('0')).arg(pattern->name);*/
    }
    if (role == Qt::TextAlignmentRole ) return Qt::AlignHCenter + Qt::AlignVCenter;
    return QVariant();
}

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

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

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

QMimeData* PatternSequencerModel::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].column());
    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-sequence-index", dd);
    return d;
}

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

        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].get();
        }

        if (parent.isValid()) column = parent.column(); // always insert before
        if (column < 0 || column > static_cast<int>(p->project->sequence.size())) column = static_cast<int>(p->project->sequence.size()); // if dropped on empty space, snap to end

        auto* c = new ProjectSequencerDeltaCommand(window->getProject());
        c->seq.insert(c->seq.begin() + column, p->shared_from_this());
        c->seqSel = column;

        return c->commit();
    }
    if (data->hasFormat("xybrid-internal/x-sequence-index")) {
        if (action == Qt::IgnoreAction) return true; // can accept type
        bool copy = (action == Qt::CopyAction);

        size_t idx;
        Project* prj;
        {
            QByteArray dd = data->data("xybrid-internal/x-sequence-index");
            QDataStream stream(&dd, QIODevice::ReadOnly);
            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->sequence.size()) return false; // index out of range
        }

        if (parent.isValid()) { // if dropped onto an item and not between, place on opposite side
            column = parent.column();
            if (column > static_cast<int>(idx)) column += 1;
        }
        if (column < 0 || column > static_cast<int>(prj->sequence.size())) column = static_cast<int>(prj->sequence.size()); // if dropped on empty space, snap to end

        if (!copy && column > static_cast<int>(idx)) column -= 1; // compensate ahead of time for snap-out

        auto* c = new ProjectSequencerDeltaCommand(window->getProject());
        SequenceEntry s = c->seq[idx];
        if (!copy) c->seq.erase(c->seq.begin() + static_cast<int>(idx));
        c->seq.insert(c->seq.begin() + column, s);
        c->seqSel = column;

        return c->commit();
    }
    return false;



}

