#include "core/MainWindow.h"
#include "OverviewWidget.h"
#include "GraphWidget.h"
#include "OverviewView.h"

OverviewWidget::OverviewWidget(MainWindow *main) : CutterDockWidget(main)
{
    setWindowTitle("Graph Overview");
    setObjectName("Graph Overview");
    setAllowedAreas(Qt::AllDockWidgetAreas);
    graphView = new OverviewView(this);
    setWidget(graphView);
    targetGraphWidget = nullptr;

    connect(graphView, &OverviewView::mouseMoved, this, &OverviewWidget::updateTargetView);

    graphDataRefreshDeferrer = createRefreshDeferrer([this]() { updateGraphData(); });

    // Zoom shortcuts
    QShortcut *shortcut_zoom_in = new QShortcut(QKeySequence(Qt::Key_Plus), this);
    shortcut_zoom_in->setContext(Qt::WidgetWithChildrenShortcut);
    connect(shortcut_zoom_in, &QShortcut::activated, this, [this]() { zoomTarget(1); });
    QShortcut *shortcut_zoom_out = new QShortcut(QKeySequence(Qt::Key_Minus), this);
    shortcut_zoom_out->setContext(Qt::WidgetWithChildrenShortcut);
    connect(shortcut_zoom_out, &QShortcut::activated, this, [this]() { zoomTarget(-1); });
}

OverviewWidget::~OverviewWidget() {}

void OverviewWidget::resizeEvent(QResizeEvent *event)
{
    graphView->refreshView();
    updateRangeRect();
    QDockWidget::resizeEvent(event);
    emit resized();
}

void OverviewWidget::showEvent(QShowEvent *event)
{
    CutterDockWidget::showEvent(event);
    setUserOpened(true);
}

void OverviewWidget::closeEvent(QCloseEvent *event)
{
    CutterDockWidget::closeEvent(event);
    setUserOpened(false);
}

void OverviewWidget::setIsAvailable(bool isAvailable)
{
    if (this->isAvailable == isAvailable) {
        return;
    }
    this->isAvailable = isAvailable;
    if (!isAvailable) {
        hide();
    } else if (userOpened) {
        show();
    }
    emit isAvailableChanged(isAvailable);
}

void OverviewWidget::setUserOpened(bool userOpened)
{
    if (this->userOpened == userOpened) {
        return;
    }
    this->userOpened = userOpened;
    emit userOpenedChanged(userOpened);
}

void OverviewWidget::zoomTarget(int d)
{
    if (!targetGraphWidget) {
        return;
    }
    targetGraphWidget->getGraphView()->zoom(QPointF(0.5, 0.5), d);
}

void OverviewWidget::setTargetGraphWidget(GraphWidget *widget)
{
    if (widget == targetGraphWidget) {
        return;
    }
    if (targetGraphWidget) {
        disconnect(targetGraphWidget->getGraphView(), &DisassemblerGraphView::viewRefreshed, this,
                   &OverviewWidget::updateGraphData);
        disconnect(targetGraphWidget->getGraphView(), &DisassemblerGraphView::resized, this,
                   &OverviewWidget::updateRangeRect);
        disconnect(targetGraphWidget->getGraphView(), &GraphView::viewOffsetChanged, this,
                   &OverviewWidget::updateRangeRect);
        disconnect(targetGraphWidget->getGraphView(), &GraphView::viewScaleChanged, this,
                   &OverviewWidget::updateRangeRect);
        disconnect(targetGraphWidget, &GraphWidget::graphClosed, this,
                   &OverviewWidget::targetClosed);
    }
    targetGraphWidget = widget;
    if (targetGraphWidget) {
        connect(targetGraphWidget->getGraphView(), &DisassemblerGraphView::viewRefreshed, this,
                &OverviewWidget::updateGraphData);
        connect(targetGraphWidget->getGraphView(), &DisassemblerGraphView::resized, this,
                &OverviewWidget::updateRangeRect);
        connect(targetGraphWidget->getGraphView(), &GraphView::viewOffsetChanged, this,
                &OverviewWidget::updateRangeRect);
        connect(targetGraphWidget->getGraphView(), &GraphView::viewScaleChanged, this,
                &OverviewWidget::updateRangeRect);
        connect(targetGraphWidget, &GraphWidget::graphClosed, this, &OverviewWidget::targetClosed);
    }
    updateGraphData();
    updateRangeRect();
    setIsAvailable(targetGraphWidget != nullptr);
}

void OverviewWidget::wheelEvent(QWheelEvent *event)
{
    zoomTarget(event->angleDelta().y() / 90);
    graphView->centreRect();
}

void OverviewWidget::targetClosed()
{
    setTargetGraphWidget(nullptr);
}

void OverviewWidget::updateTargetView()
{
    if (!targetGraphWidget) {
        return;
    }
    qreal curScale = graphView->getViewScale();
    int rectx = graphView->getRangeRect().x();
    int recty = graphView->getRangeRect().y();
    int overview_offset_x = graphView->getViewOffset().x();
    int overview_offset_y = graphView->getViewOffset().y();
    QPoint newOffset;
    newOffset.rx() = rectx / curScale + overview_offset_x;
    newOffset.ry() = recty / curScale + overview_offset_y;
    targetGraphWidget->getGraphView()->setViewOffset(newOffset);
    targetGraphWidget->getGraphView()->viewport()->update();
}

void OverviewWidget::updateGraphData()
{
    if (!graphDataRefreshDeferrer->attemptRefresh(nullptr)) {
        return;
    }
    if (targetGraphWidget && !targetGraphWidget->getGraphView()->isGraphEmpty()) {
        graphView->currentFcnAddr = targetGraphWidget->getGraphView()->currentFcnAddr;
        auto &mainGraphView = *targetGraphWidget->getGraphView();
        graphView->setData(mainGraphView.getWidth(), mainGraphView.getHeight(),
                           mainGraphView.getBlocks(), mainGraphView.getEdgeConfigurations());
    } else {
        graphView->currentFcnAddr = RVA_INVALID;
        graphView->setData(0, 0, {}, {});
        graphView->setRangeRect(QRectF(0, 0, 0, 0));
    }
}

void OverviewWidget::updateRangeRect()
{
    if (targetGraphWidget) {
        qreal curScale = graphView->getViewScale();
        qreal baseScale = targetGraphWidget->getGraphView()->getViewScale();
        qreal w = targetGraphWidget->getGraphView()->viewport()->width() * curScale / baseScale;
        qreal h = targetGraphWidget->getGraphView()->viewport()->height() * curScale / baseScale;
        int graph_offset_x = targetGraphWidget->getGraphView()->getViewOffset().x();
        int graph_offset_y = targetGraphWidget->getGraphView()->getViewOffset().y();
        int overview_offset_x = graphView->getViewOffset().x();
        int overview_offset_y = graphView->getViewOffset().y();
        int rangeRectX = graph_offset_x * curScale - overview_offset_x * curScale;
        int rangeRectY = graph_offset_y * curScale - overview_offset_y * curScale;
        graphView->setRangeRect(QRectF(rangeRectX, rangeRectY, w, h));
    } else {
        graphView->setRangeRect(QRectF(0, 0, 0, 0));
    }
}
