// Copyright 2021 Filippo Rusconi
// GPLv3+


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


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


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


namespace pappso
{

IntegrationScopeRhomb::IntegrationScopeRhomb() : IntegrationScopeBase()
{
  // qDebug() << "Constructing" << this;
}

IntegrationScopeRhomb::IntegrationScopeRhomb(const std::vector<QPointF> &points) : m_points(points)
{
  // qDebug() << "Constructing" << this << "with" << m_points.size() << "points.";
}

IntegrationScopeRhomb::IntegrationScopeRhomb(const std::vector<QPointF> &points,
                                             Enums::DataKind data_kind_x,
                                             Enums::DataKind data_kind_y)
  : IntegrationScopeBase(), m_points(points), m_dataKindX(data_kind_x), m_dataKindY(data_kind_y)
{
  // qDebug() << "Constructing" << this << "with" << m_points.size() << "points."
  //          << "data_kind_x:" << static_cast<int>(data_kind_x)
  //          << "data_kind_y:" << static_cast<int>(data_kind_y);
}
IntegrationScopeRhomb::IntegrationScopeRhomb(const IntegrationScopeRhomb &other)
  : IntegrationScopeBase(),
    m_points(other.m_points),
    m_dataKindX(other.m_dataKindX),
    m_dataKindY(other.m_dataKindY)
{
}

IntegrationScopeRhomb::~IntegrationScopeRhomb()
{
  // qDebug() << "Destructing" << this;
}

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

  m_points.assign(other.m_points.begin(), other.m_points.end());

  m_dataKindX = other.m_dataKindX;
  m_dataKindY = other.m_dataKindY;

  return *this;
}

std::size_t
IntegrationScopeRhomb::addPoint(QPointF point)
{
  m_points.push_back(point);
  return m_points.size();
}

bool
IntegrationScopeRhomb::getPoint([[maybe_unused]] QPointF &point) const
{
  return false;
}

bool
IntegrationScopeRhomb::getPoints(std::vector<QPointF> &points) const
{
  points.clear();
  points.assign(m_points.begin(), m_points.end());
  return true;
}

IntegrationScopeFeatures
IntegrationScopeRhomb::getTopMostPoint(QPointF &point) const
{
  if(m_points.size() < 4)
    qFatal("The rhomboid has not four points.");

  double top_most_y_value = std::numeric_limits<double>::min();

  for(auto &the_point : m_points)
    {
      if(the_point.y() > top_most_y_value)
        {
          top_most_y_value = the_point.y();
          point            = the_point;
        }
    }

  // Necessarily, whe have a top most point (greatest y of all).
  return IntegrationScopeFeatures::SUCCESS;
}

IntegrationScopeFeatures
IntegrationScopeRhomb::getTopMostPoints(std::vector<QPointF> &points) const
{
  if(m_points.size() < 4)
    qFatal("The rhomboid has not four points.");

  // Depending on the horiz or vert quality of the scope we are going to return
  // 2 or 1 point, respectively.

  points.clear();

  QPointF point;

  if(!(getTopMostPoint(point) & IntegrationScopeFeatures::SUCCESS))
    qFatal("Failed to get the top most point.");

  // Store that point immediately.
  points.push_back(point);

  // Now that we know at least one of the top most points, check if there are
  // other points having same y and different x. Note that one specific case
  // is when the rhomboid is flat on the x axis, in which case all the points
  // have the same y value. We will thus return 4 points. In all the other
  // cases, we return 2 points if the rhomboid is horizontal and 1 point if the
  // rhomboid is vertical.

  for(auto &the_point : m_points)
    {
      if(the_point == point)
        continue;

      if(the_point.y() == point.y())
        {
          // We are handling a horizontal rhomboid.
          points.push_back(the_point);
        }
    }

  uint temp = 0;

  if(points.size() == 1)
    temp |= IntegrationScopeFeatures::RHOMBOID_VERTICAL;
  else if(points.size() == 2)
    temp |= IntegrationScopeFeatures::RHOMBOID_HORIZONTAL;
  else if(points.size() > 2)
    temp |= IntegrationScopeFeatures::FLAT_ON_X_AXIS;

  temp |= IntegrationScopeFeatures::SUCCESS;

  return static_cast<IntegrationScopeFeatures>(temp);
}

