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

#include <QDebug>
#include <QString>
#include <QFontMetrics>

namespace { // helper functions
    int cellWidthBase = -1;
    int cellWidthParam;
    int cellWidthParamTab;
    int headerHeight;

    constexpr char hexmap[] = {'0', '1', '2', '3', '4', '5', '6', '7',
                               '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
                              };

    constexpr char notemap[] = "C-C#D-D#E-F-F#G-G#A-A#B-";

    std::string hexStr(unsigned char *data, uint len) {
        std::string s(len * 2, ' ');
        for (uint i = 0; i < len; ++i) {
            s[2 * i]     = hexmap[(data[i] & 0xF0) >> 4];
            s[2 * i + 1] = hexmap[data[i] & 0x0F];
        }
        return s;
    }
    std::string byteStr(int t) {
        unsigned char c = static_cast<unsigned char>(t & 255);
        return hexStr(&c, 1);
    }

    std::string noteStr(int n) {
        std::string s(3, ' ');
        int nn = n % 12;
        int oc = (n - nn) / 12;
        s[2] = '0' + static_cast<char>(oc);
        s[0] = notemap[nn*2];
        s[1] = notemap[nn*2+1];
        return s;
    }
}

PatternEditorHeaderProxyModel::PatternEditorHeaderProxyModel(QObject *parent, PatternEditorModel* p) : QAbstractTableModel(parent), pm(p) { }
int PatternEditorHeaderProxyModel::rowCount(const QModelIndex&) const {
    return 1;
}
int PatternEditorHeaderProxyModel::columnCount(const QModelIndex&) const {
    return pm->hdrColumnCount();
}
QVariant PatternEditorHeaderProxyModel::data(const QModelIndex&, int) const {
    return QVariant();
}
QVariant PatternEditorHeaderProxyModel::headerData(int section, Qt::Orientation orientation, int role) const {
    return pm->hdrData(section, orientation, role);
}

PatternEditorModel::PatternEditorModel(QObject *parent)
    :QAbstractTableModel(parent) {
    hprox = new PatternEditorHeaderProxyModel(parent, this);
}

int PatternEditorModel::rowCount(const QModelIndex & /*parent*/) const {
    //if (pattern->channels.size() == 0) return 1;
    return pattern->rows;
}

int PatternEditorModel::columnCount(const QModelIndex & /*parent*/) const {
    if (pattern->channels.size() == 0) return 1;
    return colsPerChannel * static_cast<int>(pattern->channels.size()) + 1;
}

QVariant PatternEditorModel::data(const QModelIndex &index, int role) const {
    if (index.column() >= colsPerChannel * static_cast<int>(pattern->channels.size())) return QVariant();
    if (role == Qt::DisplayRole) {
        int cc = index.column() % colsPerChannel;
        int ch = (index.column() - cc) / colsPerChannel;
        auto& row = pattern->rowAt(ch, index.row());
        if (cc == 0) { // port
            if (row.port >= 0 && row.port < 256) return QString::fromStdString(byteStr(row.port));
            if (row.port == -2) return QString("(G)");
            return QString(" - ");
        } else if (cc == 1) { // note
            if (row.note >= 0) return QString::fromStdString(noteStr(row.note));
            if (row.note == -2) return QString(" ^ "); // note off
            if (row.note == -3) return QString(" x "); // hard cut
            return QString(" - ");
        } else {
            size_t cp = static_cast<size_t>(((cc - 2) - (cc % 2)) / 2);
            //return QString::number((cp));
            if (cc % 2 == 0) {
                if (row.numParams() > cp) return QString::fromStdString(std::string(1,static_cast<char>(row.params->at(cp)[0])));
                if (row.numParams() == cp) return QString("» ");
                return QString("");
            }
            if (row.numParams() > cp) {
                if (row.params->at(cp)[0] == ' ') return QString("- ");
                return QString::fromStdString(byteStr(row.params->at(cp)[1]));
            }
            return QString("");
            //return QString("--");
        }
    }
    return QVariant();
}

QVariant PatternEditorModel::headerData(int section, Qt::Orientation orientation, int role) const {
    if (role == Qt::DisplayRole) {
        if (orientation == Qt::Orientation::Horizontal) return QVariant(); // blank actual-header
        return QString::number(section);
    } else if (role == Qt::SizeHintRole) {
        auto fm = QFontMetrics(QFont(/*"Iosevka Term Light", 9*/));
        if (orientation == Qt::Orientation::Vertical) return fm.boundingRect("127").size() + QSize(4, 4);
        return QSize(0, fm.height() + 4);
    } else if (role == Qt::TextAlignmentRole) return Qt::AlignCenter;
    return QVariant();
}

