/* -*- c++ -*- */
/*
 * Copyright 2013 Free Software Foundation, Inc.
 *
 * This file is part of GNU Radio
 *
 * GNU Radio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3, or (at your option)
 * any later version.
 *
 * GNU Radio is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with GNU Radio; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#ifndef HISTOGRAM_DISPLAY_PLOT_C
#define HISTOGRAM_DISPLAY_PLOT_C

#include <gnuradio/qtgui/HistogramDisplayPlot.h>

#include <qwt_scale_draw.h>
#include <qwt_legend.h>
#include <QColor>
#include <cmath>
#include <iostream>
#include <volk/volk.h>
#include <gnuradio/math.h>
#include <boost/math/special_functions/round.hpp>

#ifdef _MSC_VER
#define copysign _copysign
#endif

class TimePrecisionClass
{
public:
  TimePrecisionClass(const int timeprecision)
  {
    d_time_precision = timeprecision;
  }

  virtual ~TimePrecisionClass()
  {
  }

  virtual unsigned int getTimePrecision() const
  {
    return d_time_precision;
  }

  virtual void setTimePrecision(const unsigned int newprecision)
  {
    d_time_precision = newprecision;
  }
protected:
  unsigned int d_time_precision;
};


class HistogramDisplayZoomer: public QwtPlotZoomer, public TimePrecisionClass
{
public:
#if QWT_VERSION < 0x060100
  HistogramDisplayZoomer(QwtPlotCanvas* canvas, const unsigned int timeprecision)
#else /* QWT_VERSION < 0x060100 */
  HistogramDisplayZoomer(QWidget* canvas, const unsigned int timeprecision)
#endif /* QWT_VERSION < 0x060100 */
    : QwtPlotZoomer(canvas),TimePrecisionClass(timeprecision)
  {
    setTrackerMode(QwtPicker::AlwaysOn);
  }

  virtual ~HistogramDisplayZoomer()
  {
  }

  virtual void updateTrackerText()
  {
    updateDisplay();
  }

  void setUnitType(const std::string &type)
  {
    d_unit_type = type;
  }

protected:
  using QwtPlotZoomer::trackerText;
  virtual QwtText trackerText(const QPoint& p) const
  {
    QwtText t;
    QwtDoublePoint dp = QwtPlotZoomer::invTransform(p);
    if((dp.y() > 0.0001) && (dp.y() < 10000)) {
      t.setText(QString("%1, %2").
		arg(dp.x(), 0, 'f', 4).
		arg(dp.y(), 0, 'f', 0));
    }
    else {
      t.setText(QString("%1, %2").
		arg(dp.x(), 0, 'f', 4).
		arg(dp.y(), 0, 'e', 0));
    }

    return t;
  }

private:
  std::string d_unit_type;
};


/***********************************************************************
 * Main Time domain plotter widget
 **********************************************************************/
