#include "patterneditoritemdelegate.h"
using Xybrid::UI::PatternEditorItemDelegate;

#include "config/colorscheme.h"
using Xybrid::Config::ColorScheme;

#include "ui/patterneditorview.h"
using Xybrid::UI::PatternEditorView;
#include "ui/patterneditormodel.h"
using Xybrid::UI::PatternEditorModel;

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

#include "audio/audioengine.h"
using namespace Xybrid::Audio;

#include <limits>

#include <QDebug>
#include <QPainter>
#include <QKeyEvent>

namespace {
    constexpr int pad = PatternEditorModel::cellPadding;

    constexpr int pianoKeys[] = {
        Qt::Key_Q, Qt::Key_W, Qt::Key_E, Qt::Key_R, Qt::Key_T, Qt::Key_Y, Qt::Key_U, Qt::Key_I, Qt::Key_O, Qt::Key_P, Qt::Key_BracketLeft, Qt::Key_BracketRight,
        Qt::Key_A, Qt::Key_S, Qt::Key_D, Qt::Key_F, Qt::Key_G, Qt::Key_H, Qt::Key_J, Qt::Key_K, Qt::Key_L, Qt::Key_Semicolon, Qt::Key_Apostrophe, Qt::Key_Backslash,
        Qt::Key_Z, Qt::Key_X, Qt::Key_C, Qt::Key_V, Qt::Key_B, Qt::Key_N, Qt::Key_M, Qt::Key_Comma, Qt::Key_Period, Qt::Key_Slash,
    };
    constexpr int numberKeys[] = {
        Qt::Key_0, Qt::Key_1, Qt::Key_2, Qt::Key_3, Qt::Key_4, Qt::Key_5, Qt::Key_6, Qt::Key_7, Qt::Key_8, Qt::Key_9,
        Qt::Key_A, Qt::Key_B, Qt::Key_C, Qt::Key_D, Qt::Key_E, Qt::Key_F,
    };

    const std::unordered_map<int, int> keyConv = [] {
        std::unordered_map<int, int> m;

        m[Qt::Key_BraceLeft] = Qt::Key_BracketLeft;
        m[Qt::Key_BraceRight] = Qt::Key_BracketRight;
        m[Qt::Key_Bar] = Qt::Key_Backslash;
        m[Qt::Key_Colon] = Qt::Key_Semicolon;
        m[Qt::Key_QuoteDbl] = Qt::Key_Apostrophe;
        m[Qt::Key_Less] = Qt::Key_Comma;
        m[Qt::Key_Greater] = Qt::Key_Period;
        m[Qt::Key_Question] = Qt::Key_Slash;

        return m;
    }();

    template <typename T>
    [[maybe_unused]] void insertDigit(T& val, size_t hex) { // insert hex digit into a particular value
        if (static_cast<int>(val) < 0) val = 0;
        val = static_cast<T>((static_cast<size_t>(val) & 15) * 16 + (hex & 15));
    }

    struct SelectionBounds {
        int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
        int ch1, ch2;
        SelectionBounds(const QModelIndexList& sel) {
            x1 = std::numeric_limits<int>::max();
            y1 = std::numeric_limits<int>::max();
            for (auto s : sel) {
                x1 = std::min(x1, s.column());
                y1 = std::min(y1, s.row());
                x2 = std::max(x2, s.column());
                y2 = std::max(y2, s.row());
            }
            ch1 = (x1 - (x1 % PatternEditorModel::colsPerChannel)) / PatternEditorModel::colsPerChannel;
            ch2 = (x2 - (x2 % PatternEditorModel::colsPerChannel)) / PatternEditorModel::colsPerChannel;
        }

        [[maybe_unused]] bool portSelected(int c) {
            int cx = c * PatternEditorModel::colsPerChannel;
            return (cx >= x1 && cx <= x2);
        }
        [[maybe_unused]] bool noteSelected(int c) {
            int cx = (c * PatternEditorModel::colsPerChannel) + 1;
            return (cx >= x1 && cx <= x2);
        }
        [[maybe_unused]] bool paramSelected(int c, int p) {
            int cx = (c * PatternEditorModel::colsPerChannel) + 2 + (p*2);
            return (cx+1 >= x1 && cx <= x2);
        }
        [[maybe_unused]] int maxParamSelected(int c) {
            return std::min((x2 - (c * PatternEditorModel::colsPerChannel) - 2) / 2, PatternEditorModel::paramSoftCap);
        }
    };
}

void PatternEditorItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
    int row = index.row() - 1;
    auto mdl = const_cast<PatternEditorModel*>(static_cast<const PatternEditorModel*>(index.model()));
    auto p = mdl->getPattern();
    { /* background */ } {
        painter->fillRect(option.rect, Config::colorScheme.patternBg);
        if (row == -1 || row >= p->rows) painter->fillRect(option.rect, Config::colorScheme.patternOuterShade);
        else if (p->fold > 1 && row % p->fold != 0) painter->fillRect(option.rect, Config::colorScheme.patternFoldShade);
        if (option.state & QStyle::State_Enabled) {
            if (row % p->time.rowsPerMeasure() == 0) painter->fillRect(option.rect, Config::colorScheme.patternBgMeasure);
            else if (row % p->time.rowsPerBeat == 0) painter->fillRect(option.rect, Config::colorScheme.patternBgBeat);
        }
    }

    // selection/cursor highlight
    if (option.state & QStyle::State_Selected) painter->fillRect(option.rect, Config::colorScheme.patternSelection);
    if (option.state & QStyle::State_HasFocus && p->channels.size() > 0) {
        painter->setPen(Config::colorScheme.patternSelection);
        painter->drawRect(option.rect.adjusted(0,0,-1,-1));
        painter->drawRect(option.rect.adjusted(0,0,-1,-1));
        painter->drawRect(option.rect.adjusted(1,1,-2,-2));
        //painter->fillRect(option.rect, Config::colorScheme.patternSelection);
    }

    if (row > 0 && mdl->folded && p->fold > 1) {
        bool show = false;

        int cc = index.column() % PatternEditorModel::colsPerChannel;
        int ch = (index.column() - cc) / PatternEditorModel::colsPerChannel;
        if (ch < static_cast<int>(p->numChannels())) {
            int rs = row + 1;
            int rowEnd = rs + p->fold;
            for (int i = rs + 1; i < rowEnd; i++) {
                QString v = index.siblingAtRow(i).data(Qt::DisplayRole).toString();
                if (!(v.isEmpty() or v == " - " or v == "- " or v == "» ")) { show = true; break; }
            }
        }

        if (show) {
            painter->setPen(Config::colorScheme.patternFoldIndicator);
            painter->drawLine(QLine(option.rect.bottomLeft(), option.rect.bottomRight()).translated(0, 0));
        }
    }

    // and main data
    QString s = index.data().toString();
    auto fm = QFontMetrics(QFont("Iosevka Term Light", 9));
    int cc = index.column() % PatternEditorModel::colsPerChannel;
    int align = Qt::AlignCenter;
    if (cc > 1) { // param field
        if (cc % 2 == 0) align = Qt::AlignVCenter | Qt::AlignRight;
        else             align = Qt::AlignVCenter | Qt::AlignLeft;
    }
    if (s == QString("» ")) {
        align = Qt::AlignVCenter | Qt::AlignLeft;
        painter->setPen(Config::colorScheme.patternFgBlank);
    } else {
        if (s == QString(" - ") || s == QString("- ")) painter->setPen(Config::colorScheme.patternFgBlank);
        else if (cc == 0) painter->setPen(Config::colorScheme.patternFgPort);
        else if (cc == 1) painter->setPen(Config::colorScheme.patternFgNote);
        else if (cc % 2 == 0) painter->setPen(Config::colorScheme.patternFgParamCmd);
        else painter->setPen(Config::colorScheme.patternFgParamAmt);
    }
    painter->drawText(option.rect, align, s);
}

bool PatternEditorItemDelegate::eventFilter(QObject *obj, QEvent *event) {
    /*if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
        auto* e = static_cast<QKeyEvent*>(event);
        if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) {
            e->accept();
            return true;
        }
    }//*/
    return QObject::eventFilter(obj, event);
}

bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option [[maybe_unused]], const QModelIndex &index) {
    if (index.data().isNull()) return false; // no channels?
    auto type = event->type();
    if (type == QEvent::KeyRelease) qDebug() << "key release";
    if (type == QEvent::KeyPress) {
        if (static_cast<QKeyEvent*>(event)->isAutoRepeat()) return false; // reject autorepeat

        auto k = static_cast<QKeyEvent*>(event)->key(); // grab key
        if (auto i = keyConv.find(k); i != keyConv.end()) k = i->second;
        auto mod = static_cast<QKeyEvent*>(event)->modifiers();
        auto m = static_cast<PatternEditorModel*>(model); // we know this will always be pattern editor
        auto p = m->getPattern();
        int cc = index.column() % PatternEditorModel::colsPerChannel;
        int ch = (index.column() - cc) / PatternEditorModel::colsPerChannel;
        auto* dc = new PatternDeltaCommand(p, ch, index.row() - 1);
        auto& row = dc->row;//p->rowAt(ch, index.row());

        auto* sm = static_cast<PatternEditorView*>(parent())->selectionModel();
        auto sel = sm->selectedIndexes();
        bool multi = sel.size() > 1;

        if (mod & Qt::Modifier::CTRL) {

        } else if (mod & Qt::Modifier::ALT) {

        } else {
            if (k == Qt::Key_Space) { // TODO make this not "modify" if nothing was affected
                if (mod & Qt::Modifier::SHIFT) return dc->cancel(); // TODO: once playback is a thing, shift+space to preview row?

                dc->cancel();
                SelectionBounds s(sel);
                auto* cc = (new CompositeCommand())->reserve((1 + s.y2 - s.y1) * (1 + s.ch2 - s.ch1));

                for (int c = s.ch1; c <= s.ch2; c++) {
                    size_t mpc = 0; // max params in channel
                    for (int r = 0; r < p->rows; r++) mpc = std::max(mpc, p->rowAt(c, r).numParams());
                    auto mp = s.maxParamSelected(c);
                    if (mp < 0) continue;
                    auto mps = std::min(mpc, static_cast<size_t>(mp));
                    for (int r = s.y1; r <= s.y2; r++) {
                        if (multi && mps <= p->rowAt(c, r).numParams()) continue;
                        auto* dc = new PatternDeltaCommand(p, c, r-1);
                        for (size_t i = dc->row.numParams(); i < mps; i++) dc->row.addParam(' ');
                        if (!multi) dc->row.param(mps) = {' ', 0};
                        cc->compose(dc);
                    }
                }
                return cc->commit("pattern strut");
            }
            if (multi) {
                if (k == Qt::Key_Delete) {
                    dc->cancel();
                    SelectionBounds s(sel);
                    auto* cc = (new CompositeCommand())->reserve((1 + s.y2 - s.y1) * (1 + s.ch2 - s.ch1));

                    for (int c = s.ch1; c <= s.ch2; c++) {
                        for (int r = s.y1; r <= s.y2; r++) {
                            auto* dc = new PatternDeltaCommand(p, c, r-1);
                            if (s.portSelected(c)) dc->row.port = -1;
                            if (s.noteSelected(c)) dc->row.note = -1;
                            for (int i = static_cast<int>(dc->row.numParams()) - 1; i >= 0; i--) {
                                if (s.paramSelected(c, i)) dc->row.removeParam(static_cast<size_t>(i));
                            }
                            cc->compose(dc);
                        }
                    }

                    return cc->commit("delete selection");
                }

                // for all other commands, reset selection to cursor and defer
                sm->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
            }

            if (cc == 0) { // port column
                if (k == Qt::Key_Delete) {
                    row.port = -1;
                    return dc->commit();
                }
                if (k == Qt::Key_G) { // global commands (tempo and the like)
                    row.port = -2;
                    return dc->commit();
                }
                if (k == Qt::Key_L) {
                    row.port = -3;
                    return dc->commit();
                }
                for (size_t i = 0; i < 16; i++) {
                    if (k == numberKeys[i]) {
                        insertDigit(row.port, i);
                        return dc->commit();
                    }
                }
            } else if (cc == 1) { // note column
                if (k == Qt::Key_Delete) {
                    row.note = -1;
                    return dc->commit();
                }
                if (k == Qt::Key_Equal || k == Qt::Key_QuoteLeft) { // note off
                    row.note = -2;
                    return dc->commit();
                }
                if (k == Qt::Key_Plus || k == Qt::Key_AsciiTilde) { // shift for hard cut; for some reason this is a separate keycode
                    row.note = -3;
                    return dc->commit();
                }
                for (size_t i = 0; i < (sizeof(pianoKeys) / sizeof(pianoKeys[0])); i++) {
                    if (k == pianoKeys[i]) { // piano input
                        row.note = static_cast<int16_t>(i + (12*4)); // C-4
                        if (mod & Qt::Modifier::SHIFT) row.note += 24; // shift for +2 octave
                        if (row.port == -1) { // if no port specified, default to last port used (for a note event) in channel, then (TODO) last port value applied
                            for (int i = index.row() - 1; i >= 0; i--) {
                                auto& r = p->rowAt(ch, i);
                                if (r.port >= 0 && r.note != -1) {
                                    row.port = r.port;
                                    break;
                                }
                            }
                        }
                        return dc->commit();
                    } else if (i < 10 && k == numberKeys[i]) { // set octave
                        if (row.note >= 0) row.note = static_cast<int16_t>((row.note % 12) + 12*i);
                        //static_cast<PatternEditorModel*>(model)->updateColumnDisplay();
                        return dc->commit();
                    }
                }
            } else { // param column
                size_t par = static_cast<size_t>((cc - (cc % 2)) / 2 - 1);
                if (k == Qt::Key_Insert) { // insert from within any place in the param columns
                    if (row.numParams() >= PatternEditorModel::paramSoftCap) return false; // no overruns
                    row.insertParam(par, ' ');
                    auto view = static_cast<PatternEditorView*>(parent());
                    size_t cpar = row.numParams() - 1;
                    if (cpar > par) cpar = par;
                    view->setCurrentIndex(index.siblingAtColumn(ch * PatternEditorModel::colsPerChannel + static_cast<int>(cpar) * 2 + 2));
                    return dc->commit();
                }
                if (par < row.numParams()) {
                    if (k == Qt::Key_Delete) { // remove selected parameter
                        row.removeParam(par);
                        if (par >= row.numParams()) { // snap to arrow if beyond
                            auto view = static_cast<PatternEditorView*>(parent());
                            view->setCurrentIndex(index.siblingAtColumn(ch * PatternEditorModel::colsPerChannel + static_cast<int>(row.numParams()) * 2 + 2));
                        }
                        return dc->commit();
                    }
                    if (k == Qt::Key_Delete || k == Qt::Key_Insert || k == Qt::Key_Backspace) return dc->cancel();
                    if (cc % 2 == 0) { // char column; set to key pressed and move forward
                        char chr = static_cast<QKeyEvent*>(event)->text().toUtf8()[0];
                        row.param(par)[0] = static_cast<unsigned char>(chr);
                        auto view = static_cast<PatternEditorView*>(parent());
                        view->setCurrentIndex(index.siblingAtColumn(index.column()+1));
                        return dc->commit();
                    } else {
                        if (k == Qt::Key_Comma) { // convenience; allow inserting an extend from number column
                            if (row.numParams() >= PatternEditorModel::paramSoftCap) return dc->cancel();
                            row.insertParam(par+1, ',');
                            auto view = static_cast<PatternEditorView*>(parent());
                            view->setCurrentIndex(index.siblingAtColumn(index.column()+2));
                            return dc->commit();
                        }
                        for (size_t i = 0; i < 16; i++) {
                            if (k == numberKeys[i]) {
                                insertDigit(row.param(par)[1], i);
                                return dc->commit();
                            }
                        }
                    }
                } else { // new param; set to key pressed and move forward
                    if (k == Qt::Key_Delete || k == Qt::Key_Insert || k == Qt::Key_Backspace) return false;
                    char chr = static_cast<QKeyEvent*>(event)->text().toUtf8()[0];
                    row.addParam(chr);
                    auto view = static_cast<PatternEditorView*>(parent());
                    view->setCurrentIndex(index.siblingAtColumn(ch * PatternEditorModel::colsPerChannel + static_cast<int>(row.numParams()) * 2 + 1));
                    return dc->commit();
                }
            }
        }
        // kill command if unused
        dc->cancel();
    }
    return false;
}//*/

QSize PatternEditorItemDelegate::sizeHint(const QStyleOptionViewItem &option [[maybe_unused]], const QModelIndex &index) const {
    if (index.data().isNull()) return QSize(0, 0);
    auto fm = QFontMetrics(QFont("Iosevka Term Light", 9));
    int cc = index.column() % PatternEditorModel::colsPerChannel;
    std::string s = "FF";
    if (cc == 1) s = "C#2";
    else if (cc > 1 && cc % 2 == 0) s = "v";
    /*if (index.data().toString() == QString("» ")) {
        return fm.boundingRect(QString("»")).size() + QSize(pad*2,0);
    }//*/
    if (cc > 1) {
        return fm.boundingRect(QString::fromStdString(s)).size() + QSize(pad,0); // only one padding on params
    }
    return fm.boundingRect(QString::fromStdString(s)).size() + QSize(pad*2,0);
    //return QSize(24, 16);
}