IntegrationScopeFeatures
IntegrationScopeRhomb::getBottomMostPoint(QPointF &point) const
{
  if(m_points.size() < 4)
    qFatal("The rhomboid has not four points.");

  double bottom_most_y_value = std::numeric_limits<double>::max();

  for(auto &the_point : m_points)
    {
      if(the_point.y() < bottom_most_y_value)
        {
          bottom_most_y_value = the_point.y();
          point               = the_point;
        }
    }

  return IntegrationScopeFeatures::SUCCESS;
}

IntegrationScopeFeatures
IntegrationScopeRhomb::getBottomMostPoints(std::vector<QPointF> &points) const
{
  if(m_points.size() < 4)
    qFatal("The rhomboid has not four points.");

  // Depending of the horiz or vert quality of the scope we are going to return
  // 1 or 2 points, respectively.

  points.clear();

  QPointF point;

  if(getBottomMostPoint(point) == IntegrationScopeFeatures::FAILURE)
    qFatal("Failed to get the bottom most point.");

  // Store that point immediately.
  points.push_back(point);

  // Now that we know at least one of the bottom most points, check if there are
  // other points having same y and different x. Note that one specific case
  // is when the rhomboid is flat on the x axis, in which case all the points
  // have the same y value. We will thus return 4 points. In all the other
  // cases, we return 2 points if the rhomboid is horizontal and 1 point if the
  // rhomboid is vertical.

  for(auto &the_point : m_points)
    {
      if(the_point == point)
        continue;

      if(the_point.y() == point.y())
        {
          // We are handling a vertical rhomboid.
          points.push_back(the_point);
        }
    }

  uint temp = 0;

  if(points.size() == 1)
    temp |= static_cast<int>(IntegrationScopeFeatures::RHOMBOID_VERTICAL);
  else if(points.size() == 2)
    temp |= static_cast<int>(IntegrationScopeFeatures::RHOMBOID_HORIZONTAL);
  else if(points.size() > 2)
    temp |= static_cast<int>(IntegrationScopeFeatures::FLAT_ON_X_AXIS);

  temp |= static_cast<int>(IntegrationScopeFeatures::SUCCESS);

  return static_cast<IntegrationScopeFeatures>(temp);
}

IntegrationScopeFeatures
IntegrationScopeRhomb::getLeftMostPoint(QPointF &point) const
{
  if(m_points.size() < 4)
    qFatal("The rhomboid has not four points.");

  double left_most_x = std::numeric_limits<double>::max();

  for(auto &the_point : m_points)
    {
      if(the_point.x() < left_most_x)
        {
          left_most_x = the_point.x();
          point       = the_point;
        }
    }

  return IntegrationScopeFeatures::SUCCESS;
}

IntegrationScopeFeatures
IntegrationScopeRhomb::getLeftMostPoints(std::vector<QPointF> &points) const
{
  if(m_points.size() < 4)
    qFatal("The rhomboid has not four points.");

  // Depending of the horiz or vert quality of the scope we are going to return
  // 1 or 2 points, respectively.

  points.clear();

  QPointF point;

  if(getLeftMostPoint(point) == IntegrationScopeFeatures::FAILURE)
    qFatal("Failed to get at least one left most point.");

  // Store that point immediately.
  points.push_back(point);

  // Now that we know at least one of the left most points, check if there are
  // other points having same x and different y. Note that one specific case
  // is when the rhomboid is flat on the y axis, in which case all the points
  // have the same x value. We will thus return 4 points. In all the other
  // cases, we return 1 point if the rhomboid is horizontal and 2 points if the
  // rhomboid is vertical.

  for(auto &the_point : m_points)
    {
      if(the_point == point)
        continue;

      if(the_point.x() == point.x())
        {
          // We are handling a vertical rhomboid.
          points.push_back(the_point);
        }
    }

  uint temp = 0;

  if(points.size() == 1)
    temp |= static_cast<int>(IntegrationScopeFeatures::RHOMBOID_HORIZONTAL);
  else if(points.size() == 2)
    temp |= static_cast<int>(IntegrationScopeFeatures::RHOMBOID_VERTICAL);
  else if(points.size() > 2)
    temp |= static_cast<int>(IntegrationScopeFeatures::FLAT_ON_Y_AXIS);

  temp |= static_cast<int>(IntegrationScopeFeatures::SUCCESS);

  return static_cast<IntegrationScopeFeatures>(temp);
}

