// ============================================================================
//  mandelbrot_widget.cpp —— 交互与渲染调度实现（详见 .h）
// ============================================================================
#include "mandelbrot_widget.h"

#include <QHBoxLayout>
#include <QLabel>
#include <QMouseEvent>
#include <QPainter>
#include <QPushButton>
#include <QResizeEvent>
#include <QThreadPool>
#include <QThread>
#include <QVBoxLayout>
#include <QWheelEvent>

MandelbrotWidget::MandelbrotWidget(QWidget* parent)
    : QWidget(parent), pal_(256) {
    // 线程池默认按核数；切到「单线程」模式时再 setMaxThreadCount(1)。
    QThreadPool::globalInstance()->setMaxThreadCount(QThread::idealThreadCount());
    threadCount_ = QThreadPool::globalInstance()->maxThreadCount();

    auto* btnFreeze = new QPushButton(QStringLiteral("主线程渲染 (将冻结)"));
    auto* btnToggle = new QPushButton(QStringLiteral("切换 单/多线程"));
    label_ = new QLabel(QStringLiteral("就绪 · 滚轮缩放 · 左键拖拽平移"));

    auto* bar = new QHBoxLayout;
    bar->setContentsMargins(8, 4, 8, 4);
    bar->addWidget(btnFreeze);
    bar->addWidget(btnToggle);
    bar->addSpacing(16);
    bar->addWidget(label_);
    bar->addStretch();

    auto* root = new QVBoxLayout(this);
    root->setContentsMargins(0, 0, 0, 0);
    root->setSpacing(0);
    root->addStretch();     // 画布占主体（paintEvent 画整个 widget 背景，工具条浮在底部）
    root->addLayout(bar);

    connect(btnFreeze, &QPushButton::clicked, this, &MandelbrotWidget::onRenderOnMainThread);
    connect(btnToggle, &QPushButton::clicked, this, &MandelbrotWidget::onToggleThreadMode);
}

// ---- 绘图：只在 paintEvent 内对【窗口 widget】构造 QPainter（核心踩坑点 1）----
void MandelbrotWidget::paintEvent(QPaintEvent*) {
    QPainter p(this);
    if (!pixmap_.isNull()) {
        p.drawImage(0, 0, pixmap_);
    } else {
        p.fillRect(rect(), Qt::black);
    }
}

// ---- 事件处理：滚轮缩放（以鼠标点为锚，缩放后该点复坐标不变）----
void MandelbrotWidget::wheelEvent(QWheelEvent* e) {
    const int dy = e->angleDelta().y();
    if (dy == 0) { QWidget::wheelEvent(e); return; }
    const double factor = (dy > 0) ? 0.8 : 1.25;  // 上滚放大、下滚缩小

    const QPointF m = e->position();
    const double mx = vp_.centerX + (m.x() - width() / 2.0) * vp_.scale;
    const double my = vp_.centerY + (m.y() - height() / 2.0) * vp_.scale;
    vp_.scale *= factor;
    vp_.centerX = mx - (m.x() - width() / 2.0) * vp_.scale;
    vp_.centerY = my - (m.y() - height() / 2.0) * vp_.scale;
    scheduleRender();
}

// ---- 事件处理：左键拖拽平移 ----
void MandelbrotWidget::mousePressEvent(QMouseEvent* e) {
    if (e->button() == Qt::LeftButton) { dragging_ = true; lastMouse_ = e->pos(); }
    QWidget::mousePressEvent(e);
}
void MandelbrotWidget::mouseMoveEvent(QMouseEvent* e) {
    if (dragging_) {
        const QPoint d = e->pos() - lastMouse_;
        lastMouse_ = e->pos();
        vp_.centerX -= d.x() * vp_.scale;
        vp_.centerY -= d.y() * vp_.scale;
        scheduleRender();
    }
    QWidget::mouseMoveEvent(e);
}
void MandelbrotWidget::mouseReleaseEvent(QMouseEvent* e) {
    if (e->button() == Qt::LeftButton) dragging_ = false;
    QWidget::mouseReleaseEvent(e);
}

void MandelbrotWidget::resizeEvent(QResizeEvent* e) {
    QWidget::resizeEvent(e);
    scheduleRender();   // 首次 show 与窗口缩放都经此触发首帧
}