HistogramDisplayPlot::HistogramDisplayPlot(int nplots, QWidget* parent)
  : DisplayPlot(nplots, parent)
{
  d_bins = 100;
  d_accum = false;

  // Initialize x-axis data array
  d_xdata = new double[d_bins];
  memset(d_xdata, 0x0, d_bins*sizeof(double));

  d_zoomer = new HistogramDisplayZoomer(canvas(), 0);

#if QWT_VERSION < 0x060000
  d_zoomer->setSelectionFlags(QwtPicker::RectSelection | QwtPicker::DragSelection);
#endif

  d_zoomer->setMousePattern(QwtEventPattern::MouseSelect2,
                            Qt::RightButton, Qt::ControlModifier);
  d_zoomer->setMousePattern(QwtEventPattern::MouseSelect3,
                            Qt::RightButton);

  const QColor c(Qt::darkRed);
  d_zoomer->setRubberBandPen(c);
  d_zoomer->setTrackerPen(c);

  d_semilogx = false;
  d_semilogy = false;
  d_autoscale_state = true;
  d_autoscalex_state = false;

  setAxisScaleEngine(QwtPlot::xBottom, new QwtLinearScaleEngine);
  setXaxis(-1, 1);
  setAxisTitle(QwtPlot::xBottom, "Value");

  setAxisScaleEngine(QwtPlot::yLeft, new QwtLinearScaleEngine);
  setYaxis(-2.0, d_bins);
  setAxisTitle(QwtPlot::yLeft, "Count");
  
  QList<QColor> colors;
  colors << QColor(Qt::blue) << QColor(Qt::red) << QColor(Qt::green)
	 << QColor(Qt::black) << QColor(Qt::cyan) << QColor(Qt::magenta)
	 << QColor(Qt::yellow) << QColor(Qt::gray) << QColor(Qt::darkRed)
	 << QColor(Qt::darkGreen) << QColor(Qt::darkBlue) << QColor(Qt::darkGray);

  // Setup dataPoints and plot vectors
  // Automatically deleted when parent is deleted
  for(int i = 0; i < d_nplots; i++) {
    d_ydata.push_back(new double[d_bins]);
    memset(d_ydata[i], 0, d_bins*sizeof(double));

    d_plot_curve.push_back(new QwtPlotCurve(QString("Data %1").arg(i)));
    d_plot_curve[i]->attach(this);
    d_plot_curve[i]->setPen(QPen(colors[i]));
    d_plot_curve[i]->setRenderHint(QwtPlotItem::RenderAntialiased);

    // Adjust color's transparency for the brush
    colors[i].setAlpha(127 / d_nplots);
    d_plot_curve[i]->setBrush(QBrush(colors[i]));

    colors[i].setAlpha(255 / d_nplots);
    QwtSymbol *symbol = new QwtSymbol(QwtSymbol::NoSymbol, QBrush(colors[i]),
				      QPen(colors[i]), QSize(7,7));

#if QWT_VERSION < 0x060000
    d_plot_curve[i]->setRawData(d_xdata, d_ydata[i], d_bins);
    d_plot_curve[i]->setSymbol(*symbol);
#else
    d_plot_curve[i]->setRawSamples(d_xdata, d_ydata[i], d_bins);
    d_plot_curve[i]->setSymbol(symbol);
#endif
  }

  _resetXAxisPoints(-1, 1);
}

HistogramDisplayPlot::~HistogramDisplayPlot()
{
  for(int i = 0; i < d_nplots; i++)
    delete[] d_ydata[i];
  delete[] d_xdata;

  // d_zoomer and _panner deleted when parent deleted
}

void
HistogramDisplayPlot::replot()
{
  QwtPlot::replot();
}

void
HistogramDisplayPlot::plotNewData(const std::vector<double*> dataPoints,
				   const int64_t numDataPoints,
				   const double timeInterval)
{
  if(!d_stop) {
    if((numDataPoints > 0)) {

      // keep track of the min/max values for when autoscaleX is called.
      d_xmin = 1e20;
      d_xmax = -1e20;
      for(int n = 0; n < d_nplots; n++) {
        d_xmin = std::min(d_xmin, *std::min_element(dataPoints[n], dataPoints[n]+numDataPoints));
        d_xmax = std::max(d_xmax, *std::max_element(dataPoints[n], dataPoints[n]+numDataPoints));
      }

      // If autoscalex has been clicked, clear the data for the new
      // bin widths and reset the x-axis.
      if(d_autoscalex_state) {
        for(int n = 0; n < d_nplots; n++)
          memset(d_ydata[n], 0, d_bins*sizeof(double));
        _resetXAxisPoints(d_xmin, d_xmax);
        d_autoscalex_state = false;
      }

      int index;
      for(int n = 0; n < d_nplots; n++) {
        if(!d_accum)
          memset(d_ydata[n], 0, d_bins*sizeof(double));
        for(int64_t point = 0; point < numDataPoints; point++) {
          index = boost::math::iround(1e-20 + (dataPoints[n][point] - d_left)/d_width);
          if((index >= 0) && (index < d_bins))
            d_ydata[n][static_cast<int>(index)] += 1;
        }
      }

      double height = *std::max_element(d_ydata[0], d_ydata[0]+d_bins);
      for(int n = 1; n < d_nplots; n++) {
        height = std::max(height, *std::max_element(d_ydata[n], d_ydata[n]+d_bins));
      }

      if(d_autoscale_state)
        _autoScaleY(0, height);

      replot();
    }
  }
}