Qt::ItemFlags PatternEditorModel::flags(const QModelIndex &index) const {
    if (index.column() >= colsPerChannel * static_cast<int>(pattern->channels.size())) {
        return QAbstractTableModel::flags(index) & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
    }
    return QAbstractTableModel::flags(index);
}

int PatternEditorModel::hdrColumnCount() const {
    return static_cast<int>(pattern->channels.size()) + 1;
}
QVariant PatternEditorModel::hdrData(int section, Qt::Orientation, int role) const {
    if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
        if (pattern->channels.size() == 0) return QString("(no channels)");
        if (static_cast<size_t>(section) >= pattern->channels.size()) return QString("");
        auto n = QString::fromStdString(pattern->channels.at(static_cast<size_t>(section)).name);
        //if (n.length() == 0) return QString("(Channel %1)").arg(section);
        if (n.length() == 0) return QString("(ch%1)").arg(section);
        return n;
    } else if (role == Qt::SizeHintRole) {
        if (cellWidthBase <= 0) {
            auto fm = QFontMetrics(QFont("Iosevka Term Light", 9));
            headerHeight = fm.height()+4;
            cellWidthBase = fm.width(QString("FF")) + fm.width(QString("C#2")) + cellPadding*4;
            cellWidthParamTab = fm.width(QString("v")) + cellPadding;
            cellWidthParam = cellWidthParamTab + fm.width(QString("FF")) + cellPadding;
        }/*
        auto& c = pattern->channels.at(static_cast<size_t>(section));
        size_t maxParams = 0;
        for (auto& r : c.rows) {
            if (r.numParams() > maxParams) maxParams = r.numParams();
        }
        int width = cellWidthBase;
        width += static_cast<int>(maxParams) * cellWidthParam;
        if (maxParams < paramSoftCap) width += cellWidthParamTab;
        return QSize(width, headerHeight);*/
        return QSize(0, headerHeight);
    }
    return QVariant();
}

void PatternEditorModel::setPattern(const std::shared_ptr<Pattern>& pattern) {
    if (this->pattern == pattern) return;
    this->pattern = pattern;
    refresh();
}

void PatternEditorModel::updateColumnDisplay() {
    /*static int qi = 0;
    qDebug() << QString("column display request #%1").arg(qi++);//*/
    if (pattern == nullptr) return;
    auto view = static_cast<PatternEditorView*>(parent());
    auto fm = QFontMetrics(QFont("Iosevka Term Light", 9));
    for (size_t ch = 0; ch < pattern->channels.size(); ch++) {
        auto& c = pattern->channels.at(ch);
        size_t maxParams = 0;
        for (auto& r : c.rows) {
            if (r.numParams() > maxParams) maxParams = r.numParams();
        }
        int lastShown = 0;
        int chWidth = 0;
        for (size_t i = 0; i < PatternEditorModel::colsPerChannel; i++) {
            auto col = static_cast<int>(ch*PatternEditorModel::colsPerChannel+i);
            if (i < 3+2*maxParams) {
                view->setColumnHidden(col, false);
                view->resizeColumnToContents(col);
                lastShown = col;
                chWidth += view->columnWidth(col);
            } else view->setColumnHidden(col, true);
            //view->setColumnWidth(col, 1);
        }
        int minWidth = 0;
        if (fitHeaderToName) { // ensure exact fit
            QStyleOptionHeader opt;
            opt.initFrom(view->horizontalHeader());
            opt.state = QStyle::State_None | QStyle::State_Raised | QStyle::State_Horizontal;
            opt.orientation = Qt::Horizontal;
            opt.section = 0;
            opt.fontMetrics = view->horizontalHeader()->fontMetrics();
            opt.text = QString::fromStdString(c.name);
            minWidth = view->horizontalHeader()->style()->sizeFromContents(QStyle::CT_HeaderSection, &opt, QSize(), view->horizontalHeader()).width();
        }
        int lsw = view->columnWidth(lastShown);
        view->setColumnWidth(lastShown, std::max(lsw + 3, minWidth - (chWidth - lsw)));
    }
    view->updateHeader(true);
}
