// Copyright 2021 Filippo Rusconi
// GPLv3+


/////////////////////// StdLib includes
#include <limits>
#include <cmath>


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


/////////////////////// Local includes
#include "selectionpolygon.h"


namespace pappso
{

SelectionPolygon::SelectionPolygon()
{
  // When we create a polygon, we create it as immense as possible, so that
  // if this polygon is not modified, any other polygon created on the basis
  // of experimental data will fit inside it.

  // See the definition of the points in the header file.
}


SelectionPolygon::SelectionPolygon(QPointF top_left_point, QPointF top_right_point)
{
  // First clear the default values points because we want to push_back
  // new points and we want to only ever have 4 points.
  m_points.clear();

  // We get only two points that provide the horizontal range of the polygon.
  // These two points show the x range of the polygon. We need to craft a
  // polygon that has:
  //
  // that specified x range and
  //
  // the widest y range possible.

  // In other words, we are crafting a 1D selection polygon.

  // top left point
  m_points.push_back(QPointF(top_left_point.x(), std::numeric_limits<double>::max()));

  // top right point
  m_points.push_back(QPointF(top_right_point.x(), std::numeric_limits<double>::max()));

  // bottom right point
  m_points.push_back(QPointF(top_right_point.x(), std::numeric_limits<double>::min()));

  // bottom left point
  m_points.push_back(QPointF(top_left_point.x(), std::numeric_limits<double>::min()));

  // Compute the min|max x|y coordinates of the polygon that will be used to
  // quickly check if a point is outside.
  computeMinMaxCoordinates();
}


SelectionPolygon::SelectionPolygon(QPointF top_left_point,
                                   QPointF top_right_point,
                                   QPointF bottom_right_point,
                                   QPointF bottom_left_point)
{
  // First clear the default values points.
  m_points.clear();

  // Attention, we need to push back the points starting top left and clockwise.

  m_points.push_back(top_left_point);
  m_points.push_back(top_right_point);
  m_points.push_back(bottom_right_point);
  m_points.push_back(bottom_left_point);

  // Compute the min|max x|y coordinates of the polygon that will be used to
  // quickly check if a point is outside.
  computeMinMaxCoordinates();
}


SelectionPolygon::SelectionPolygon(const SelectionPolygon &other)
{
  if(other.m_points.size() != static_cast<int>(PointSpec::ENUM_LAST))
    qFatal("The template selection polygon must have four points, no less, no more");

  // First clear the default values points.
  m_points.clear();

  for(int iter = 0; iter < static_cast<int>(PointSpec::ENUM_LAST); ++iter)
    {
      m_points.push_back(other.m_points[iter]);
    }

  m_minX = other.m_minX;
  m_minY = other.m_minY;

  m_maxX = other.m_maxX;
  m_maxY = other.m_maxY;
}


SelectionPolygon::~SelectionPolygon()
{
}


void
SelectionPolygon::setPoint(PointSpec point_spec, double x, double y)
{
  m_points[static_cast<int>(point_spec)].setX(x);
  m_points[static_cast<int>(point_spec)].setY(y);

  computeMinMaxCoordinates();
}


void
SelectionPolygon::setPoint(PointSpec point_spec, QPointF point)
{
  setPoint(point_spec, point.x(), point.y());

  computeMinMaxCoordinates();
}


void
SelectionPolygon::copyPoint(PointSpec point_spec_src, PointSpec point_spec_dest)
{
  QPointF src_point = getPoint(point_spec_src);
  setPoint(point_spec_dest, src_point);

  computeMinMaxCoordinates();
}


void
SelectionPolygon::set1D(double x_range_start, double x_range_end)
{
  // We get only two points that provide the horizontal range of the polygon.
  // These two points show the x range of the polygon. We need to craft a
  // polygon that has:
  //
  // that specified x range and
  //
  // the widest y range possible.

  resetPoints();

  // top left point
  setPoint(PointSpec::TOP_LEFT_POINT, QPointF(x_range_start, std::numeric_limits<double>::max()));

  // top right point
  setPoint(PointSpec::TOP_RIGHT_POINT, QPointF(x_range_end, std::numeric_limits<double>::max()));

  // bottom right point
  setPoint(PointSpec::BOTTOM_RIGHT_POINT, QPointF(x_range_end, std::numeric_limits<double>::min()));

  // bottom left point
  setPoint(PointSpec::BOTTOM_LEFT_POINT,
           QPointF(x_range_start, std::numeric_limits<double>::min()));

  // Compute the min|max x|y coordinates of the polygon that will be used to
  // quickly check if a point is outside.
  computeMinMaxCoordinates();
}


void
SelectionPolygon::set2D(QPointF top_left,
                        QPointF top_right,
                        QPointF bottom_right,
                        QPointF bottom_left)
{
  resetPoints();

  // top left point
  setPoint(PointSpec::TOP_LEFT_POINT, top_left);
  // qDebug() << "PointSpecs::TOP_LEFT_POINT:" << top_left;

  // top right point
  setPoint(PointSpec::TOP_RIGHT_POINT, top_right);
  // qDebug() << "PointSpecs::TOP_RIGHT_POINT:" << top_right;

  // bottom right point
  setPoint(PointSpec::BOTTOM_RIGHT_POINT, bottom_right);
  // qDebug() << "PointSpecs::BOTTOM_RIGHT_POINT:" << bottom_right;

  // bottom left point
  setPoint(PointSpec::BOTTOM_LEFT_POINT, bottom_left);
  // qDebug() << "PointSpecs::BOTTOM_LEFT_POINT:" << bottom_left;

  // Compute the min|max x|y coordinates of the polygon that will be used to
  // quickly check if a point is outside.
  computeMinMaxCoordinates();

  // qDebug() << toString();
}


void
SelectionPolygon::convertTo1D()
{
  // When a 2D polygon is converted to a 1D polygon, the x axis range is
  // unchanged, but the height is set to its maximum possible with the bottom
  // line at y = min and the top line at y = max.

  computeMinMaxCoordinates();

  set1D(m_minX, m_maxX);
}


QPointF
SelectionPolygon::getLeftMostPoint() const
{
  // When we say leftmost, that means that we are implicitely interesed in
  // x-axis coordinate of the points.

  QPointF temp_point(std::numeric_limits<double>::max(), 0);

  for(int iter = 0; iter < static_cast<int>(PointSpec::ENUM_LAST); ++iter)
    {
      if(m_points[iter].x() < temp_point.x())
        {
          temp_point = m_points[iter];
        }
    }

  return temp_point;
}


QPointF
SelectionPolygon::getRightMostPoint() const
{
  // When we say rightmost, that means that we are implicitely interesed in
  // x-axis coordinate of the points.

  QPointF temp_point(std::numeric_limits<double>::min(), 0);

  for(int iter = 0; iter < static_cast<int>(PointSpec::ENUM_LAST); ++iter)
    {
      if(m_points[iter].x() > temp_point.x())
        {
          temp_point = m_points[iter];
        }
    }

  return temp_point;
}


QPointF
SelectionPolygon::getTopMostPoint() const
{
  // When we say topmost or bottommost , that means that we are implicitely
  // interesed in y-axis coordinate of the points.

  QPointF temp_point(0, std::numeric_limits<double>::min());

  for(int iter = 0; iter < static_cast<int>(PointSpec::ENUM_LAST); ++iter)
    {
      if(m_points[iter].y() > temp_point.y())
        {
          temp_point = m_points[iter];
        }
    }

  return temp_point;
}


QPointF
SelectionPolygon::getBottomMostPoint() const
{
  // When we say topmost or bottommost , that means that we are implicitely
  // interesed in y-axis coordinate of the points.

  QPointF temp_point(0, std::numeric_limits<double>::max());

  for(int iter = 0; iter < static_cast<int>(PointSpec::ENUM_LAST); ++iter)
    {
      if(m_points[iter].y() < temp_point.y())
        {
          temp_point = m_points[iter];
        }
    }

  return temp_point;
}


const std::vector<QPointF> &
SelectionPolygon::getPoints() const
{
  return m_points;
}


QPointF
SelectionPolygon::getPoint(PointSpec point_spec) const
{
  return m_points[static_cast<int>(point_spec)];
}


bool
SelectionPolygon::computeMinMaxCoordinates()
{
  // Set the variable to starting values that allow easy value comparisons with
  // std::min() and std::max() for checking the x|y values below.

  m_minX = std::numeric_limits<double>::max();
  m_minY = std::numeric_limits<double>::max();
  m_maxX = std::numeric_limits<double>::min();
  m_maxY = std::numeric_limits<double>::min();

  for(int iter = 0; iter < static_cast<int>(PointSpec::ENUM_LAST); ++iter)
    {
      m_minX = std::min(m_points.at(iter).x(), m_minX);
      m_maxX = std::max(m_points.at(iter).x(), m_maxX);

      m_minY = std::min(m_points.at(iter).y(), m_minY);
      m_maxY = std::max(m_points.at(iter).y(), m_maxY);
    }

  return true;
}


bool
SelectionPolygon::computeMinMaxCoordinates(double &min_x,
                                           double &max_x,
                                           double &min_y,
                                           double &max_y) const
{
  // Set the variable to starting values that allow easy value comparisons with
  // std::min() and std::max() for checking the x|y values below.

  min_x = std::numeric_limits<double>::max();
  min_y = std::numeric_limits<double>::max();
  max_x = std::numeric_limits<double>::min();
  max_y = std::numeric_limits<double>::min();

  for(int iter = 0; iter < static_cast<int>(PointSpec::ENUM_LAST); ++iter)
    {
      min_x = std::min(m_points.at(iter).x(), min_x);
      max_x = std::max(m_points.at(iter).x(), max_x);

      min_y = std::min(m_points.at(iter).y(), min_y);
      max_y = std::max(m_points.at(iter).y(), max_y);
    }

  // qDebug() << "min_x:" << min_x << "max_x:" << max_x << "min_y:" << min_y
  //<< "max_y:" << max_y;

  return true;
}


double
SelectionPolygon::width(bool &ok) const
{
  double min_x;
  double min_y;
  double max_x;
  double max_y;

  computeMinMaxCoordinates(min_x, max_x, min_y, max_y);

  ok = true;
  return max_x - min_x;
}


double
SelectionPolygon::height(bool &ok) const
{
  double min_x;
  double min_y;
  double max_x;
  double max_y;

  computeMinMaxCoordinates(min_x, max_x, min_y, max_y);

  ok = true;
  return max_y - min_y;
}


bool
SelectionPolygon::rangeX(double &range_start, double &range_end) const
{
  double min_y = std::numeric_limits<double>::max();
  double max_y = std::numeric_limits<double>::min();

  return computeMinMaxCoordinates(range_start, range_end, min_y, max_y);
}


bool
SelectionPolygon::rangeY(double &range_start, double &range_end) const
{
  double min_x = std::numeric_limits<double>::max();
  double max_x = std::numeric_limits<double>::min();

  return computeMinMaxCoordinates(min_x, max_x, range_start, range_end);
}


bool
SelectionPolygon::range(Enums::Axis axis, double &range_start, double &range_end) const
{
  if(axis == Enums::Axis::x)
    return rangeX(range_start, range_end);
  else if(axis == Enums::Axis::y)
    return rangeY(range_start, range_end);

  return false;
}


// All this is valid only if the polygon has four sides.
//
// Transposing means that we take each point and permute x with y.
// During transposition, the TOP_RIGHT_POINT BOTTOM_LEFT_POINT are invariant.
//
// Transposition is like rotating the polygon arount the
// BOTTOM_LEFT_POINT--TOP_RIGHT_POINT axis, which make them invariant and
// permutates TOP_LEFT_POINT with BOTTOM_RIGHT_POINT.
//
// If iteration in the points is clockwise before, iteration below
// counter-clockwise after transposition, that is:
// TOP_LEFT_POINT becomes BOTTOM_RIGHT_POINT
//
SelectionPolygon
SelectionPolygon::transpose() const
{
  SelectionPolygon selection_polygon;

  // Make sure we do this for a polygon with four sides.

  if(m_points.size() != static_cast<int>(PointSpec::ENUM_LAST))
    qFatal("The polygon must have four points, no less, no more");

  // The two invariant points, that is, the two points that do no change
  // position in the polygon corners. Of course, x becomes y.
  selection_polygon.setPoint(
    PointSpec::TOP_RIGHT_POINT,
    QPointF(getPoint(PointSpec::TOP_RIGHT_POINT).y(), getPoint(PointSpec::TOP_RIGHT_POINT).x()));

  selection_polygon.setPoint(PointSpec::BOTTOM_LEFT_POINT,
                             QPointF(getPoint(PointSpec::BOTTOM_LEFT_POINT).y(),
                                     getPoint(PointSpec::BOTTOM_LEFT_POINT).x()));

  // The two other points.

  selection_polygon.setPoint(
    PointSpec::BOTTOM_RIGHT_POINT,
    QPointF(getPoint(PointSpec::TOP_LEFT_POINT).y(), getPoint(PointSpec::TOP_LEFT_POINT).x()));

  selection_polygon.setPoint(PointSpec::TOP_LEFT_POINT,
                             QPointF(getPoint(PointSpec::BOTTOM_RIGHT_POINT).y(),
                                     getPoint(PointSpec::BOTTOM_RIGHT_POINT).x()));

  return selection_polygon;
}


bool
SelectionPolygon::contains(const QPointF &tested_point) const
{
  // Easy check: if the point lies outside of most external limits of the
  // polygon, return false.

  if(tested_point.x() < m_minX || tested_point.x() > m_maxX || tested_point.y() < m_minY ||
     tested_point.y() > m_maxY)
    {
      // qDebug() << "Testing point:" << tested_point
      //<< "aginst polygon:" << toString()
      //<< "is out of x and y ranges.";
      return false;
    }

  // There are two situations:
  //
  // 1. The selection polygon is a rectangle, we can check the tested_point very
  // easily.
  //
  // 2. The selection polygon is a skewed rectangle, that is, it is a
  // parallelogram, we need to really use the point-in-polygon algorithm.

  if(isRectangle())
    {
      // qDebug() << "Selection polygon *is* rectangle.";

      double x = tested_point.x();
      double y = tested_point.y();

      // return (x >= getPoint(PointSpecs::TOP_LEFT_POINT).x() &&
      // x <= getPoint(PointSpecs::TOP_RIGHT_POINT).x() &&
      // y >= getPoint(PointSpecs::BOTTOM_LEFT_POINT).y() &&
      // y <= getPoint(PointSpecs::TOP_LEFT_POINT).y());

      bool res = x >= m_minX && x <= m_maxX && y >= m_minY && y <= m_maxY;

      // qDebug() << qSetRealNumberPrecision(10) << "Returning: " << res
      //<< "for point:" << tested_point
      //<< "and selection polygon:" << toString();

      return res;
    }

  // qDebug() << "Testing point:" << tested_point
  //<< "aginst polygon:" << toString()
  //<< "is tested against a skewed selection polygon rectangle.";

  // At this point, we know the selection polygon is not rectangle, we have to
  // make the real check using the point-in-polygon algorithm.

  // This code is inspired by the work described here:
  // https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html

  // int pnpoly(int vertex_count, float *vertx, float *verty, float testx,
  // float testy)

  int i          = 0;
  int j          = 0;
  bool is_inside = false;

  int vertex_count = m_points.size();

  for(i = 0, j = vertex_count - 1; i < vertex_count; j = i++)
    {
      if(((m_points.at(i).y() > tested_point.y()) != (m_points.at(j).y() > tested_point.y())) &&
         (tested_point.x() < (m_points.at(j).x() - m_points.at(i).x()) *
                                 (tested_point.y() - m_points.at(i).y()) /
                                 (m_points.at(j).y() - m_points.at(i).y()) +
                               m_points.at(i).x()))
        is_inside = !is_inside;
    }

  // if(is_inside)
  // qDebug() << "Testing point:" << tested_point
  //<< "aginst polygon:" << toString() << "turns out be in.";
  // else
  // qDebug() << "Testing point:" << tested_point
  //<< "aginst polygon:" << toString() << "turns out be out.";

  return is_inside;
}


bool
SelectionPolygon::contains(const SelectionPolygon &selection_polygon) const
{
  //  A polygon is inside another polygon if all its points are inside the
  //  polygon.

  bool is_inside = true;

  for(int iter = 0; iter < static_cast<int>(PointSpec::ENUM_LAST); ++iter)
    {
      if(!contains(selection_polygon.getPoint(static_cast<PointSpec>(iter))))
        is_inside = false;
    }

  return is_inside;
}


SelectionPolygon &
SelectionPolygon::operator=(const SelectionPolygon &other)
{
  if(this == &other)
    return *this;

  if(other.m_points.size() != static_cast<int>(PointSpec::ENUM_LAST))
    qFatal("Programming error.");

  if(m_points.size() != static_cast<int>(PointSpec::ENUM_LAST))
    qFatal("Programming error.");

  for(int iter = 0; iter < static_cast<int>(PointSpec::ENUM_LAST); ++iter)
    m_points[iter] = other.m_points[iter];

  m_minX = other.m_minX;
  m_minY = other.m_minY;

  m_maxX = other.m_maxX;
  m_maxY = other.m_maxY;

  return *this;
}


void
SelectionPolygon::resetPoints()
{
  // Reset the points exactly as they were set upon construction of an empty
  // polygon.

  m_points[0] = QPointF(std::numeric_limits<double>::min(), std::numeric_limits<double>::max());
  m_points[0] = QPointF(std::numeric_limits<double>::max(), std::numeric_limits<double>::max());
  m_points[0] = QPointF(std::numeric_limits<double>::max(), std::numeric_limits<double>::min());
  m_points[0] = QPointF(std::numeric_limits<double>::min(), std::numeric_limits<double>::max());
}


bool
SelectionPolygon::is1D() const
{
  // qDebug() << "Selection polygon:" << toString();

  bool ok = false;

  double width_value = width(ok);
  if(!ok)
    return false;

  double height_value = height(ok);
  if(!ok)
    return false;

  // qDebug() << "Width and height computations succeeded:"
  //<< "width:" << width_value << "height:" << height_value;

  // A polygon is mono-dimensional if it has both non-0 width and no (max-min)
  // width AND if the height is 0 or (max-min).
  return ((width_value > 0 &&
           width_value < std::numeric_limits<double>::max() - std::numeric_limits<double>::min()) &&
          (height_value == 0 || height_value == std::numeric_limits<double>::max() -
                                                  std::numeric_limits<double>::min()));
}


bool
SelectionPolygon::is2D() const
{
  // A selection polygon can behave like a line segment if the bottom side
  // confounds with the top side.

  bool ok = false;

  double width_value = width(ok);
  if(!ok)
    return false;

  double height_value = height(ok);
  if(!ok)
    return false;

  // A polygon is two-dimensional if it has both non-0 width and no (max-min)
  // width AND same for height.
  return ((width_value > 0 &&
           width_value < std::numeric_limits<double>::max() - std::numeric_limits<double>::min()) &&
          (height_value > 0 &&
           height_value < std::numeric_limits<double>::max() - std::numeric_limits<double>::min()));
}


bool
SelectionPolygon::isRectangle() const
{
  // A skewed rectangle polygon has the following conditions verified:
  //
  // 1. If its left|right sides are vertical, then its top|bottom lines are
  // *not* horizontal.
  //
  // 2 If its top|bottom lines are horizontal, then its left|right sides are
  // *not* vertical.
  //
  // 3. Then, if a selection polygon is rectangle, its top|bottom lines are
  // horizontal and its left|right lines are vertical.

  // A line is vertical if its two defining points have the same X.
  // A line is horizontal if its two defining points have the same Y.

  // Try the horiontal top|bottom lines.

  if(getPoint(PointSpec::TOP_LEFT_POINT).y() == getPoint(PointSpec::TOP_RIGHT_POINT).y() &&
     getPoint(PointSpec::BOTTOM_LEFT_POINT).y() == getPoint(PointSpec::BOTTOM_RIGHT_POINT).y())
    {
      // We have horizontal top|bottom lines

      // Try the vertical lines

      if(getPoint(PointSpec::TOP_LEFT_POINT).x() == getPoint(PointSpec::BOTTOM_LEFT_POINT).x() &&
         getPoint(PointSpec::TOP_RIGHT_POINT).x() == getPoint(PointSpec::BOTTOM_RIGHT_POINT).x())
        {
          // The top|bottom lines are vertical

          return true;
        }
    }

  return false;
}


QString
SelectionPolygon::toString() const
{
  // By essence, a selection polygon is designed to always have 4 points.

  if(m_points.size() != static_cast<int>(PointSpec::ENUM_LAST))
    qFatal("Programming error.");

  // qDebug() << "size:" << m_points.size();

  QString text = "Selection polygon points, from top left, clockwise\n";

  for(int iter = 0; iter < static_cast<int>(PointSpec::ENUM_LAST); ++iter)
    {
      QPointF iter_point = m_points[iter];

      QString x_string = "NOT_SET";

      if(iter_point.x() != std::numeric_limits<double>::min() &&
         iter_point.x() != std::numeric_limits<double>::max())
        x_string = QString("%1").arg(iter_point.x(), 0, 'f', 10);

      QString y_string = "NOT_SET";

      if(iter_point.y() != std::numeric_limits<double>::min() &&
         iter_point.y() != std::numeric_limits<double>::max())
        y_string = QString("%1").arg(iter_point.y(), 0, 'f', 10);

      text += QString("(%1,%2)\n").arg(x_string).arg(y_string);
    }

  if(m_minX != std::numeric_limits<double>::min() && m_minX != std::numeric_limits<double>::max())
    text += QString("minX: %1 - ").arg(m_minX, 0, 'f', 10);
  else
    text += QString("minX: NOT_SET - ");

  if(m_maxX != std::numeric_limits<double>::min() && m_maxX != std::numeric_limits<double>::max())
    text += QString("maxX: %1 - ").arg(m_maxX, 0, 'f', 10);
  else
    text += QString("maxX: NOT_SET - ");

  if(m_minY != std::numeric_limits<double>::min() && m_minY != std::numeric_limits<double>::max())
    text += QString("minY: %1 - ").arg(m_minY, 0, 'f', 10);
  else
    text += QString("minY: NOT_SET - ");

  if(m_maxY != std::numeric_limits<double>::min() && m_maxY != std::numeric_limits<double>::max())
    text += QString("maxY: %1 - ").arg(m_maxY, 0, 'f', 10);
  else
    text += QString("maxY: NOT_SET - ");

  return text;
}


QString
SelectionPolygon::toShort4PointsString() const
{
  // By essence, a selection polygon is designed to always have 4 points.

  if(m_points.size() != static_cast<int>(PointSpec::ENUM_LAST))
    qFatal("Programming error.");

  // qDebug() << "size:" << m_points.size();

  QString text = "[";

  QString x_string = "NOT_SET";
  QString y_string = "NOT_SET";

  // There are two situations:
  //
  // 1. The selection polygon is 1D, we only need to provide two points
  //
  // 2. The selection polygon is 2D, we need to provide four points.

  if(is1D())
    {
      text += QString("(%1,%2)").arg(getLeftMostPoint().x()).arg("NOT_SET");
      text += QString("(%1,%2)").arg(getRightMostPoint().x()).arg("NOT_SET");
    }
  else
    {
      for(int iter = 0; iter < static_cast<int>(PointSpec::ENUM_LAST); ++iter)
        {
          QPointF iter_point = m_points[iter];


          if(iter_point.x() != std::numeric_limits<double>::min() &&
             iter_point.x() != std::numeric_limits<double>::max())
            x_string = QString("%1").arg(iter_point.x(), 0, 'f', 3);

          if(iter_point.y() != std::numeric_limits<double>::min() &&
             iter_point.y() != std::numeric_limits<double>::max())
            y_string = QString("%1").arg(iter_point.y(), 0, 'f', 3);

          text += QString("(%1,%2)").arg(x_string).arg(y_string);
        }
    }

  text += "]";

  return text;
}


void
SelectionPolygon::debugAlgorithm(const SelectionPolygon &selection_polygon,
                                 const QPointF &tested_point)
{
  bool is_point_inside = false;

  QString debug_string;

  is_point_inside = selection_polygon.contains(tested_point);
  debug_string    = QString("(%1,%2) is inside: %3")
                   .arg(tested_point.x(), 0, 'f', 10)
                   .arg(tested_point.y(), 0, 'f', 10)
                   .arg(is_point_inside ? "true" : "false");
  qDebug().noquote() << debug_string;
}
} // namespace pappso

