// Copyright 2021 Filippo Rusconi
// GPLv3+

#pragma once

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

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


/////////////////////// Local includes
#include "pappsomspp/export-import-config.h"
#include "../../types.h"


namespace pappso
{

/*
 Bitwise stuff (from StackOverflow)

 It is sometimes worth using an enum to name the bits:

 enum ThingFlags = {
 ThingMask  = 0x0000,
 ThingFlag0 = 1 << 0,
 ThingFlag1 = 1 << 1,
 ThingError = 1 << 8,
 }

 Then use the names later on. I.e. write

 thingstate |= ThingFlag1;
 thingstate &= ~ThingFlag0;
 if (thing & ThingError) {...}

 to set, clear and test. This way you hide the magic numbers from the rest of
 your code.
 */

enum IntegrationScopeFeatures
{
  // This one needs to be 0 because of (if!<function>() syntax)
  FAILURE = 0x0000,
  // As soon as not 0, success and potentially with more info.
  SUCCESS             = 1 << 1,
  FLAT_ON_X_AXIS      = 1 << 2,
  FLAT_ON_Y_AXIS      = 1 << 3,
  RHOMBOID_VERTICAL   = 1 << 4,
  RHOMBOID_HORIZONTAL = 1 << 5,
  NOT_PRINTABLE       = 1 << 6,
  FLAT_ON_ANY_AXIS    = (FLAT_ON_X_AXIS | FLAT_ON_Y_AXIS)
};

class PMSPP_LIB_DECL IntegrationScopeBase : public QObject
{
    Q_OBJECT

public:
    IntegrationScopeBase(QObject *parent_p = nullptr);
    IntegrationScopeBase(const IntegrationScopeBase &other, QObject *parent_p = nullptr);
    virtual ~IntegrationScopeBase();

    virtual bool getPoint(QPointF &point) const;
    virtual bool getPoints(std::vector<QPointF> &points) const;

    virtual IntegrationScopeFeatures getLeftMostPoint(QPointF &point) const;
    virtual IntegrationScopeFeatures getLeftMostPoints(std::vector<QPointF> &points) const;
    virtual IntegrationScopeFeatures getLeftMostTopPoint(QPointF &point) const;
    virtual IntegrationScopeFeatures getLeftMostBottomPoint(QPointF &point) const;

    virtual IntegrationScopeFeatures getRightMostPoint(QPointF &point) const;
    virtual IntegrationScopeFeatures getRightMostPoints(std::vector<QPointF> &points) const;
    virtual IntegrationScopeFeatures getRightMostTopPoint(QPointF &point) const;
    virtual IntegrationScopeFeatures getRightMostBottomPoint(QPointF &point) const;

    virtual IntegrationScopeFeatures getTopMostPoint(QPointF &point) const;
    virtual IntegrationScopeFeatures getTopMostPoints(std::vector<QPointF> &points) const;
    virtual IntegrationScopeFeatures getBottomMostPoint(QPointF &point) const;
    virtual IntegrationScopeFeatures getBottomMostPoints(std::vector<QPointF> &points) const;

    virtual IntegrationScopeFeatures getRhombHorizontalSize(double &size) const;
    virtual IntegrationScopeFeatures getRhombVerticalSize(double &size) const;

    virtual IntegrationScopeFeatures getWidth(double &width) const;
    virtual IntegrationScopeFeatures getHeight(double &height) const;

    virtual bool range(Enums::Axis axis, double &start, double &end) const;

    virtual void setDataKindX(Enums::DataKind data_kind);
    virtual bool getDataKindX(Enums::DataKind &data_kind);

    virtual void setDataKindY(Enums::DataKind data_kind);
    virtual bool getDataKindY(Enums::DataKind &data_kind);

    virtual bool is1D() const;
    virtual bool is2D() const;

    virtual bool isRectangle() const;
    virtual bool isRhomboid() const;

    virtual bool transpose();

    virtual bool contains(const QPointF &point) const;

    virtual QString toString() const;

    virtual void reset();
};

typedef std::shared_ptr<IntegrationScopeBase> IntegrationScopeBaseSPtr;
typedef std::shared_ptr<const IntegrationScopeBase> IntegrationScopeBaseCstSPtr;


struct PMSPP_LIB_DECL IntegrationScopeSpec
{

  IntegrationScopeBaseSPtr integrationScopeSPtr;
  Enums::DataKind dataKind = Enums::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.

  IntegrationScopeSpec();

  IntegrationScopeSpec(IntegrationScopeBaseSPtr integration_scope_sp,
                       Enums::DataKind data_kind)
    : integrationScopeSPtr(integration_scope_sp), dataKind(data_kind)
  {
  }

  IntegrationScopeSpec(const IntegrationScopeSpec &other)
    : integrationScopeSPtr(other.integrationScopeSPtr), dataKind(other.dataKind)
  {
  }

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

    integrationScopeSPtr = other.integrationScopeSPtr;
    dataKind             = other.dataKind;

    return *this;
  }

  QString
  toString() const
  {
    QString text = "Integration scope spec:";
    text += integrationScopeSPtr->toString();

    text += " - data kind: ";

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

    return text;
  }
};

typedef std::shared_ptr<IntegrationScopeSpec> IntegrationScopeSpecSPtr;
typedef std::shared_ptr<const IntegrationScopeSpec> IntegrationScopeSpecCstSPtr;

} // namespace pappso