IntegrationScopeFeatures
IntegrationScopeRhomb::getRightMostPoint(QPointF &point) const
{
  if(m_points.size() < 4)
    qFatal("The rhomboid has not four points.");

  double greatest_x = std::numeric_limits<double>::min();

  for(auto &the_point : m_points)
    {
      if(the_point.x() > greatest_x)
        {
          greatest_x = the_point.x();
          point      = the_point;
        }
    }

  return IntegrationScopeFeatures::SUCCESS;
}

IntegrationScopeFeatures
IntegrationScopeRhomb::getRightMostPoints(std::vector<QPointF> &points) const
{
  if(m_points.size() < 4)
    qFatal("The rhomboid has not four points.");

  // Depending of the horiz or vert quality of the scope we are going to return
  // 1 or 2 points, respectively.

  points.clear();

  QPointF point;

  if(getRightMostPoint(point) == IntegrationScopeFeatures::FAILURE)
    qFatal("Failed to get at least one left most point.");

  // Store that point immediately.
  points.push_back(point);

  // Now that we know at least one of the right most points, check if there are
  // other points having same x and different y. Note that one specific case
  // is when the rhomboid is flat on the y axis, in which case all the points
  // have the same x value. We will thus return 4 points. In all the other
  // cases, we return 1 point if the rhomboid is horizontal and 2 points if the
  // rhomboid is vertical.

  for(auto &the_point : m_points)
    {
      if(the_point == point)
        continue;

      if(the_point.x() == point.x())
        {
          // We are handling a vertical rhomboid.
          points.push_back(the_point);
        }
    }

  uint temp = 0;

  if(points.size() == 1)
    temp |= static_cast<int>(IntegrationScopeFeatures::RHOMBOID_HORIZONTAL);
  else if(points.size() == 2)
    temp |= static_cast<int>(IntegrationScopeFeatures::RHOMBOID_VERTICAL);
  else if(points.size() > 2)
    temp |= static_cast<int>(IntegrationScopeFeatures::FLAT_ON_Y_AXIS);

  temp |= static_cast<int>(IntegrationScopeFeatures::SUCCESS);

  return static_cast<IntegrationScopeFeatures>(temp);
}

IntegrationScopeFeatures
IntegrationScopeRhomb::getLeftMostTopPoint(QPointF &point) const
{
  if(m_points.size() < 4)
    qFatal("The rhomboid has not four points.");

  std::vector<QPointF> points;

  // Try the top most points, which will tell us if the rhomboid is horizontal
  // or not.

  IntegrationScopeFeatures scope_features = getTopMostPoints(points);

  if(scope_features == IntegrationScopeFeatures::FAILURE)
    qFatal("Failed to get the top most points.");

  if(scope_features & IntegrationScopeFeatures::RHOMBOID_HORIZONTAL)
    {
      // We should have gotten 2 points.

      if(points.size() != 2)
        qFatal("We should have gotten two points.");

      if(points.at(0).x() < points.at(1).x())
        point = points.at(0);
      else
        point = points.at(1);
    }
  else if(scope_features & IntegrationScopeFeatures::RHOMBOID_VERTICAL)
    {
      // In this case, we need to ask for the left most points. We'll have to
      // check the
      // results again!

      scope_features = getLeftMostPoints(points);

      if(scope_features == IntegrationScopeFeatures::FAILURE)
        qFatal("Failed to get the left most points.");

      if(scope_features & IntegrationScopeFeatures::RHOMBOID_VERTICAL)
        {
          // We should have gotten 2 points.

          if(points.size() != 2)
            qFatal("We should have gotten two points.");

          if(points.at(0).y() > points.at(1).y())
            point = points.at(0);
          else
            point = points.at(1);
        }
      else if(scope_features & IntegrationScopeFeatures::FLAT_ON_Y_AXIS)
        {
          // It is possible that the user has rotated the vertical rhomboid
          // such that all the points are aligned on the y axis (all have the
          // same x axis value). This is not an error condition. All we do is
          // return scope_features so the caller understands the situations.
        }
      else
        qFatal("This point should never be reached.");
    }
  else if(scope_features & IntegrationScopeFeatures::FLAT_ON_X_AXIS)
    {
      // This is not an error condition. All we do is return scope_features
      // so the caller understands the situations.
    }
  else
    qFatal("This point should never be reached.");

  return scope_features;
}

