#include "InkCanvas.h"
#include "epfb.h"

#include <QPainter>
#include <QPolygon>
#include <QTimer>

InkCanvas::InkCanvas(QQuickItem *parent) : QQuickPaintedItem(parent)
{
    setRenderTarget(QQuickPaintedItem::Image);
    // Crisp 1-bit black so the Mono waveform renders single-pass (no gray dither).
    setAntialiasing(false);
}

void InkCanvas::ensureBuffer(const QSize &size)
{
    if (size.isEmpty() || m_buffer.size() == size)
        return;
    QImage img(size, QImage::Format_RGB32);
    img.fill(Qt::white);
    if (!m_buffer.isNull()) {
        QPainter p(&img);
        p.drawImage(0, 0, m_buffer);
    }
    m_buffer = img;
}

void InkCanvas::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
{
    QQuickPaintedItem::geometryChange(newGeometry, oldGeometry);
    ensureBuffer(newGeometry.size().toSize());
    update();
}

void InkCanvas::paint(QPainter *painter)
{
    if (!m_buffer.isNull())
        painter->drawImage(0, 0, m_buffer);
}

void InkCanvas::strokeStart(QPointF p)
{
    ensureBuffer(boundingRect().size().toSize());
    m_last = p;
    m_drawing = true;
}

void InkCanvas::strokeMove(QPointF p, qreal pressure, bool eraser)
{
    if (m_buffer.isNull())
        ensureBuffer(boundingRect().size().toSize());
    if (m_buffer.isNull())
        return;
    if (!m_drawing) {
        m_last = p;
        m_drawing = true;
    }

    qreal w;
    QPainter painter(&m_buffer);
    painter.setRenderHint(QPainter::Antialiasing, false);
    if (eraser) {
        w = 40.0;
        painter.setPen(QPen(Qt::white, w, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
    } else {
        w = m_baseWidth * (0.6 + 0.4 * pressure);
        painter.setPen(QPen(m_inkColor, w, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
    }
    painter.drawLine(m_last, p);
    painter.end();

    QRectF dirty = QRectF(m_last, p).normalized().adjusted(-w - 1, -w - 1, w + 1, w + 1);
    m_last = p;
    update(dirty.toRect());
    queueRefresh(dirty.toRect());
    scheduleFlush();
}

void InkCanvas::strokeEnd()
{
    m_drawing = false;
    scheduleFlush();
}

void InkCanvas::queueRefresh(const QRect &r)
{
    m_pending = m_pending.isValid() ? m_pending.united(r) : r;
}

// Coalesce: schedule one delayed flush (GUI thread). The delay lets the scene
// composite the new ink into the framebuffer before we re-push it solid; the
// throttle keeps us from flooding the SWTCON.
void InkCanvas::scheduleFlush()
{
    if (m_flushScheduled)
        return;
    m_flushScheduled = true;
    QTimer::singleShot(40, this, [this]() {
        m_flushScheduled = false;
        flushRefresh();
    });
}

// GUI thread: re-push the accumulated region with a single explicit swapBuffers,
// which renders SOLID (replacing the scene's dashed two-pass). Params are not
// critical — screen=Pen(0), content=0, flags=0.
void InkCanvas::flushRefresh()
{
    const QRect r = m_pending;
    m_pending = QRect();
    if (!r.isValid())
        return;
    if (void *fb = epfb_instance())
        epfb_swapBuffers(fb, {r.left(), r.top(), r.right(), r.bottom()}, 0, 0, 0);
    // If more ink arrived while we were flushing, keep cleaning up.
    if (m_drawing)
        scheduleFlush();
}

void InkCanvas::clearPage()
{
    if (m_buffer.isNull())
        return;
    m_buffer.fill(Qt::white);
    update();
    queueRefresh(boundingRect().toRect());
}

QRect InkCanvas::testRectQ() const
{
    return QRect(80, 300, 780, 280);
}

void InkCanvas::drawTestStroke()
{
    ensureBuffer(boundingRect().size().toSize());
    if (m_buffer.isNull())
        return;
    m_buffer.fill(Qt::white);

    QPainter p(&m_buffer);
    p.setRenderHint(QPainter::Antialiasing, false);
    p.setPen(QPen(Qt::black, m_baseWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));

    const QRect r = testRectQ();
    // A zigzag plus the two diagonals — a recognizable, stroke-like pattern.
    QPolygon zig;
    int i = 0;
    for (int x = r.left(); x <= r.right(); x += 70, ++i)
        zig << QPoint(x, (i % 2 == 0) ? r.top() + 40 : r.bottom() - 40);
    p.drawPolyline(zig);
    p.drawLine(r.topLeft(), r.bottomRight());
    p.drawLine(r.bottomLeft(), r.topRight());
    p.end();

    update();
}
