#include "ringbuffer.h"
#include "sparkline_plotitem.h"
#include <QSGGeometryNode>
#include <QSGFlatColorMaterial>
#include <QSGSimpleTextureNode>
#include <QSGTexture>
#include <QPainter>
#include <QImage>
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <iostream>
#include <memory>
#include <qcolor.h>
#include <qcontainerfwd.h>
#include <qfontmetrics.h>
#include <qimage.h>
#include <qnamespace.h>
#include <qpoint.h>
#include <qquickitem.h>
#include <qsggeometry.h>
#include <qsgnode.h>
#include <qsgtexture.h>
#include <qsize.h>
#include <qtypes.h>
#include <qvectornd.h>
#include <vector>

SparklinePlotItem::SparklinePlotItem(QQuickItem* parent)
    : QQuickItem(parent) {

    setFlag(ItemHasContents, true);
    m_buf = std::make_unique<RingBuffer<double>>(10'000'000);
}

void SparklinePlotItem::append(float y) {
    m_buf->push(y);

    if (m_follow) {
        const auto newest = m_buf->newestGlobalIndex();
        
        if (newest + 1 >= (uint64_t)m_viewCount) {
            m_viewStart = newest + 1 - (uint64_t)m_viewCount;
        }
        else {
            m_viewStart = 0;
        }
        emit viewStartChanged();
    }
}

void SparklinePlotItem::clear() {
    m_textCache.clear();
    m_buf = std::make_unique<RingBuffer<double>>(m_buf->capacity());
    m_viewStart = 0;
    emit viewStartChanged();
    update();
}

void SparklinePlotItem::setViewCount(int c) {
    c = std::max(10, c);
    if (m_viewCount == c)
        return;
    m_viewCount = c;
    emit viewCountChanged();
    update();
}

void SparklinePlotItem::setViewStart(qulonglong s) {
    if (m_viewStart == s)
        return;
    m_viewStart = s;
    emit viewStartChanged();
    update();
}

void SparklinePlotItem::setFollow(bool f) {
    if (m_follow == f)
        return;
    m_follow = f;
    emit followChanged();
}

void SparklinePlotItem::setLineColor(const QColor& c) {
    if (m_lineColor == c)
        return;
    m_lineColor = c;
    emit lineColorChanged();
    update();
}

void SparklinePlotItem::setGridColor(const QColor& c) {
    if (m_gridColor == c) {
        return;
    }
    m_gridColor = c;
    emit gridColorChanged();
    update();
}

void SparklinePlotItem::setTextColor(const QColor& c) {
    if (m_textColor == c) {
        return;
    }
    m_textColor = c;
    emit textColorChanged();
    update();
}

void SparklinePlotItem::setYTickCount(int c) {
    c = std::max(3, std::min(8, c));
    if (m_yTickCount == c)
        return;
    m_yTickCount = c;
    emit yTickCountChanged();
    update();
}

void SparklinePlotItem::setLeftPadding(int v) { 
    if(m_leftPad==v) 
        return; m_leftPad=v; 
    emit leftPaddingChanged(); 
    update(); 
}
void SparklinePlotItem::setRightPadding(int v){ if(m_rightPad==v) return; m_rightPad=v; emit rightPaddingChanged(); update(); }
void SparklinePlotItem::setTopPadding(int v){ if(m_topPad==v) return; m_topPad=v; emit topPaddingChanged(); update(); }
void SparklinePlotItem::setBottomPadding(int v){ if(m_bottomPad==v) return; m_bottomPad=v; emit bottomPaddingChanged(); update(); }

QSGTexture* SparklinePlotItem::getTextTexture(const QString& text, const QFont& font, QSize* outSize) {
    const qreal dpr = window() ? window()->devicePixelRatio() : 1.0;
    const QString key = text + "|" + font.family() + "|" + QString::number(font.pixelSize()) +
        "|" + QString::number(dpr, 'f', 2);
    auto it = m_textCache.find(key);
    if (it != m_textCache.end()) {
        if (outSize)
            *outSize = it->second.size;
        return it->second.tex;
    }

    QFont f = font;
    if (f.pixelSize() < 0) {
        f.setPixelSize(10);
    }

    QFontMetrics fm(f);
    QSize sz = fm.size(Qt::TextSingleLine, text) + QSize(6, 4);

    const QSize pixelSize(qMax(1, qRound(sz.width() * dpr)), qMax(1, qRound(sz.height() * dpr)));
    QImage img(pixelSize, QImage::Format_ARGB32_Premultiplied);
    img.setDevicePixelRatio(dpr);
    img.fill(Qt::transparent);

    QPainter p(&img);
    p.setRenderHint(QPainter::Antialiasing, true);
    p.setFont(f);
    p.setPen(m_textColor);
    p.drawText(QRect(QPoint(0, 0), sz), Qt::AlignCenter, text);
    p.end();

    QSGTexture* tex = window()->createTextureFromImage(img);

    m_textCache[key] = { tex, sz };
    if (outSize)
        *outSize = sz;
    return tex;
}