IntegrationScopeFeatures
IntegrationScopeRhomb::getLeftMostBottomPoint(QPointF &point) const
{
  if(m_points.size() < 4)
    qFatal("The rhomboid has not four points.");

  std::vector<QPointF> points;

  // Try the bottom most points, which will tell us if the rhomboid is
  // horizontal or not.

  IntegrationScopeFeatures scope_features = getBottomMostPoints(points);

  if(scope_features == IntegrationScopeFeatures::FAILURE)
    qFatal("Failed to get the bottom most points.");

  if(scope_features & IntegrationScopeFeatures::RHOMBOID_HORIZONTAL)
    {
      // We should have gotten 2 points.

      if(points.size() != 2)
        qFatal("We should have gotten two points.");

      if(points.at(0).x() < points.at(1).x())
        point = points.at(0);
      else
        point = points.at(1);
    }
  else if(scope_features & IntegrationScopeFeatures::RHOMBOID_VERTICAL)
    {
      // In this case, we need to ask for the left most points. We'll have to
      // check the results again!

      scope_features = getLeftMostPoints(points);

      if(!(scope_features & IntegrationScopeFeatures::SUCCESS))
        qFatal("Failed to get the left most points.");

      if(scope_features & IntegrationScopeFeatures::RHOMBOID_VERTICAL)
        {
          // We should have gotten 2 points.

          if(points.size() != 2)
            qFatal("We should have gotten two points.");

          if(points.at(0).y() < points.at(1).y())
            point = points.at(0);
          else
            point = points.at(1);
        }
      else if(scope_features & IntegrationScopeFeatures::FLAT_ON_Y_AXIS)
        {
          // It is possible that the user has rotated the vertical rhomboid
          // such that all the points are aligned on the y axis (all have the
          // same x axis value). This is not an error condition. All we do is
          // return scope_features so the caller understands the situations.
        }
      else
        qFatal("This point should never be reached.");
    }
  else if(scope_features & IntegrationScopeFeatures::FLAT_ON_X_AXIS)
    {
      // This is not an error condition. All we do is return scope_features
      // so the caller understands the situations.
    }
  else
    qFatal("This point should never be reached.");

  return scope_features;
}

IntegrationScopeFeatures
IntegrationScopeRhomb::getRightMostTopPoint(QPointF &point) const
{
  if(m_points.size() < 4)
    qFatal("The rhomboid has not four points.");

  std::vector<QPointF> points;

  // Try the top most points, which will tell us if the rhomboid is horizontal
  // or not.

  IntegrationScopeFeatures scope_features = getTopMostPoints(points);

  if(scope_features == IntegrationScopeFeatures::FAILURE)
    qFatal("Failed to get the top most points.");

  if(scope_features & IntegrationScopeFeatures::RHOMBOID_HORIZONTAL)
    {
      // We should have gotten 2 points.

      if(points.size() != 2)
        qFatal("We should have gotten two points.");

      if(points.at(0).x() > points.at(1).x())
        point = points.at(0);
      else
        point = points.at(1);
    }
  else if(scope_features & IntegrationScopeFeatures::RHOMBOID_VERTICAL)
    {
      // In this case, we need to ask for the left most points. We'll have to
      // check the results again!

      scope_features = getRightMostPoints(points);

      if(scope_features == IntegrationScopeFeatures::FAILURE)
        qFatal("Failed to get the right most points.");

      if(scope_features & IntegrationScopeFeatures::RHOMBOID_VERTICAL)
        {
          // We should have gotten 2 points.

          if(points.size() != 2)
            qFatal("We should have gotten two points.");

          if(points.at(0).y() > points.at(1).y())
            point = points.at(0);
          else
            point = points.at(1);
        }
      else if(scope_features & IntegrationScopeFeatures::FLAT_ON_Y_AXIS)
        {
          // It is possible that the user has rotated the vertical rhomboid
          // such that all the points are aligned on the y axis (all have the
          // same x axis value). This is not an error condition. All we do is
          // return scope_features so the caller understands the situations.
        }
      else
        qFatal("This point should never be reached.");
    }
  else if(scope_features & IntegrationScopeFeatures::FLAT_ON_X_AXIS)
    {
      // This is not an error condition. All we do is return scope_features
      // so the caller understands the situations.
    }
  else
    qFatal("This point should never be reached.");

  return scope_features;
}

