#include "OverviewView.h"
#include <QMouseEvent>
#include <QPainter>

#include "common/Colors.h"
#include "common/Configuration.h"
#include "common/TempConfig.h"
#include "core/Iaito.h"

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#define localPos position
#endif

OverviewView::OverviewView(QWidget *parent)
    : GraphView(parent)
{
    connect(Config(), &Configuration::colorsUpdated, this, &OverviewView::colorsUpdatedSlot);
    colorsUpdatedSlot();
    grabGesture(Qt::PinchGesture);
}

void OverviewView::setData(
    int baseWidth,
    int baseHeight,
    std::unordered_map<ut64, GraphBlock> baseBlocks,
    DisassemblerGraphView::EdgeConfigurationMapping baseEdgeConfigurations,
    DisassemblerGraphView::MinimapBars baseMinimapBars)
{
    width = baseWidth;
    height = baseHeight;
    blocks = baseBlocks;
    edgeConfigurations = baseEdgeConfigurations;
    minimapBars = baseMinimapBars;
    scaleAndCenter();
    setCacheDirty();
    viewport()->update();
}

void OverviewView::centreRect()
{
    qreal w = rangeRect.width();
    qreal h = rangeRect.height();
    initialDiff = QPointF(w / 2, h / 2);
}

OverviewView::~OverviewView() {}

void OverviewView::scaleAndCenter()
{
    if (width <= 0 || height <= 0 || viewport()->width() <= 0 || viewport()->height() <= 0) {
        setViewScale(1.0);
        return;
    }

    qreal wScale = (qreal) viewport()->width() / width;
    qreal hScale = (qreal) viewport()->height() / height;
    setViewScale(std::min(wScale, hScale));
    center();
}

void OverviewView::refreshView()
{
    scaleAndCenter();
    viewport()->update();
}

void OverviewView::drawBlock(QPainter &p, GraphView::GraphBlock &block, bool interactive)
{
    Q_UNUSED(interactive)
    QRectF blockRect(block.x, block.y, block.width, block.height);

    p.setPen(graphNodeColor);
    p.setBrush(disassemblyBackgroundColor);
    p.drawRect(blockRect);
    p.setBrush(QColor(0, 0, 0, 100));
    p.drawRect(blockRect.translated(2, 2));

    // Draw basic block highlighting/tracing
    auto bb = Core()->getBBHighlighter()->getBasicBlock(block.entry);
    if (bb) {
        QColor color(bb->color);
        color.setAlphaF(0.5);
        p.setBrush(color);
    } else {
        p.setBrush(disassemblyBackgroundColor);
    }
    p.setPen(QPen(graphNodeColor, 1));
    p.drawRect(blockRect);

    auto barsIt = minimapBars.find(block.entry);
    if (barsIt != minimapBars.end()) {
        p.setPen(Qt::NoPen);
        for (const auto &bar : barsIt->second) {
            p.fillRect(bar.rect, bar.color);
        }
    }
}

void OverviewView::paintEvent(QPaintEvent *event)
{
    GraphView::paintEvent(event);
    if (rangeRect.width() == 0 && rangeRect.height() == 0) {
        return;
    }
    QPainter p(viewport());
    p.setPen(graphSelectionBorder);
    p.setBrush(graphSelectionFill);
    p.drawRect(rangeRect);
}

void OverviewView::mousePressEvent(QMouseEvent *event)
{
    mouseActive = true;
    if (rangeRect.contains(event->pos())) {
        initialDiff
            = QPointF(event->localPos().x() - rangeRect.x(), event->localPos().y() - rangeRect.y());
    } else {
        qreal w = rangeRect.width();
        qreal h = rangeRect.height();
        qreal x = event->localPos().x() - w / 2;
        qreal y = event->localPos().y() - h / 2;
        rangeRect = QRectF(x, y, w, h);
        initialDiff = QPointF(w / 2, h / 2);
        viewport()->update();
        emit mouseMoved();
    }
}

void OverviewView::mouseReleaseEvent(QMouseEvent *event)
{
    mouseActive = false;
    GraphView::mouseReleaseEvent(event);
}

void OverviewView::mouseMoveEvent(QMouseEvent *event)
{
    if (!mouseActive) {
        return;
    }
    qreal x = event->localPos().x() - initialDiff.x();
    qreal y = event->localPos().y() - initialDiff.y();
    rangeRect = QRectF(x, y, rangeRect.width(), rangeRect.height());
    viewport()->update();
    emit mouseMoved();
}

void OverviewView::wheelEvent(QWheelEvent *event)
{
    event->ignore();
}

bool OverviewView::gestureEvent(QGestureEvent *event)
{
    if (!event) {
        return false;
    }
    if (auto gesture = static_cast<QPinchGesture *>(event->gesture(Qt::PinchGesture))) {
        if (gesture->changeFlags() & QPinchGesture::ScaleFactorChanged) {
            emit pinchZoom(gesture->scaleFactor());
        }
        event->accept(gesture);
        return true;
    }
    return false;
}

GraphView::EdgeConfiguration OverviewView::edgeConfiguration(
    GraphView::GraphBlock &from, GraphView::GraphBlock *to, bool interactive)
{
    Q_UNUSED(interactive)
    EdgeConfiguration ec;
    auto baseEcIt = edgeConfigurations.find({from.entry, to->entry});
    if (baseEcIt != edgeConfigurations.end())
        ec = baseEcIt->second;
    ec.width_scale = 1.0 / getViewScale();
    return ec;
}

void OverviewView::colorsUpdatedSlot()
{
    disassemblyBackgroundColor = ConfigColor("gui.overview.node");
    graphNodeColor = ConfigColor("gui.border");
    backgroundColor = ConfigColor("gui.background");
    graphSelectionFill = ConfigColor("gui.overview.fill");
    graphSelectionBorder = ConfigColor("gui.overview.border");
    setCacheDirty();
    if (Config()->isInterfacePaletteOnlyUpdate()) {
        viewport()->update();
        return;
    }
    refreshView();
}

void OverviewView::setRangeRect(QRectF rect)
{
    rangeRect = rect;
    viewport()->update();
}
