/**
 * @file SourceSidebar.cpp
 */

#include "SourceSidebar.h"
#include "Hub.h"
#include "Theme.h"

#include <QVBoxLayout>
#include <QTreeWidget>
#include <QHeaderView>
#include <QPushButton>
#include <QMenu>
#include <QMimeData>
#include <QDrag>
#include <QPainter>
#include <QPixmap>
#include <QIcon>
#include <QSet>
#include <QDataStream>
#include <QApplication>

namespace shq {

const char* const kMimeSignal = "application/x-shq-signal";

/* Roles for tree items. */
enum { RoleSrcIdx = Qt::UserRole + 1, RoleSigIdx, RoleSrcId };

/** QTreeWidget subclass that emits a signal drag with the qint32[2] payload. */
class SourceTree : public QTreeWidget {
public:
    explicit SourceTree(QWidget* parent = nullptr) : QTreeWidget(parent) {
        setDragEnabled(true);
        setDragDropMode(QAbstractItemView::DragOnly);
    }
protected:
    void startDrag(Qt::DropActions /*supported*/) override {
        QTreeWidgetItem* it = currentItem();
        if (!it) { return; }
        bool ok = false;
        int sigIdx = it->data(0, RoleSigIdx).toInt(&ok);
        if (!ok || sigIdx < 0) { return; }   /* only signal leaves drag */
        int srcIdx = it->data(0, RoleSrcIdx).toInt();

        QByteArray payload;
        {
            QDataStream ds(&payload, QIODevice::WriteOnly);
            ds.setByteOrder(QDataStream::LittleEndian);
            ds << static_cast<qint32>(srcIdx) << static_cast<qint32>(sigIdx);
        }
        auto* mime = new QMimeData;
        mime->setData(kMimeSignal, payload);

        auto* drag = new QDrag(this);
        drag->setMimeData(mime);
        drag->exec(Qt::CopyAction);
    }
};

SourceSidebar::SourceSidebar(Hub* hub, QWidget* parent)
    : QWidget(parent), hub_(hub) {
    auto* lay = new QVBoxLayout(this);
    lay->setContentsMargins(4, 4, 4, 4);
    lay->setSpacing(4);

    tree_ = new SourceTree(this);
    tree_->setHeaderHidden(true);
    tree_->setColumnCount(1);
    tree_->setContextMenuPolicy(Qt::CustomContextMenu);
    tree_->setIndentation(12);
    connect(tree_, &QTreeWidget::customContextMenuRequested,
            this, &SourceSidebar::onContextMenu);
    lay->addWidget(tree_, 1);

    auto* addBtn = new QPushButton("+ Add Source", this);
    connect(addBtn, &QPushButton::clicked, this,
            &SourceSidebar::addSourceRequested);
    lay->addWidget(addBtn);

    auto* saveBtn = new QPushButton("Save Sources", this);
    connect(saveBtn, &QPushButton::clicked, this,
            [this]() { hub_->sendSaveSources(); });
    lay->addWidget(saveBtn);

    refresh();
}

/** Small square color swatch icon. */
static QIcon swatch(const QColor& c) {
    QPixmap pm(12, 12);
    pm.fill(Qt::transparent);
    QPainter p(&pm);
    p.setPen(Qt::NoPen);
    p.setBrush(c);
    p.drawRect(0, 0, 12, 12);
    return QIcon(pm);
}

void SourceSidebar::refresh() {
    /* Preserve expansion state by source id. */
    QSet<QString> expanded;
    for (int i = 0; i < tree_->topLevelItemCount(); i++) {
        QTreeWidgetItem* it = tree_->topLevelItem(i);
        if (it->isExpanded()) {
            expanded.insert(it->data(0, RoleSrcId).toString());
        }
    }

    tree_->clear();
    const auto& sources = hub_->sources();
    for (int s = 0; s < static_cast<int>(sources.size()); s++) {
        const Source& src = sources[s];
        QString label = QString::fromStdString(
            src.label.empty() ? src.id : src.label);
        bool connected = (src.state == "connected");
        QString dot = connected ? QString::fromUtf8("\u25cf ")
                                : QString::fromUtf8("\u25cb ");
        auto* top = new QTreeWidgetItem(tree_);
        top->setText(0, dot + label);
        top->setForeground(0, connected ? col::green() : col::red());
        top->setData(0, RoleSrcIdx, s);
        top->setData(0, RoleSigIdx, -1);
        top->setData(0, RoleSrcId, QString::fromStdString(src.id));
        top->setFlags(Qt::ItemIsEnabled);

        for (int g = 0; g < static_cast<int>(src.signals.size()); g++) {
            const SignalView& sig = src.signals[g];
            QString name = QString::fromStdString(sig.meta.name);
            if (sig.meta.numElements > 1) {
                name += QString(" [%1]").arg(sig.meta.numElements);
            }
            if (!sig.meta.unit.empty()) {
                name += " (" + QString::fromStdString(sig.meta.unit) + ")";
            }
            auto* leaf = new QTreeWidgetItem(top);
            leaf->setText(0, name);
            leaf->setIcon(0, swatch(sig.color));
            leaf->setData(0, RoleSrcIdx, s);
            leaf->setData(0, RoleSigIdx, g);
            leaf->setData(0, RoleSrcId, QString::fromStdString(src.id));
            leaf->setForeground(0, col::text());
            leaf->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable |
                           Qt::ItemIsDragEnabled);
        }

        if (expanded.isEmpty() ||
            expanded.contains(QString::fromStdString(src.id))) {
            top->setExpanded(true);
        }
    }
}

void SourceSidebar::onContextMenu(const QPoint& pos) {
    QTreeWidgetItem* it = tree_->itemAt(pos);
    if (!it) { return; }
    QString srcId = it->data(0, RoleSrcId).toString();
    if (srcId.isEmpty()) { return; }

    QMenu menu(this);
    QAction* rm = menu.addAction("Remove source");
    if (menu.exec(tree_->viewport()->mapToGlobal(pos)) == rm) {
        hub_->sendRemoveSource(srcId.toStdString());
    }
}

} /* namespace shq */