static void downsampleMinMax(const RingBuffer<double>& buf, uint64_t startGidx, uint64_t count, int widthPx,
    std::vector<QPointF>& out, double& outMinY, double& outMaxY) {
    out.clear();
    outMinY = 1e30;
    outMaxY = -1e30;
    if (count < 2 || widthPx <= 2)
        return;

    const int buckets = std::max(2, widthPx);
    const double samplesPerBucket = double(count) / double(buckets);

    out.reserve(buckets * 2);

    for (int b = 0; b < buckets; ++b) {
        uint64_t s0 = startGidx + uint64_t(std::floor(b * samplesPerBucket));
        uint64_t s1 = startGidx + uint64_t(std::floor((b + 1) * samplesPerBucket));
        if (s1 <= s0) {
            s1 = s0 + 1;
        }
        if (s1 > startGidx + count) {
            s1 = startGidx + count;
        }

        double v;
        bool ok = false;
        double mn = 1e30;
        double mx = -1e30;

        for (uint64_t g = s0; g < s1; ++g) {
            if (buf.readByGlobalIndex(g, v)) {
                ok = true;
                mn = std::min(mn, (double)v);
                mx = std::max(mx, (double)v);
            }
        }

        if (!ok) {
            continue;
        }

        outMinY = std::min(outMinY, mn);
        outMaxY = std::max(outMaxY, mx);

        out.emplace_back(double(b), mn);
        out.emplace_back(double(b), mx);
    }
}