#if 0

// This is to test the algo that check if a point is left of a line or right of
// it or onto it. In this implementation, two lines define the vertical sides of
// a polygon (square to ease analysis) and if the point is on the left line, then it
// is considered ok, that is, that it is on the right side of the line and if it
// is on the right line, then it is considered ok, that is that it is on the
// left side of the line. If both conditions are ok, then the point is inside
// the vertical lines of the polygon.

  qDebug();

  pappso::SelectionPolygon selection_polygon(QPointF(22.4, 473),
                                             QPointF(28.9, 473),
                                             QPointF(28.9, 250),
                                             QPointF(22.4, 250));
  qDebug() << "The test selection polygon:" << selection_polygon.toString();

  std::vector<QPointF> test_points;

  test_points.push_back(QPointF(25, 250));
  test_points.push_back(QPointF(22.3, 362));
  test_points.push_back(QPointF(22.4, 473));
  test_points.push_back(QPointF(22.4, 473.5));
  test_points.push_back(QPointF(25, 250));
  test_points.push_back(QPointF(25, 250.5));
  test_points.push_back(QPointF(25, 360));
  test_points.push_back(QPointF(28.9, 250));
  test_points.push_back(QPointF(29, 250));
  test_points.push_back(QPointF(29, 360));
  test_points.push_back(QPointF(28.9, 473));
  test_points.push_back(QPointF(28.9, 473.5));
  test_points.push_back(QPointF(20, 200));
  test_points.push_back(QPointF(20, 600));
  test_points.push_back(QPointF(35, 200));
  test_points.push_back(QPointF(35, 600));

  // double res = sideofline(XX;YY;xA;yA;xB;yB) =
  // (xB-xA) * (YY-yA) - (yB-yA) * (XX-xA)

  // If res == 0, the point is on the line
  // If rest < 0, the point is on the right of the line
  // If rest > 0, the point is on the left of the line

  for(auto &&data_point : test_points)
    {
      // Left vertical line of the polygon

      // Bottom point
      double xA_left =
        selection_polygon.getPoint(pappso::PointSpecs::BOTTOM_LEFT_POINT).x();
      double yA_left =
        selection_polygon.getPoint(pappso::PointSpecs::BOTTOM_LEFT_POINT).y();

      // Top point
      double xB_left =
        selection_polygon.getPoint(pappso::PointSpecs::TOP_LEFT_POINT).x();
      double yB_left =
        selection_polygon.getPoint(pappso::PointSpecs::TOP_LEFT_POINT).y();

      if((xB_left - xA_left) * (data_point.y() - yA_left) -
           (yB_left - yA_left) * (data_point.x() - xA_left) >
         0)
        {
          // The point is left of the left line. We can remove the point
          // from the mass spectrum.

          qDebug() << qSetRealNumberPrecision(10)
                   << "Filtered out point (left of left line):"
                   << data_point.x() << "-" << data_point.y();
          continue;
        }
      else
        {
          qDebug() << qSetRealNumberPrecision(10)
                   << "Kept point (right of left line):" << data_point.x()
                   << "-" << data_point.y();
        }

      // Right vertical line of the polygon

      // Bottom point
      double xA_right =
        selection_polygon.getPoint(pappso::PointSpecs::BOTTOM_RIGHT_POINT).x();
      double yA_right =
        selection_polygon.getPoint(pappso::PointSpecs::BOTTOM_RIGHT_POINT).y();

      // Top point
      double xB_right =
        selection_polygon.getPoint(pappso::PointSpecs::TOP_RIGHT_POINT).x();
      double yB_right =
        selection_polygon.getPoint(pappso::PointSpecs::TOP_RIGHT_POINT).y();

      if((xB_right - xA_right) * (data_point.y() - yA_right) -
           (yB_right - yA_right) * (data_point.x() - xA_right) <
         0)
        {
          qDebug() << qSetRealNumberPrecision(10)
                   << "Filtered out point (right of right line):"
                   << data_point.x() << "-" << data_point.y();
        }
      else
        {
          qDebug() << qSetRealNumberPrecision(10)
                   << "Definitively kept point (left of right line):"
                   << data_point.x() << "-" << data_point.y();
        }
    }