void
HistogramDisplayPlot::setXaxis(double min, double max)
{
  _resetXAxisPoints(min, max);
}

void
HistogramDisplayPlot::_resetXAxisPoints(double left, double right)
{
  // Something's wrong with the data (NaN, Inf, or something else)
  if((left == right) || (left > right))
    throw std::runtime_error("HistogramDisplayPlot::_resetXAxisPoints left and/or right values are invalid");

  d_left  = left *(1 - copysign(0.1, left));
  d_right = right*(1 + copysign(0.1, right));
  d_width = (d_right - d_left)/(d_bins);
  for(long loc = 0; loc < d_bins; loc++){
    d_xdata[loc] = d_left + loc*d_width;
  }
#if QWT_VERSION < 0x060100
  axisScaleDiv(QwtPlot::xBottom)->setInterval(d_left, d_right);
#else /* QWT_VERSION < 0x060100 */
  QwtScaleDiv scalediv(d_left, d_right);
  setAxisScaleDiv(QwtPlot::xBottom, scalediv);
#endif /* QWT_VERSION < 0x060100 */

  // Set up zoomer base for maximum unzoom x-axis
  // and reset to maximum unzoom level
  QwtDoubleRect zbase = d_zoomer->zoomBase();

  if(d_semilogx) {
    setAxisScale(QwtPlot::xBottom, 1e-1, d_right);
    zbase.setLeft(1e-1);
  }
  else {
    setAxisScale(QwtPlot::xBottom, d_left, d_right);
    zbase.setLeft(d_left);
  }

  zbase.setRight(d_right);
  d_zoomer->zoom(zbase);
  d_zoomer->setZoomBase(zbase);
  d_zoomer->zoom(0);
}

void
HistogramDisplayPlot::_autoScaleY(double bottom, double top)
{
  // Auto scale the y-axis with a margin of 20% (10 dB for log scale)
  double b = bottom - fabs(bottom)*0.20;
  double t = top + fabs(top)*0.20;
  if(d_semilogy) {
    if(bottom > 0) {
      setYaxis(b-10, t+10);
    }
    else {
      setYaxis(1e-3, t+10);
    }
  }
  else {
    setYaxis(b, t);
  }
}

void
HistogramDisplayPlot::setAutoScaleX()
{
  d_autoscalex_state = true;
}

void
HistogramDisplayPlot::setAutoScale(bool state)
{
  d_autoscale_state = state;
}

void
HistogramDisplayPlot::setSemilogx(bool en)
{
  d_semilogx = en;
  if(!d_semilogx) {
    setAxisScaleEngine(QwtPlot::xBottom, new QwtLinearScaleEngine);
  }
  else {
#if QWT_VERSION < 0x060100
    setAxisScaleEngine(QwtPlot::xBottom, new QwtLog10ScaleEngine);
#else /* QWT_VERSION < 0x060100 */
    setAxisScaleEngine(QwtPlot::xBottom, new QwtLogScaleEngine);
#endif /* QWT_VERSION < 0x060100 */
  }
}

