// Copyright 2021 Filippo Rusconi
// GPLv3+

#pragma once

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

/////////////////////// Qt includes
#include <QString>
#include <QPointF>


/////////////////////// Local includes
#include "../../exportinmportconfig.h"
#include "../../types.h"

namespace pappso
{


enum class PointSpec
{
  // Starting top left and then clockwise
  TOP_LEFT_POINT     = 0,
  TOP_RIGHT_POINT    = 1,
  BOTTOM_RIGHT_POINT = 2,
  BOTTOM_LEFT_POINT  = 3,
  ENUM_LAST          = 4,
};


enum class DataDimension
{
  NOT_SET = 0,
  HORIZONTAL,
  VERTICAL
};



class PMSPP_LIB_DECL SelectionPolygon
{
  public:
  // Constructor with no arguments
  SelectionPolygon();

  SelectionPolygon(QPointF top_left_point, QPointF top_right_point);

  SelectionPolygon(QPointF top_left_point,
                   QPointF top_right_point,
                   QPointF bottom_right_point,
                   QPointF bottom_left_point);

  SelectionPolygon(const SelectionPolygon &other);

  virtual ~SelectionPolygon();


  void setPoint(PointSpec point_spec, double x, double y);
  void setPoint(PointSpec point_spec, QPointF point);
  void copyPoint(PointSpec point_spec_src, PointSpec point_spec_dest);

  void set1D(double x_range_start, double x_range_end);
  void set2D(QPointF top_left,
             QPointF top_right,
             QPointF bottom_right,
             QPointF bottom_left);
  void convertTo1D();

  const std::vector<QPointF> &getPoints() const;

  QPointF getLeftMostPoint() const;
  QPointF getRightMostPoint() const;
  QPointF getTopMostPoint() const;
  QPointF getBottomMostPoint() const;

  QPointF getPoint(PointSpec point_spec) const;

  bool computeMinMaxCoordinates();
  bool computeMinMaxCoordinates(double &min_x,
                                double &max_x,
                                double &min_y,
                                double &max_y) const;

  double width(bool &ok) const;
  double height(bool &ok) const;

  bool rangeX(double &range_start, double &range_end) const;
  bool rangeY(double &range_start, double &range_end) const;
  bool range(Axis axis, double &range_start, double &range_end) const;

  SelectionPolygon transpose() const;

  bool contains(const QPointF &tested_point) const;
  bool contains(const SelectionPolygon &selection_polygon) const;

  SelectionPolygon &operator=(const SelectionPolygon &other);

  void resetPoints();

  bool is1D() const;
  bool is2D() const;
  bool isRectangle() const;

  QString toShort4PointsString() const;
  QString toString() const;


  static void debugAlgorithm(const SelectionPolygon &selection_polygon,
                             const QPointF &tested_point);

  protected:
  // The PointSpec enum and the setPoint function ensure that there are no more
  // than four points in the vector.

  // We want to create a largest polygon possible, by settting the first point,
  // top_left on the lowest_x and highest_y, the top_right on the highest_x and
  // highest_y, the bottom_right on the highest_x and lowest_y, the bottom_left
  // at the lowest_x and lowest_y.
  std::vector<QPointF> m_points = {QPointF(std::numeric_limits<double>::min(),
                                           std::numeric_limits<double>::max()),
                                   QPointF(std::numeric_limits<double>::max(),
                                           std::numeric_limits<double>::max()),
                                   QPointF(std::numeric_limits<double>::max(),
                                           std::numeric_limits<double>::min()),
                                   QPointF(std::numeric_limits<double>::min(),
                                           std::numeric_limits<double>::min())};

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

  double m_maxX = std::numeric_limits<double>::max();
  double m_maxY = std::numeric_limits<double>::max();
};


struct PMSPP_LIB_DECL SelectionPolygonSpec
{

  SelectionPolygon selectionPolygon;
  DataKind dataKind = DataKind::unset;

  // NO specification of the axis because it is implicit that MZ is Y and the
  // checked value is either DT or RT depending on dataKind.

  SelectionPolygonSpec();

  SelectionPolygonSpec(const SelectionPolygon &selection_polygon,
                       DataKind data_kind)
    : selectionPolygon(selection_polygon), dataKind(data_kind)
  {
  }

  SelectionPolygonSpec(const SelectionPolygonSpec &other)
    : selectionPolygon(other.selectionPolygon), dataKind(other.dataKind)
  {
  }

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

    selectionPolygon = other.selectionPolygon;
    dataKind         = other.dataKind;

    return *this;
  }


  QString
  toString() const
  {
    QString text = "Selection polygon spec:";
    text += selectionPolygon.toString();

    text += " - data kind: ";

    if(dataKind == DataKind::dt)
      text += "dt.";
    else if(dataKind == DataKind::mz)
      text += "m/z.";
    else if(dataKind == DataKind::rt)
      text += "rt.";
    else
      text += "unset.";

    return text;
  }
};


} // namespace pappso