IntegrationScopeFeatures
IntegrationScopeRhomb::getRightMostBottomPoint(QPointF &point) const
{
  if(m_points.size() < 4)
    qFatal("The rhomboid has not four points.");

  std::vector<QPointF> points;

  // Try the bottom most points, which will tell us if the rhomboid is
  // horizontal or not.

  IntegrationScopeFeatures scope_features = getBottomMostPoints(points);

  if(scope_features == IntegrationScopeFeatures::FAILURE)
    qFatal("Failed to get the bottom most points.");

  if(scope_features & IntegrationScopeFeatures::RHOMBOID_HORIZONTAL)
    {
      // We should have gotten 2 points.

      if(points.size() != 2)
        qFatal("We should have gotten two points.");

      if(points.at(0).x() > points.at(1).x())
        point = points.at(0);
      else
        point = points.at(1);
    }
  else if(scope_features & IntegrationScopeFeatures::RHOMBOID_VERTICAL)
    {
      // In this case, we need to ask for the left most points. We'll have to
      // check the results again!

      scope_features = getRightMostPoints(points);

      if(!(scope_features & IntegrationScopeFeatures::SUCCESS))
        qFatal("Failed to get the right most points.");

      if(scope_features & IntegrationScopeFeatures::RHOMBOID_VERTICAL)
        {
          // We should have gotten 2 points.

          if(points.size() != 2)
            qFatal("We should have gotten two points.");

          if(points.at(0).y() < points.at(1).y())
            point = points.at(0);
          else
            point = points.at(1);
        }
      else if(scope_features & IntegrationScopeFeatures::FLAT_ON_Y_AXIS)
        {
          // It is possible that the user has rotated the vertical rhomboid
          // such that all the points are aligned on the y axis (all have the
          // same x axis value). This is not an error condition. All we do is
          // return scope_features so the caller understands the situations.
        }
      else
        qFatal("This point should never be reached.");
    }
  else if(scope_features & IntegrationScopeFeatures::FLAT_ON_ANY_AXIS)
    {
      // This is not an error condition. All we do is return scope_features
      // so the caller understands the situations.
    }
  else
    qFatal("This point should never be reached.");

  return scope_features;
}

IntegrationScopeFeatures
IntegrationScopeRhomb::getWidth(double &width) const
{
  if(m_points.size() < 4)
    qFatal("The IntegrationScopeRhomb has less than four points.");

  // There are two kinds of rhomboid integration scopes:

  /*
              4 +-----------+3
                |          |
              |          |
            |          |
          |          |
        |          |
       |          |
     |          |
    1+----------+2
     ----width---
   */

  // As visible here, the fixed size of the rhomboid (using the S key in the
  // plot widget) is the *horizontal* side (this is the plot context's
  // m_integrationScopeRhombWidth). In this case the height of the scope is 0.

  // and


  /*
   *                                       +3
   *                                     . |
   *                                   .   |
   *                                 .     |
   *                               .       +2
   *                             .       .
   *                           .       .
   *                         .       .
   *                      4+       .
   *                     | |     .
   *            height   | |   .
   *                     | | .
   *                      1+
   *
   */

  // As visible here, the fixed size of the rhomboid (using the S key in the
  // plot widget) is the *vertical* side (this is the plot context's
  // m_integrationScopeRhombHeight). In this case the width of the scope is 0.

  // The width of the rhomboid is the entire span that it has on the x axis.

  QPointF left_most_point;
  QPointF right_most_point;

  if(!getLeftMostPoint(left_most_point))
    qFatal("Failed to get the left most point.");

  if(!getRightMostPoint(right_most_point))
    qFatal("Failed to get the right most point.");

  width = fabs(right_most_point.x() - left_most_point.x());

  return IntegrationScopeFeatures::SUCCESS;
}

IntegrationScopeFeatures
IntegrationScopeRhomb::getHeight(double &height) const
{
  // See getWidth() for explanations.

  if(m_points.size() < 4)
    qFatal("The IntegrationScopeRhomb has less than four points.");

  // The height of the rhomboid is the entire span that it has on the y axis.

  QPointF top_most_point;
  QPointF bottom_most_point;

  if(!getTopMostPoint(top_most_point))
    qFatal("Failed to get the top most point.");

  if(!getBottomMostPoint(bottom_most_point))
    qFatal("Failed to get the bottom most point.");

  height = fabs(top_most_point.y() - bottom_most_point.y());

  return IntegrationScopeFeatures::SUCCESS;
}