void
HistogramDisplayPlot::setSemilogy(bool en)
{
  if(d_semilogy != en) {
    d_semilogy = en;

#if QWT_VERSION < 0x060100
    double max = axisScaleDiv(QwtPlot::yLeft)->upperBound();
#else /* QWT_VERSION < 0x060100 */
    double max = axisScaleDiv(QwtPlot::yLeft).upperBound();
#endif /* QWT_VERSION < 0x060100 */

    if(!d_semilogy) {
      setAxisScaleEngine(QwtPlot::yLeft, new QwtLinearScaleEngine);
      setYaxis(-pow(10.0, max/10.0), pow(10.0, max/10.0));
    }
    else {
#if QWT_VERSION < 0x060100
      setAxisScaleEngine(QwtPlot::yLeft, new QwtLog10ScaleEngine);
#else /* QWT_VERSION < 0x060100 */
    setAxisScaleEngine(QwtPlot::yLeft, new QwtLogScaleEngine);
#endif /* QWT_VERSION < 0x060100 */
      setYaxis(1e-10, 10.0*log10(100*max));
    }
  }
}

void
HistogramDisplayPlot::setAccumulate(bool state)
{
  d_accum = state;
}

void
HistogramDisplayPlot::setMarkerAlpha(int which, int alpha)
{
  if(which < d_nplots) {
    // Get the pen color
    QPen pen(d_plot_curve[which]->pen());
    QBrush brush(d_plot_curve[which]->brush());
    QColor color = brush.color();
    
    // Set new alpha and update pen
    color.setAlpha(alpha);
    brush.setColor(color);
    color.setAlpha(std::min(255, static_cast<int>(alpha*1.5)));
    pen.setColor(color);
    d_plot_curve[which]->setBrush(brush);
    d_plot_curve[which]->setPen(pen);

    // And set the new color for the markers
#if QWT_VERSION < 0x060000
    QwtSymbol sym = (QwtSymbol)d_plot_curve[which]->symbol();
    setLineMarker(which, sym.style());
#else
    QwtSymbol *sym = (QwtSymbol*)d_plot_curve[which]->symbol();
    if(sym) {
      sym->setColor(color);
      sym->setPen(pen);
      d_plot_curve[which]->setSymbol(sym);
    }
#endif
  }
}

int
HistogramDisplayPlot::getMarkerAlpha(int which) const
{
  if(which < d_nplots) {
    return d_plot_curve[which]->brush().color().alpha();
  }
  else {
    return 0;
  }
}

void
HistogramDisplayPlot::setLineColor(int which, QColor color)
{
  if(which < d_nplots) {
    // Adjust color's transparency for the brush
    color.setAlpha(127 / d_nplots);

    QBrush brush(d_plot_curve[which]->brush());
    brush.setColor(color);
    d_plot_curve[which]->setBrush(brush);

    // Adjust color's transparency darker for the pen and markers
    color.setAlpha(255 / d_nplots);

    QPen pen(d_plot_curve[which]->pen());
    pen.setColor(color);
    d_plot_curve[which]->setPen(pen);

#if QWT_VERSION < 0x060000
    d_plot_curve[which]->setPen(pen);
    QwtSymbol sym = (QwtSymbol)d_plot_curve[which]->symbol();
    setLineMarker(which, sym.style());
#else
    QwtSymbol *sym = (QwtSymbol*)d_plot_curve[which]->symbol();
    if(sym) {
      sym->setColor(color);
      sym->setPen(pen);
      d_plot_curve[which]->setSymbol(sym);
    }
#endif
  }
}

void
HistogramDisplayPlot::setNumBins(int bins)
{
  d_bins = bins;
  
  delete [] d_xdata;
  d_xdata = new double[d_bins];
  _resetXAxisPoints(d_left, d_right);

  for(int i = 0; i < d_nplots; i++) {
    delete [] d_ydata[i];
    d_ydata[i] = new double[d_bins];
    memset(d_ydata[i], 0, d_bins*sizeof(double));

#if QWT_VERSION < 0x060000
    d_plot_curve[i]->setRawData(d_xdata, d_ydata[i], d_bins);
#else
    d_plot_curve[i]->setRawSamples(d_xdata, d_ydata[i], d_bins);
#endif
  }
}

#endif /* HISTOGRAM_DISPLAY_PLOT_C */