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

#include "util/strings.h"

#include "ui/channelheaderview.h"
using Xybrid::UI::ChannelHeaderView;

#include "data/project.h"
using Xybrid::Data::Project;
using Xybrid::Data::Pattern;

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

#include <QKeyEvent>
#include <QDebug>

#include <QHeaderView>
#include <QScrollBar>
#include <QHBoxLayout>
#include <QMenu>
#include <QTextEdit>
#include <QInputDialog>
#include <QMessageBox>


namespace {
    std::shared_ptr<Pattern> pt = std::make_shared<Pattern>(1, 0); // fallback pattern
}

PatternEditorView::PatternEditorView(QWidget *parent) : QTableView(parent) {
    hdr.reset(new ChannelHeaderView(this));
    hdr->setSectionResizeMode(QHeaderView::Fixed);
    hdr->setTextElideMode(Qt::ElideNone);//Middle);
    hdr->setStretchLastSection(true);
    hdr->setSectionsMovable(true);
    hdr->setFirstSectionMovable(true);
    hdr->setEditTriggers(EditTrigger::DoubleClicked);

    // hook up scrolling to update header position
    connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, &PatternEditorView::updateHeaderOffset);
    // and header swap
    connect(hdr.get(), &QHeaderView::sectionMoved, this, &PatternEditorView::headerMoved);

    // hook up context menu
    hdr->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(hdr.get(), &QHeaderView::customContextMenuRequested, this, &PatternEditorView::headerContextMenu);

    connect(hdr.get(), &QHeaderView::sectionDoubleClicked, this, &PatternEditorView::headerDoubleClicked);

    horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);//ResizeToContents);
    horizontalHeader()->setStretchLastSection(true);
    verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
    verticalHeader()->setStretchLastSection(true);
    verticalHeader()->setSectionsClickable(false);
    setCornerButtonEnabled(false);
    //verticalHeader()->setDefaultAlignment(Qt::AlignTop);

    cornerBoxBox.reset(new QWidget(this));
    QHBoxLayout *layoutCheckBox = new QHBoxLayout(cornerBoxBox.get());
    cornerBox.reset(new QCheckBox());
    layoutCheckBox->addWidget(cornerBox.get());
    layoutCheckBox->setAlignment(Qt::AlignCenter);
    layoutCheckBox->setContentsMargins(0,0,0,0);
    cornerBox->setToolTip("Expand to fit names");
    cornerBox->setFocusPolicy(Qt::NoFocus);
    connect(cornerBox.get(), &QCheckBox::toggled, this, [this](bool state) {
        mdl->fitHeaderToName = state;
        mdl->updateColumnDisplay();
    });

    mdl.reset(new PatternEditorModel(this));
    del.reset(new PatternEditorItemDelegate(this));
    setItemDelegate(&*del);
    mdl->setPattern(pt);
    setModel(&*mdl);
    hdr->setModel(&*mdl->hprox);
}

PatternEditorView::~PatternEditorView() {
    //
    /*mdl.release();
    del.release();
    hdr.release();
    cornerBoxBox.release();
    cornerBox.release();*/
    //horizontalHeader()->deleteLater();
}

void PatternEditorView::keyPressEvent(QKeyEvent *event) {
    if (/*event->modifiers() & Qt::Modifier::CTRL &&*/ (event->key() == Qt::Key_Tab || event->key() == Qt::Key_Backtab)) { // don't block ctrl+tab
        event->ignore();
        return;
        //QKeyEvent()
    }
    if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Insert) {
        if (!edit(currentIndex(), AnyKeyPressed, event)) {
            event->ignore();
            return;
        }
    }
    QAbstractItemView::keyPressEvent(event);
}

void PatternEditorView::setPattern(const std::shared_ptr<Pattern>& pattern) {
    mdl->setPattern(pattern);
}

void PatternEditorView::updateGeometries() {
    if (mdl && colUpdateNeeded && horizontalHeader()->count() > 0) {
        mdl->updateColumnDisplay();
        colUpdateNeeded = false;
    } else updateHeader(true); // do this once on every geom update
    if (cornerBox) {
        cornerBoxBox->setGeometry(verticalHeader()->x(), horizontalHeader()->y(), verticalHeader()->width(), horizontalHeader()->height());
        //cornerBox->move(verticalHeader()->x() + verticalHeader()->width() / 2 - cornerBox->minimumWidth() / 2, horizontalHeader()->y() + cornerBox->height() / 2);
    }
    this->QTableView::updateGeometries();
}

void PatternEditorView::updateHeader(bool full) {
    auto* bh = horizontalHeader(); // base header
    if (full) {
        hdr->reset(); // force section update
        for (int i = 0; i < hdr->count(); i++) { // set sizes
            constexpr int cpc = PatternEditorModel::colsPerChannel;
            int w = 0;
            for (int j = 0; j < cpc; j++) {
                w += bh->sectionSize(i*cpc+j);
            }
            hdr->resizeSection(i, w);
        }
    }
    hdr->setGeometry(bh->x(), bh->y(), bh->width(), bh->height());
    hdr->setOffset(bh->offset());
}

void PatternEditorView::refresh() {
    mdl->refresh();
}
void PatternEditorView::updateHeaderOffset(int) {
    updateHeader(false);
}

void PatternEditorView::headerMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex) {
    if (logicalIndex == newVisualIndex) return; // assume moving back
    hdr->moveSection(newVisualIndex, logicalIndex); // maintain straight-through order
    if (logicalIndex >= hdr->count() - 1) return; // no dragging the endcap :|

    (new PatternChannelMoveCommand(mdl->getPattern(), oldVisualIndex, newVisualIndex))->commit();
}

void PatternEditorView::headerDoubleClicked(int section) {
    startRenameChannel(section);
}

void PatternEditorView::headerContextMenu(QPoint pt) {
    int idx = hdr->logicalIndexAt(pt);
    std::shared_ptr<Pattern> p = mdl->getPattern();

    QMenu* menu = new QMenu(this);
    menu->addAction("Add Channel", this, [/*this,*/ idx, p]() {
        (new PatternChannelAddCommand(p, idx))->commit();
    });
    if (idx < hdr->count() - 1) {
        menu->addAction("Delete Channel", this, [this, idx, p]() {
            if (QMessageBox::warning(this, "Are you sure?", QString("Remove channel %1 from pattern %2?").arg(Util::numAndName(idx, p->channel(idx).name)).arg(Util::numAndName(p->index, p->name)), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) return;
            (new PatternChannelDeleteCommand(p, idx))->commit();
        });
        menu->addAction("Rename Channel...", this, [this, idx, p]() {
            if (p != mdl->getPattern()) return; // swapped already
            startRenameChannel(idx);
        });
    }
    menu->setAttribute(Qt::WA_DeleteOnClose);
    menu->popup(hdr->mapToGlobal(pt));

}

void PatternEditorView::startRenameChannel(int channel) {
    auto p = mdl->getPattern();
    if (static_cast<size_t>(channel) >= p->numChannels()) return;
    auto c = &p->channel(channel);
    bool ok = false;
    auto capt = QString("Rename channel %1:").arg(channel);
    auto n = QInputDialog::getText(this, "Rename...", capt, QLineEdit::Normal, QString::fromStdString(c->name), &ok);
    if (!ok) return; // canceled
    if (p != mdl->getPattern() || c != &p->channel(channel)) return; // abort if this somehow isn't the channel it was before
    (new PatternChannelRenameCommand(p, channel, n.toStdString()))->commit();
}