IntegrationScopeFeatures
IntegrationScopeRhomb::getRhombHorizontalSize(double &size) const
{
  if(m_points.size() < 4)
    qFatal("The IntegrationScopeRhomb has less than four points.");

  // There are two kinds of rhomboid integration scopes:

  /*
   *              4 +-----------+3
   *                |          |
   *              |          |
   *            |          |
   *          |          |
   *        |          |
   *       |          |
   *     |          |
   *    1+----------+2
   *     ----width---
   */

  // As visible here, the fixed size of the rhomboid (using the S key in the
  // plot widget) is the *horizontal* side (this is the plot context's
  // m_integrationScopeRhombWidth). In this case the height of the scope is 0.

  // and


  /*
   *                                       +3
   *                                     . |
   *                                   .   |
   *                                 .     |
   *                               .       +2
   *                             .       .
   *                           .       .
   *                         .       .
   *                      4+       .
   *                     | |     .
   *            height   | |   .
   *                     | | .
   *                      1+
   *
   */

  // As visible here, the fixed size of the rhomboid (using the S key in the
  // plot widget) is the *vertical* side (this is the plot context's
  // m_integrationScopeRhombHeight). In this case the width of the scope is 0.

  // In this function we need to establish what kind of rhomboid (horizontal or
  // vertical) we are dealing with.

  // If the scope is horizontal, then two points of same y value (either top or
  // bottom) have different x values. That is, leftmost top point has the same y
  // value as the rightmost top point. Similarly, the leftmost bottom point has
  // the same y value as the rightmost bottom point.

  // If the scope is vertical, then there is only one single point that has the
  // greatest y value. Likewise, there is only one single point having the
  // smallest y value. Conversely, there are going to be two points of differing
  // y values having the same x value (2 leftmost points and two rightmost
  // points).

  std::vector<QPointF> points;

  // First get the top most point, we'll use the y value to check if there is
  // only one point sharing that value or not.

  // qDebug() << "The rhomboid integration scope:" << toString();

  IntegrationScopeFeatures scope_features = getTopMostPoints(points);

  if(scope_features == IntegrationScopeFeatures::FAILURE)
    qFatal("Failed to get top most points.");

  // qDebug() << "getTopMostPoints() got" << points.size() << "points.";

  if(scope_features & IntegrationScopeFeatures::FLAT_ON_X_AXIS)
    {
      // Do not change anything to the width passed as parameter.
    }
  else if(scope_features & IntegrationScopeFeatures::RHOMBOID_HORIZONTAL)
    {
      // We are dealing with a horizontal rhomboid. Thus we *must* have
      // gotten 2 points.
      size = fabs(points.at(0).x() - points.at(1).x());
    }
  else if(scope_features & IntegrationScopeFeatures::RHOMBOID_VERTICAL)
    {
      // We are dealing with a vertical rhomboid.
      size = 0;
    }

  return scope_features;
}

IntegrationScopeFeatures
IntegrationScopeRhomb::getRhombVerticalSize(double &size) const
{
  // See getRhombHorizontalSize() for explanations.

  // If the scope is horizontal, then two points of same y value (either top or
  // bottom) have different x values. That is, leftmost top point has the same y
  // value as the rightmost top point. Similarly, the leftmost bottom point has
  // the same y value as the rightmost bottom point.

  // If the scope is vertical, then there is only one single point that has the
  // greatest y value. Likewise, there is only one single point having the
  // smallest y value. Conversely, there are going to be two points of differing
  // y values having the same x value (2 leftmost points and two rightmost
  // points).

  std::vector<QPointF> points;

  // Get the leftmost points (there are going to be 1 or 2 points in the
  // vector depending on the kind of rhomboid.

  // qDebug() << "The rhomboid integration scope:" << toString();

  IntegrationScopeFeatures scope_features = getLeftMostPoints(points);

  if(scope_features == IntegrationScopeFeatures::FAILURE)
    qFatal("Failed to get left most points.");

  // qDebug() << "getLeftMostPoints() got" << points.size() << "points.";

  if(scope_features & IntegrationScopeFeatures::FLAT_ON_Y_AXIS)
    {
      // Do not change anything to the width passed as parameter.
    }
  else if(scope_features & IntegrationScopeFeatures::RHOMBOID_HORIZONTAL)
    {
      // We are dealing with a horizontal rhomboid.
      size = 0;
    }
  else if(scope_features & IntegrationScopeFeatures::RHOMBOID_VERTICAL)
    {
      // We are dealing with a vertical rhomboid. Thus we *must* have
      // gotten 2 points.
      size = fabs(points.at(0).y() - points.at(1).y());
    }

  return scope_features;
}

