/* Distributed under the Apache License, Version 2.0.
See accompanying NOTICE file for details.*/
#include "QwtPulsePlotLoop.h"

#include <QVector>
#include <QGridLayout>

#include <qwt_plot.h>
#include <qwt_plot_grid.h>
#include <qwt_plot_curve.h>
#include <qwt_point_data.h>
#include <qwt_plot_canvas.h>
#include <qwt_plot_rescaler.h>
#include <qwt_text.h>
#include <qwt_plot_directpainter.h>
#include <qwt_painter.h>
#include <qwt_symbol.h>
#include <qpaintengine.h>

// Uses incrementalplot, rescaler and other qwt examples 

/// QwtSeriesData is abstract interface to represent data
/// separates access from handling 
class CurveData : public QwtSeriesData<QPointF>
{
public:
    CurveData(int size)
    {
        m_samples.fill({ 0,0 }, size);
    }

    QRectF boundingRect() const override
    {
        if (m_boundsDirty)
        {
            d_boundingRect = qwtBoundingRect(*this);
            m_boundsDirty = false;
        }
        return d_boundingRect;
    }

    inline void append(const QPointF& point)
    {
        m_boundsDirty = true;
        m_samples[m_first] = point;
        m_first = (m_first + 1) % m_samples.size();
    }

    void clear()
    {
        m_samples.fill({ 0,0 });
        d_boundingRect = QRectF(0.0, 0.0, -1.0, -1.0);
    }

    QPointF sample(size_t i) const override
    {
        const int size = m_samples.size();

        size_t index = m_first + i;
        if (index >= size)
            index -= size;

        return m_samples[static_cast<int>(index)];
    }

    size_t size() const override
    {
        return m_samples.size();
    }

    void resize(size_t size)
    {
        m_samples.resize(static_cast<int>(size));
        clear();
    }

private:
    mutable std::atomic<bool> m_boundsDirty{ true };
    QVector<QPointF> m_samples;
    int m_first = 0;
};


class QwtPulsePlotLoop::Data
{
public:
  QwtPlot*                Plot;
  QwtPlotCurve*           Curve;
  QwtPlotGrid*            Grid;
  QwtPlotDirectPainter*   Painter;
  QwtPlotRescaler*        Rescaler;
  CurveData*              Data;
  size_t                  MaxSize;
};

QwtPulsePlotLoop::QwtPulsePlotLoop(QWidget* parent, int max_points) : QWidget(parent)
{
  d = new QwtPulsePlotLoop::Data();

  d->Plot = new QwtPlot(parent);
  d->Plot->setMaximumHeight(parent->height());
  d->Plot->setAutoReplot(false);

  d->Painter = new QwtPlotDirectPainter(d->Plot);

  d->Rescaler = new QwtPlotRescaler(d->Plot->canvas());
  d->Rescaler->setRescalePolicy(QwtPlotRescaler::Fitting);
  d->Rescaler->setReferenceAxis(QwtPlot::xBottom);
  d->Rescaler->setAspectRatio(QwtPlot::yLeft, 1.0);
  d->Rescaler->setAspectRatio(QwtPlot::yRight, 0.0);
  d->Rescaler->setAspectRatio(QwtPlot::xTop, 0.0);
  for (int axis = 0; axis < QwtPlot::axisCnt; axis++)
  {
      d->Rescaler->setExpandingDirection(QwtPlotRescaler::ExpandBoth);
  }


  if (QwtPainter::isX11GraphicsSystem())
  {
      d->Plot->canvas()->setAttribute(Qt::WA_PaintOnScreen, true);
  }

  d->Data = new CurveData(max_points);
  
  d->Curve = new QwtPlotCurve();
  d->Curve->setRenderHint(QwtPlotItem::RenderAntialiased);
  d->Curve->setPen(Qt::blue, 2);
  d->Curve->setData(d->Data);
  d->Curve->attach(d->Plot);

  d->Grid = new QwtPlotGrid();
  d->Grid->enableX(false);
  d->Grid->enableY(false);
  d->Grid->attach(d->Plot);

  auto layout = new QGridLayout();
  layout->setMargin(0);
  layout->addWidget(d->Plot);
  setLayout(layout);
}

QwtPulsePlotLoop::~QwtPulsePlotLoop()
{
  delete d->Plot;
  delete d;
}

void QwtPulsePlotLoop::Clear()
{
  d->Data->clear();
  d->Plot->replot();
}

QwtPlot& QwtPulsePlotLoop::GetPlot() { return *d->Plot; }
QwtPlotCurve& QwtPulsePlotLoop::GetCurve() { return *d->Curve; }
QwtPlotGrid& QwtPulsePlotLoop::GetGrid() { return *d->Grid; }

void QwtPulsePlotLoop::SetMaxPoints(size_t max)
{
    d->Data->resize(max);
}

void QwtPulsePlotLoop::SetAspectRatio(double value)
{
    d->Rescaler->setAspectRatio(value);
}

void QwtPulsePlotLoop::Append(double x, double y)
{
    if (std::isnan(x) || std::isnan(y))
    {
        return;
    }
    d->Data->append({ x, y });
}

void QwtPulsePlotLoop::UpdateUI(bool pad)
{
    const bool doClip = !d->Plot->canvas()->testAttribute(Qt::WA_PaintOnScreen);
//     if (false)
//     {
//         /*
//            Depending on the platform setting a clip might be an important
//            performance issue. F.e. for Qt Embedded this reduces the
//            part of the backing store that has to be copied out - maybe
//            to an unaccelerated frame buffer device.
//          */
//         const QwtScaleMap xMap = d->Plot->canvasMap(d->Curve->xAxis());
//         const QwtScaleMap yMap = d->Plot->canvasMap(d->Curve->yAxis());
// 
//         QRegion clipRegion;
// 
//         const QSize symbolSize = d->Curve->symbol()->size();
//         QRect r(0, 0, symbolSize.width() + 2, symbolSize.height() + 2);
// 
//         const QPointF center =
//             QwtScaleMap::transform(xMap, yMap, (*d->Data).sample(d->Data->size()-1));
//         r.moveCenter(center.toPoint());
//         clipRegion += r;
// 
//         d->Painter->setClipRegion(clipRegion);
//     }

    auto rect = d->Data->boundingRect();
    d->Rescaler->setIntervalHint(QwtPlot::xBottom, { rect.x(), rect.width() });
    d->Rescaler->setIntervalHint(QwtPlot::yLeft, { rect.y(), rect.height() });

    d->Rescaler->rescale();
    d->Plot->replot();
}