#endif


#if 0
  // This is code to test the algorithm we have to establish if a given
  // point is inside a selection polygon.

  // First polygon that is square.
  SelectionPolygon first_polygon(
    QPointF(3, 8), QPointF(12, 8), QPointF(12, 3), QPointF(3, 3));

  qDebug() << "square rectangle polygon: " << first_polygon.toString();

  qDebug() << "outside";

  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(2,1));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(2.999999,5));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(2.999999,8.000001));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(2,5));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(2,9));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(7,1));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(7,8.0000001));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(12,1));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(12.0000001,3));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(14,3));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(14,5));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(14,8));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(14,9));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(7,9));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(7,2.9999999));

  qDebug() << "on the lines";

  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(3,4));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(7,3));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(12,3));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(12,7));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(12,8));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(7,8));

  qDebug() << "inside";

  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(4,4));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(3.00001, 3.00001));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(7,4));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(7,3.1));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(11,5));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(11.99999,5));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(7,7.9));
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());

  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());

  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());

  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());

  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());


  // Second polygon that is skewed.
  SelectionPolygon second_polygon(
    QPointF(9, 8), QPointF(12, 8), QPointF(6, 2), QPointF(3, 2));

  qDebug() << "skewed  rectangle polygon: " << second_polygon.toString();

  qDebug() << "outside";

  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(2,1));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(2.999999,1.999999));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(2.999999,3));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(2.999999,8.000001));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(2,5));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(2,9));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(6,5.0000001));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(7,1));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(7,2.999999));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(9,4.999999));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(9,8.0000001));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(12.00000001,8));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(12,7.999999));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(14,3));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(14,5));

  qDebug() << "on the lines";

  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(3,2));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(6,5));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(12,8));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(9,8));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(9,5));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(11,7));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(7,6));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(4,3));

  qDebug() << "inside";

  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(3.00001,2.000001));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(4,2.000001));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(6.000001,2.00003));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(7,4));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(8.99999,5));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(10,7.99999));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(5,3));
  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(5,3.99999));