bool
IntegrationScopeRhomb::range(Enums::Axis axis, double &start, double &end) const
{
  if(axis == Enums::Axis::x)
    {
      QPointF left_most_point;
      if(getLeftMostPoint(left_most_point) == IntegrationScopeFeatures::FAILURE)
        qFatal("Failed to get left-most point.");

      QPointF right_most_point;
      if(getRightMostPoint(right_most_point) == IntegrationScopeFeatures::FAILURE)
        qFatal("Failed to get right-most point.");

      start = left_most_point.x();
      end   = right_most_point.x();

      return true;
    }
  else if(axis == Enums::Axis::y)
    {
      QPointF bottom_most_point;
      if(getBottomMostPoint(bottom_most_point) == IntegrationScopeFeatures::FAILURE)
        qFatal("Failed to get bottom-most point.");

      QPointF top_most_point;
      if(getTopMostPoint(top_most_point) == IntegrationScopeFeatures::FAILURE)
        qFatal("Failed to get top-most point.");

      start = bottom_most_point.y();
      end   = top_most_point.y();

      return true;
    }

  return false;
}

void
IntegrationScopeRhomb::setDataKindX([[maybe_unused]] Enums::DataKind data_kind)
{
  m_dataKindX = data_kind;
}

void
IntegrationScopeRhomb::setDataKindY([[maybe_unused]] Enums::DataKind data_kind)
{
  m_dataKindY = data_kind;
}

bool
IntegrationScopeRhomb::getDataKindX([[maybe_unused]] Enums::DataKind &data_kind)
{
  data_kind = m_dataKindX;
  return true;
}

bool
IntegrationScopeRhomb::getDataKindY([[maybe_unused]] Enums::DataKind &data_kind)
{
  data_kind = m_dataKindY;
  return true;
}

bool
IntegrationScopeRhomb::is1D() const
{
  return false;
}

bool
IntegrationScopeRhomb::is2D() const
{
  return !is1D();
}

bool
IntegrationScopeRhomb::isRectangle() const
{
  return false;
}

bool
IntegrationScopeRhomb::isRhomboid() const
{
  return true;
}

bool
IntegrationScopeRhomb::transpose()
{
  Enums::DataKind was_data_kind_y = m_dataKindY;
  m_dataKindY                     = m_dataKindX;
  m_dataKindX                     = was_data_kind_y;

  // Transpose each point in a new vector.
  std::vector<QPointF> transposed_points;

  for(QPointF &point : m_points)
    transposed_points.push_back(QPointF(point.y(), point.x()));

  // And now set them back to the member datum.
  m_points.assign(transposed_points.begin(), transposed_points.end());

  return true;
}

bool
IntegrationScopeRhomb::contains(const QPointF &point) const
{
  // 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() > point.y()) != (m_points.at(j).y() > point.y())) &&
         (point.x() < (m_points.at(j).x() - m_points.at(i).x()) * (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:" << point
  //            << "against rhomboid polygon - turns out be in.";
  // else
  //   qDebug() << "Testing point:" << point
  //            << "against rhomboid polygon - turns out be out.";

  return is_inside;
}

QString
IntegrationScopeRhomb::toString() const
{
  QString text = "[";

#if 0

  // This version is bad because it has reentrancy problems: it might be
  // called by functions that are actually called in turn here.

  // First the bottom points pair
  getLeftMostBottomPoint(point);
  text.append(QString("(%1, %2)").arg(point.x()).arg(point.y()));

  getRightMostBottomPoint(point);
  text.append(QString("(%1, %2)").arg(point.x()).arg(point.y()));

  // Second the top points pair
  getLeftMostTopPoint(point);
  text.append(QString("(%1, %2)").arg(point.x()).arg(point.y()));

  getRightMostTopPoint(point);
  text.append(QString("(%1, %2)").arg(point.x()).arg(point.y()));

#endif

  for(auto &point : m_points)
    text.append(QString("(%1, %2)").arg(point.x()).arg(point.y()));

  text.append("]");

  // qDebug() << "Returning toString():" << text;

  return text;
}

void
IntegrationScopeRhomb::reset()
{
  IntegrationScopeBase::reset();
  m_points.clear();
}

} // namespace pappso