QSGNode* SparklinePlotItem::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
    if (!window()) {
        return oldNode;
    }

    const qreal dpr = window()->devicePixelRatio();
    auto alignToPixel = [dpr](float v) -> float {
        return (dpr > 0.0) ? (std::round(v * dpr) / dpr) : v;
    };

    QSGNode* root = oldNode;
    if (!root) {
        root = new QSGNode();
    }

    QSGGeometryNode* gridNode = nullptr;
    QSGGeometryNode* lineNode = nullptr;

    if (root->childCount() >= 2) {
        gridNode = static_cast<QSGGeometryNode*>(root->childAtIndex(0));
        lineNode = static_cast<QSGGeometryNode*>(root->childAtIndex(1));
    }
    else {
        gridNode = new QSGGeometryNode();
        auto* gridGeom = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 0);
        gridGeom->setDrawingMode(QSGGeometry::DrawLines);
        gridNode->setGeometry(gridGeom);
        gridNode->setFlag(QSGNode::OwnsGeometry);

        auto* gridMat = new QSGFlatColorMaterial();
        gridMat->setColor(m_gridColor);
        gridNode->setMaterial(gridMat);
        gridNode->setFlag(QSGNode::OwnsMaterial);

        // Line node
        lineNode = new QSGGeometryNode();
        auto* lineGeom = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 0);
        lineGeom->setDrawingMode(QSGGeometry::DrawLineStrip);
        lineNode->setGeometry(lineGeom);
        lineNode->setFlag(QSGNode::OwnsGeometry);

        auto* lineMat = new QSGFlatColorMaterial();
        lineMat->setColor(m_lineColor);
        lineNode->setMaterial(lineMat);
        lineNode->setFlag(QSGNode::OwnsMaterial);

        root->appendChildNode(gridNode);
        root->appendChildNode(lineNode);
    }

    static_cast<QSGFlatColorMaterial*>(gridNode->material())->setColor(m_gridColor);
    static_cast<QSGFlatColorMaterial*>(lineNode->material())->setColor(m_lineColor);

    // 计算绘图区
    const float W = float(width());
    const float H = float(height());
    const float left = float(m_leftPad);
    const float right = W - float(m_rightPad);
    const float top = float(m_topPad);
    const float bottom = H - float(m_bottomPad);

    const float plotW = std::max(1.0f, right - left);
    const float plotH = std::max(1.0f, bottom - top);

    const uint64_t oldest = m_buf->oldestGlobalIndex();
    const uint64_t newest = m_buf->newestGlobalIndex();
    const uint64_t sz = m_buf->size();
    if (sz < 2) {
        gridNode->geometry()->allocate(0);
        lineNode->geometry()->allocate(0);
        return root;
    }

    // clamp viewStart/viewCount
    uint64_t startG = std::max<uint64_t>(oldest, (uint64_t)m_viewStart);
    uint64_t count = (uint64_t)m_viewCount;
    if (startG + count > newest + 1) {
        if (newest + 1 >= count) startG = newest + 1 - count;
        else startG = oldest;
    }
    startG = std::max<uint64_t>(oldest, startG);

    // 降采样
    std::vector<QPointF> ds;
    ds.reserve(int(plotW) * 2);
    double rawMinY, rawMaxY;
    downsampleMinMax(*m_buf, startG, count, int(plotW), ds, rawMinY, rawMaxY);
    if (ds.size() < 2) {
        gridNode->geometry()->allocate(0);
        lineNode->geometry()->allocate(0);
        return root;
    }

    // 自动 nice ticks
    // 增加一点 padding，避免贴边
    const double pad = (rawMaxY - rawMinY) * 0.08 + 1e-9;
    auto ticks = niceTicks(rawMinY - pad, rawMaxY + pad, m_yTickCount);

    const double yMin = ticks.niceMin;
    const double yMax = ticks.niceMax;
    const double yRange = (yMax - yMin != 0) ? (yMax - yMin) : 1.0;

    // --- 1) 网格线（水平线） ---
    {
        auto* geom = gridNode->geometry();
        // 每个 tick 画一条水平线 = 2 points
        const int n = int(ticks.ticks.size());
        geom->allocate(n * 2);
        auto* v = geom->vertexDataAsPoint2D();

        for (int i = 0; i < n; ++i) {
            double tv = ticks.ticks[i];
            float yn = float((tv - yMin) / yRange);
            float py = alignToPixel(bottom - yn * plotH);

            v[i*2 + 0].set(left, py);
            v[i*2 + 1].set(right, py);
        }
        gridNode->markDirty(QSGNode::DirtyGeometry);
    }

    // --- 2) 折线（注意 min/max per pixel 输出是 [x, min][x,max]，可直接 line strip） ---
    {
        auto* geom = lineNode->geometry();
        const int vCount = int(ds.size());
        geom->allocate(vCount);
        auto* v = geom->vertexDataAsPoint2D();

        const double xMaxBucket = ds.back().x();
        const double xDen = (xMaxBucket > 0) ? xMaxBucket : 1.0;

        for (int i = 0; i < vCount; ++i) {
            const double bx = ds[i].x();
            const double yv = ds[i].y();
            float xn = float(bx / xDen);
            float yn = float((yv - yMin) / yRange);

            float px = left + xn * plotW;
            float py = bottom - yn * plotH;
            v[i].set(px, py);
        }
        lineNode->markDirty(QSGNode::DirtyGeometry);
    }

    // --- 3) Tick 标签 ---
    // 先删除旧 label nodes（保留 root 的前两个 children）
    while (root->childCount() > 2) {
        delete root->childAtIndex(2);
    }

    QFont font;
    font.setPixelSize(10);

    const int nTicks = int(ticks.ticks.size());
    for (int i = 0; i < nTicks; ++i) {
        const double tv = ticks.ticks[i];
        QString label = QString::number(tv, 'g', 4);

        QSize logicalSize;
        QSGTexture* tex = getTextTexture(label, font, &logicalSize);
        if (!tex) continue;

        float yn = float((tv - yMin) / yRange);
        float py = bottom - yn * plotH;

        // 创建 texture node
        auto* tnode = new QSGSimpleTextureNode();
        tnode->setTexture(tex);
        // label 放到左侧 padding 区
        float tx = alignToPixel(2.0f);
        float ty = alignToPixel(py - logicalSize.height() / 2.0f);
        tnode->setRect(QRectF(tx, ty, logicalSize.width(), logicalSize.height()));
        root->appendChildNode(tnode);
    }

    return root;
}
