/* Distributed under the Apache License, Version 2.0.
See accompanying NOTICE file for details.*/

#include "PulsePainterPlot.h"

#include <QThread>
#include <QPainter>
#include <QVector>

//-----------------------------------------------------------------------------
class PulsePainterPlot::Private
{
public:
  Private(PulsePainterPlot* q) : q{q} {}

  void scheduleUpdate();
  void update();

  PulsePainterPlot* const q;

  QVector<double> m_times;
  QVector<double> m_values;
  double m_userMinY = +std::numeric_limits<double>::infinity();
  double m_userMaxY = -std::numeric_limits<double>::infinity();
  double m_dataMinY = +std::numeric_limits<double>::infinity();
  double m_dataMaxY = -std::numeric_limits<double>::infinity();
  double m_padding = 0.0;
  int m_maxSize = 100;
  bool m_updateScheduled = false;
};

//-----------------------------------------------------------------------------
void PulsePainterPlot::Private::scheduleUpdate()
{
  if (!m_updateScheduled)
  {
    m_updateScheduled = true;
    QMetaObject::invokeMethod(q, [this]{ update(); }, Qt::QueuedConnection);
  }
}

//-----------------------------------------------------------------------------
void PulsePainterPlot::Private::update()
{
  m_dataMinY = +std::numeric_limits<double>::infinity();
  m_dataMaxY = -std::numeric_limits<double>::infinity();

  auto const size = m_values.size();
  if (size > 2)
  {
    for (int i = 0; i < size; i++)
    {
      auto const v = m_values[i];
      m_dataMinY = qMin(m_dataMinY, v);
      m_dataMaxY = qMax(m_dataMaxY, v);
    }
    if (m_dataMinY == m_dataMaxY)
    {
      m_dataMinY -= 0.5;
      m_dataMaxY += 0.5;
    }

    if (m_padding > 0.0)
    {
      auto const d = m_dataMaxY - m_dataMinY;
      auto const p = m_padding * d;
      m_dataMinY -= p;
      m_dataMaxY += p;
    }
  }

  m_updateScheduled = false;
  q->update();
}

//-----------------------------------------------------------------------------
PulsePainterPlot::PulsePainterPlot(QWidget* parent)
  : QWidget{parent}, d{new Private{this}}
{
}

//-----------------------------------------------------------------------------
PulsePainterPlot::~PulsePainterPlot()
{
}

//-----------------------------------------------------------------------------
void PulsePainterPlot::setMaxPoints(int count)
{
  d->m_maxSize = count;
  while (d->m_times.size() > count)
    d->m_times.pop_front();
  while (d->m_values.size() > count)
    d->m_values.pop_front();
}

//-----------------------------------------------------------------------------
void PulsePainterPlot::clear()
{
  if (QThread::currentThread() != this->thread())
  {
    QMetaObject::invokeMethod(this, &PulsePainterPlot::clear,
                              Qt::QueuedConnection);
    return;
  }

  d->m_times.clear();
  d->m_values.clear();
}

//-----------------------------------------------------------------------------
void PulsePainterPlot::setDataRange(double min, double max)
{
  d->m_userMinY = min;
  d->m_userMaxY = max;
}

//-----------------------------------------------------------------------------
void PulsePainterPlot::append(double time, double value)
{
  if (QThread::currentThread() != this->thread())
  {
    QMetaObject::invokeMethod(
      this, [time, value, this]{ append(time, value); },
      Qt::QueuedConnection);
    return;
  }

  d->m_times.push_back(time);
  d->m_values.push_back(value);

  double t = time;
  if (d->m_values.size() < d->m_maxSize)
  {
    for (int i = d->m_values.size(); i <= d->m_maxSize; ++i)
    {
      t -= 1./50.;
      d->m_values.push_back(value);
      d->m_times.push_front(t);
    }
  }

  d->m_values.pop_front();
  d->m_times.pop_front();

  d->scheduleUpdate();
}

//-----------------------------------------------------------------------------
void PulsePainterPlot::paintEvent(QPaintEvent*)
{
  auto const size = d->m_values.size();
  if (size > 2)
  {
    QPainter p{this};
    auto const& c = palette().color(QPalette::WindowText);

    auto const w = width();
    auto const h = height();

    auto const minT = d->m_times.first();
    auto const maxT = d->m_times.last();
    auto const minY = qMin(d->m_userMinY, d->m_dataMinY);
    auto const maxY = qMax(d->m_userMaxY, d->m_dataMaxY);

    auto const xRange = maxT - minT;
    auto const yRange = maxY - minY;
    auto const xs = static_cast<double>(w) / xRange;
    auto const ys = static_cast<double>(h) / yRange;

    auto points = QPolygonF{};
    points.reserve(d->m_maxSize);

    for (int i = 0; i < size; i++)
    {
      auto const t = d->m_times[i];
      auto const v = d->m_values[i];
      auto const x = static_cast<qreal>((t - minT) * xs);
      auto const y = static_cast<qreal>((v - minY) * ys);

      points.append({x, h - y});
    }

    p.setRenderHint(QPainter::Antialiasing);
    p.setRenderHint(QPainter::Antialiasing);
    p.setPen({c, 2.0f});
    p.drawPolyline(points);
  }
}

