#include <numeric>
#include <limits>
#include <vector>
#include <map>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <iomanip>

/////////////////////// Qt includes
#include <QDebug>
#include <QObject>


#include "../exception/exceptionnotpossible.h"
#include "maptrace.h"
#include "../processing/combiners/tracepluscombiner.h"
#include "../processing/combiners/traceminuscombiner.h"
#include "../types.h"


int mapTraceMetaTypeId =
  qRegisterMetaType<pappso::MapTrace>("pappso::MapTrace");
int mapTracePtrMetaTypeId =
  qRegisterMetaType<pappso::MapTrace *>("pappso::MapTrace *");


namespace pappso
{

MapTrace::MapTrace()
{
}


MapTrace::MapTrace(
  const std::vector<std::pair<pappso_double, pappso_double>> &dataPoints)
{
  for(auto &dataPoint : dataPoints)
    {
      insert(dataPoint);
    }
}


MapTrace::MapTrace(const std::vector<DataPoint> &dataPoints)
{
  for(auto &dataPoint : dataPoints)
    {
      insert(std::pair<pappso_double, pappso_double>(dataPoint.x, dataPoint.y));
    }
}


MapTrace::MapTrace(const MapTrace &other)
  : std::map<pappso_double, pappso_double>(other)
{
}


MapTrace::MapTrace(const Trace &trace)
{
  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << " ()";

  for(auto &dataPoint : trace)
    {
      // std::cout << __FILE__ << " @ " << __LINE__ << " " << __FUNCTION__ << "
      // () "
      //<< std::setprecision(15)
      //<< "Current data point: " << dataPoint.toString().toStdString() <<
      // std::endl;

      insert(std::pair<pappso_double, pappso_double>(dataPoint.x, dataPoint.y));
    }

  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << " ()"
  //<< "After construction, map size: " << size();
}


MapTrace::~MapTrace()
{
  // Calls the destructor for each DataPoint object in the vector.
  clear();
}


size_t
MapTrace::initialize(const std::vector<pappso_double> &xVector,
                     const std::vector<pappso_double> &yVector)
{
  // Clear *this, because initialize supposes that *this will contain only the
  // data in the vectors.

  clear();

  // Sanity check
  if(xVector.size() != yVector.size())
    throw ExceptionNotPossible(
      QObject::tr("Fatal error at msrundatasettreenode.cpp "
                  "-- ERROR xVector and yVector must have the same size."
                  "Program aborted."));

  for(std::size_t iter = 0; iter < xVector.size(); ++iter)
    {
      insert(std::pair<pappso_double, pappso_double>(xVector.at(iter),
                                                     yVector.at(iter)));
    }

  return size();
}


size_t
MapTrace::initialize(const std::map<pappso_double, pappso_double> &map)
{

  // Clear *this, because initialize supposes that *this will be identical to
  // map.

  clear();

  for(auto &&pair : map)
    {
      insert(pair);
    }

  return size();
}


MapTrace &
MapTrace::operator=(const MapTrace &other)
{

  if(&other == this)
    return *this;

  // Clear *this, because initialize supposes that *this will be identical to
  // other.

  clear();

  for(auto &pair : other)
    {
      insert(pair);
    }

  return *this;
}


MapTraceSPtr
MapTrace::makeMapTraceSPtr() const
{
  return std::make_shared<MapTrace>(*this);
}


MapTraceCstSPtr
MapTrace::makeMapTraceCstSPtr() const
{
  return std::make_shared<const MapTrace>(*this);
}


std::vector<pappso_double>
MapTrace::xValues()
{
  std::vector<pappso_double> vector;

  for(auto &&pair : *this)
    vector.push_back(pair.first);

  return vector;
}


std::vector<pappso_double>
MapTrace::yValues()
{
  std::vector<pappso_double> vector;

  for(auto &&pair : *this)
    vector.push_back(pair.second);

  return vector;
}


void
MapTrace::insertOrUpdate(const DataPoint &data_point)
{

  // Try to insert the data point into the map. Check if that was done or
  // not.
  std::pair<std::map<double, double>::iterator, bool> res =
    insert(std::pair<double, double>(data_point.x, data_point.y));

  if(!res.second)
    {
      // One other same (x,y) value pair was seen already. Only increment the y
      // value.

      res.first->second += data_point.y;
    }
}


void
MapTrace::insertOrUpdate(const Trace &trace)
{
  for(const DataPoint &data_point : trace)
    insertOrUpdate(data_point);
}


Trace
MapTrace::toTrace() const
{
  Trace trace;

  for(auto &&pair : *this)
    trace.push_back(DataPoint(pair.first, pair.second));

  return trace;
}


QString
MapTrace::toString() const
{
  // Even if the spectrum is empty, we should return an empty string.
  QString text;

  for(auto &&pair : *this)
    {
// For debugging
#if 0

      QString new_data_point_text = QString("%1 %2\n")
                                      .arg(pair.first, 0, 'f', 10)
                                      .arg(pair.second, 0, 'f', 10);

      qDebug() << "new data point text:" << new_data_point_text;
      text.append(new_data_point_text);
#endif

      text.append(QString("%1 %2\n")
                    .arg(pair.first, 0, 'f', 10)
                    .arg(pair.second, 0, 'f', 10));
    }

  return text;
}


} // namespace pappso