// ---- 多线程调度：切条带丢池，渐进回传 ----
void MandelbrotWidget::scheduleRender() {
    ++epoch_;   // 令所有在途旧任务的结果作废
    vp_.width = width();
    vp_.height = height();
    if (vp_.width <= 0 || vp_.height <= 0) return;

    pixmap_ = QImage(vp_.width, vp_.height, QImage::Format_RGB32);
    stripsTotal_ = multiThread_ ? QThread::idealThreadCount() * 2 : 1;  // 多线程：2×核数均衡负载
    stripsDone_ = 0;
    timer_.start();

    const int N = stripsTotal_;
    for (int i = 0; i < N; ++i) {
        const int y0 = vp_.height * i / N;
        const int y1 = vp_.height * (i + 1) / N;
        auto* task = new MandelbrotStripTask(epoch_, i, vp_, pal_, y0, y1);
        // 排队连接：stripReady 自动调度回主线程（接收者 this 所在线程）执行 onStripReady。
        connect(task, &MandelbrotStripTask::stripReady, this, &MandelbrotWidget::onStripReady);
        QThreadPool::globalInstance()->start(task);
    }
}

void MandelbrotWidget::onStripReady(quint64 epoch, int stripIndex, QImage strip) {
    if (epoch != epoch_) return;            // 过期结果丢弃（防竞态覆盖新画面）
    if (!strip.isNull()) {
        QPainter p(&pixmap_);               // 对【离屏 QImage】构造 QPainter——合法（非窗口 widget）
        p.drawImage(0, vp_.height * stripIndex / stripsTotal_, strip);
    }
    ++stripsDone_;
    update();                               // 渐进刷新：能看到条带自上而下并行填满
    if (stripsDone_ >= stripsTotal_) {
        label_->setText(QString(QStringLiteral("%1 · %2 ms · %3 线程"))
                            .arg(multiThread_ ? QStringLiteral("多线程渲染")
                                              : QStringLiteral("单线程渲染"))
                            .arg(int(timer_.elapsed()))
                            .arg(threadCount_));
    }
}

// ---- 卡死演示：在 GUI 线程同步算整帧，不分块、不丢池 → 事件循环停摆 → 界面冻结 ----
void MandelbrotWidget::onRenderOnMainThread() {
    ++epoch_;                               // 让在途后台任务的结果作废，避免覆盖本帧
    vp_.width = width();
    vp_.height = height();
    if (vp_.width <= 0 || vp_.height <= 0) return;

    timer_.start();
    pixmap_ = QImage(vp_.width, vp_.height, QImage::Format_RGB32);
    const int H = vp_.height;
    const int chunk = 24;                   // 分块调 renderStrip 只为复用同一纯函数
    // 自适应加重：单帧仅约几十毫秒（实测 820x640@256 ≈ 66ms），不足以让「冻结」肉眼可感。
    // 故循环渲染直到累计 ≥ 1500ms——冻结的本质是事件循环被占用，与「算什么」无关，
    // 这模拟任意一个「占着 GUI 线程不返回事件循环」的耗时操作（解析大文件、复杂运算……）。
    // 上限 100 轮防止极慢机器卡死过久。
    int frames = 0;
    do {
        for (int y = 0; y < H; y += chunk) {
            const QImage s = renderStrip(vp_, pal_, y, qMin(y + chunk, H));
            QPainter p(&pixmap_);
            p.drawImage(0, y, s);
        }
        ++frames;
    } while (timer_.elapsed() < 1500 && frames < 100);
    // 注意：本函数返回前事件循环不跑——期间按钮无响应、窗口拖不动、paintEvent 排队等待。
    update();
    label_->setText(QString(QStringLiteral("主线程渲染 (界面冻结) · %1 ms · %2 轮"))
                        .arg(int(timer_.elapsed())).arg(frames));
}

// ---- 单/多线程切换：复用同一套分块逻辑，仅切线程池大小 ----
void MandelbrotWidget::onToggleThreadMode() {
    multiThread_ = !multiThread_;
    QThreadPool::globalInstance()->setMaxThreadCount(
        multiThread_ ? QThread::idealThreadCount() : 1);
    threadCount_ = QThreadPool::globalInstance()->maxThreadCount();
    scheduleRender();
}