#endif


#if 0

void
SelectionPolygon::reorderPoints()
{
  // We want to make sure that we actually have the right QPointF instances at
  // the right corners.

  computeMinMaxCoordinates();

  // Start by finding a point almost centered in the polygon. Not exactly
  // centered because our polygon can be squared and we'll look at angles
  // between the center-point->polygon-point_n and
  // center-point->polygon-point_n'.

  double PI = 3.14159265358979323846;
  QPointF center_point(0, 0);

  for(auto &point : m_points)
    {
      center_point.setX(center_point.x() + point.x());
      center_point.setY(center_point.y() + point.y());
    }

  center_point.setX(center_point.x() / m_points.size());
  center_point.setY(center_point.y() / m_points.size());

  // For a rectangle polygon, that would be the exact center and the angles
  // between the line segments would be similar two-by-two. So we need to move
  // the center_point a bit.

  double distance_x = center_point.x() - m_minX;
  double distance_y = center_point.y() - m_minY;

  center_point.setX(center_point.x() - (distance_x / 20));
  center_point.setY(center_point.y() - (distance_y / 20));

  std::sort(m_points.begin(),
            m_points.end(),
            [center_point, PI](const QPointF &a, const QPointF &b) -> bool {
              double a1 = std::fmod(
                std::atan2(a.x() - center_point.x(), a.y() - center_point.y()) *
                    180.0 / PI +
                  360,
                (double)360);

              double a2 = std::fmod(
                std::atan2(b.x() - center_point.x(), b.y() - center_point.y()) *
                    180.0 / PI +
                  360,
                (double)360);

              // Original version that had problems because arguments to '%'
              // were double and int. fmod() is % for double.
              //(std::atan2(b.x() - center_point.x(), b.y() - center_point.y())
              //* 180.0 / PI + 360) % 360;

              return (int)(a1 - a2);
            });
}

#endif
