#pragma once

#include <functional>
#include <vector>
#include <algorithm>

#include "opencv2/core/version.hpp"
#if CV_MAJOR_VERSION >= 2 && CV_MAJOR_VERSION <= 3

#include "BlobLibraryConfiguration.h"
// opencv legacy includes
#include "OpenCvLegacyIncludes.h"

namespace bgslibrary
{
  namespace algorithms
  {
    namespace multilayer
    {
      namespace blob
      {
        //! Factor de conversió de graus a radians
        const double DEGREE2RAD = (CV_PI / 180.0);

        /**
          Classe que representa un blob, entés com un conjunt de pixels del
          mateix color contigus en una imatge binaritzada.

          Class to represent a blob, a group of connected pixels in a binary image
          */
        class CBlob
        {
        public:
          //! Constructor estàndard
          //! Standard constructor
          CBlob();
          //! Constructor de còpia
          //! Copy constructor
          CBlob(const CBlob &src);
          CBlob(const CBlob *src);

          //! Destructor estàndard
          //! Standard Destructor
          ~CBlob();

          //! Operador d'assignació
          //! Assigment operator
          CBlob& operator=(const CBlob &src);

          //! Indica si el blob està buit ( no té cap info associada )
          //! Shows if the blob has associated information
          bool IsEmpty() const
          {
            return (area == 0.0 && perimeter == 0.0);
          };

          //! Neteja les cantonades del blob
          //! Clears the edges of the blob
          void ClearEdges();
          //! Copia les cantonades del blob a un altre (les afegeix al destí)
          //! Adds the blob edges to another blob
          void CopyEdges(CBlob &destination) const;
          //! Retorna el poligon convex del blob
          //! Calculates the convex hull of the blob
          bool GetConvexHull(CvSeq **dst) const;
          //! Calcula l'elipse que s'adapta als vèrtexs del blob
          //! Fits an ellipse to the blob edges
          CvBox2D GetEllipse() const;

          //! Pinta l'interior d'un blob d'un color determinat
          //! Paints the blob in an image
          void FillBlob(IplImage *imatge, CvScalar color, int offsetX = 0, int offsetY = 0) const;

          //! Funcions GET sobre els valors dels blobs
          //! Get functions

          inline int Label() const { return etiqueta; }
          inline int Parent() const { return parent; }
          inline double Area() const { return area; }
          inline double Perimeter() const { return perimeter; }
          inline double ExternPerimeter() const { return externPerimeter; }
          inline int	  Exterior() const { return exterior; }
          inline double Mean() const { return mean; }
          inline double StdDev() const { return stddev; }
          inline double MinX() const { return minx; }
          inline double MinY() const { return miny; }
          inline double MaxX() const { return maxx; }
          inline double MaxY() const { return maxy; }
          inline CvSeq *Edges() const { return edges; }
          inline double SumX() const { return sumx; }
          inline double SumY() const { return sumy; }
          inline double SumXX() const { return sumxx; }
          inline double SumYY() const { return sumyy; }
          inline double SumXY() const { return sumxy; }

          //! etiqueta del blob
          //! label of the blob
          int etiqueta;
          //! flag per indicar si es exterior o no
          //! true for extern blobs
          int exterior;
          //! area del blob
          //! Blob area
          double area;
          //! perimetre del blob
          //! Blob perimeter
          double perimeter;
          //! quantitat de perimetre del blob extern
          //! amount of blob perimeter which is exterior
          double externPerimeter;
          //! etiqueta del blob pare
          //! label of the parent blob
          int parent;
          //! moments
          double sumx;
          double sumy;
          double sumxx;
          double sumyy;
          double sumxy;
          //! Bounding rect
          double minx;
          double maxx;
          double miny;
          double maxy;

          //! mitjana
          //! mean of the grey scale values of the blob pixels
          double mean;
          //! desviació standard
          //! standard deviation of the grey scale values of the blob pixels
          double stddev;

          //! àrea de memòria on es desaran els punts de contorn del blob
          //! storage which contains the edges of the blob
          CvMemStorage *m_storage;
          //!	Sequència de punts del contorn del blob
          //! Sequence with the edges of the blob
          CvSeq *edges;


          //! Point datatype for plotting (FillBlob)
          typedef std::vector<CvPoint> vectorPunts;

          //! Helper class to compare two CvPoints (for sorting in FillBlob)
          struct comparaCvPoint : public std::binary_function<CvPoint, CvPoint, bool>
          {
            //! Definim que un punt és menor com més amunt a la dreta estigui
            bool operator()(CvPoint a, CvPoint b)
            {
              if (a.y == b.y)
                return a.x < b.x;
              else
                return a.y < b.y;
            }
          };
        };



        /**************************************************************************
          Definició de les classes per a fer operacions sobre els blobs

          Helper classes to perform operations on blobs
          **************************************************************************/


          //! Classe d'on derivarem totes les operacions sobre els blobs
          //! Interface to derive all blob operations
        class COperadorBlob
        {
        public:
          virtual ~COperadorBlob() {};

          //! Aplica l'operació al blob
          virtual double operator()(const CBlob &blob) const = 0;
          //! Obté el nom de l'operador
          virtual const char *GetNom() const = 0;

          operator COperadorBlob*() const
          {
            return (COperadorBlob*)this;
          }
        };

        typedef COperadorBlob funcio_calculBlob;

      #ifdef BLOB_OBJECT_FACTORY
        /**
          Funció per comparar dos identificadors dins de la fàbrica de COperadorBlobs
          */
        struct functorComparacioIdOperador
        {
          bool operator()(const char* s1, const char* s2) const
          {
            return strcmp(s1, s2) < 0;
          }
        };

        //! Definition of Object factory type for COperadorBlob objects
        typedef ObjectFactory<COperadorBlob, const char *, functorComparacioIdOperador > t_OperadorBlobFactory;

        //! Funció global per a registrar tots els operadors definits a blob.h
        void RegistraTotsOperadors(t_OperadorBlobFactory &fabricaOperadorsBlob);

      #endif

        //! Classe per calcular l'àrea d'un blob
        //! Class to get the area of a blob
        class CBlobGetArea : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            return blob.Area();
          }
          const char *GetNom() const
          {
            return "CBlobGetArea";
          }
        };

        //! Classe per calcular el perimetre d'un blob
        //! Class to get the perimeter of a blob
        class CBlobGetPerimeter : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            return blob.Perimeter();
          }
          const char *GetNom() const
          {
            return "CBlobGetPerimeter";
          }
        };

        //! Classe que diu si un blob és extern o no
        //! Class to get the extern flag of a blob
        class CBlobGetExterior : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            return blob.Exterior();
          }
          const char *GetNom() const
          {
            return "CBlobGetExterior";
          }
        };

        //! Classe per calcular la mitjana de nivells de gris d'un blob
        //! Class to get the mean grey level of a blob
        class CBlobGetMean : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            return blob.Mean();
          }
          const char *GetNom() const
          {
            return "CBlobGetMean";
          }
        };

        //! Classe per calcular la desviació estàndard dels nivells de gris d'un blob
        //! Class to get the standard deviation of the grey level values of a blob
        class CBlobGetStdDev : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            return blob.StdDev();
          }
          const char *GetNom() const
          {
            return "CBlobGetStdDev";
          }
        };

        //! Classe per calcular la compacitat d'un blob
        //! Class to calculate the compactness of a blob
        class CBlobGetCompactness : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const;
          const char *GetNom() const
          {
            return "CBlobGetCompactness";
          }
        };

        //! Classe per calcular la longitud d'un blob
        //! Class to calculate the length of a blob
        class CBlobGetLength : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const;
          const char *GetNom() const
          {
            return "CBlobGetLength";
          }
        };

        //! Classe per calcular l'amplada d'un blob
        //! Class to calculate the breadth of a blob
        class CBlobGetBreadth : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const;
          const char *GetNom() const
          {
            return "CBlobGetBreadth";
          }
        };

        //! Classe per calcular la diferència en X del blob
        class CBlobGetDiffX : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            return blob.maxx - blob.minx;
          }
          const char *GetNom() const
          {
            return "CBlobGetDiffX";
          }
        };

        //! Classe per calcular la diferència en X del blob
        class CBlobGetDiffY : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            return blob.maxy - blob.miny;
          }
          const char *GetNom() const
          {
            return "CBlobGetDiffY";
          }
        };

        //! Classe per calcular el moment PQ del blob
        //! Class to calculate the P,Q moment of a blob
        class CBlobGetMoment : public COperadorBlob
        {
        public:
          //! Constructor estàndard
          //! Standard constructor (gets the 00 moment)
          CBlobGetMoment()
          {
            m_p = m_q = 0;
          }
          //! Constructor: indiquem el moment p,q a calcular
          //! Constructor: gets the PQ moment
          CBlobGetMoment(int p, int q)
          {
            m_p = p;
            m_q = q;
          };
          double operator()(const CBlob &blob) const;
          const char *GetNom() const
          {
            return "CBlobGetMoment";
          }

        private:
          //! moment que volem calcular
          int m_p, m_q;
        };

        //! Classe per calcular el perimetre del poligon convex d'un blob
        //! Class to calculate the convex hull perimeter of a blob
        class CBlobGetHullPerimeter : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const;
          const char *GetNom() const
          {
            return "CBlobGetHullPerimeter";
          }
        };

        //! Classe per calcular l'àrea del poligon convex d'un blob
        //! Class to calculate the convex hull area of a blob
        class CBlobGetHullArea : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const;
          const char *GetNom() const
          {
            return "CBlobGetHullArea";
          }
        };

        //! Classe per calcular la x minima en la y minima
        //! Class to calculate the minimum x on the minimum y
        class CBlobGetMinXatMinY : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const;
          const char *GetNom() const
          {
            return "CBlobGetMinXatMinY";
          }
        };

        //! Classe per calcular la y minima en la x maxima
        //! Class to calculate the minimum y on the maximum x
        class CBlobGetMinYatMaxX : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const;
          const char *GetNom() const
          {
            return "CBlobGetMinYatMaxX";
          }
        };

        //! Classe per calcular la x maxima en la y maxima
        //! Class to calculate the maximum x on the maximum y
        class CBlobGetMaxXatMaxY : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const;
          const char *GetNom() const
          {
            return "CBlobGetMaxXatMaxY";
          }
        };

        //! Classe per calcular la y maxima en la x minima
        //! Class to calculate the maximum y on the minimum y
        class CBlobGetMaxYatMinX : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const;
          const char *GetNom() const
          {
            return "CBlobGetMaxYatMinX";
          }
        };

        //! Classe per a calcular la x mínima
        //! Class to get the minimum x
        class CBlobGetMinX : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            return blob.MinX();
          }
          const char *GetNom() const
          {
            return "CBlobGetMinX";
          }
        };

        //! Classe per a calcular la x màxima
        //! Class to get the maximum x
        class CBlobGetMaxX : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            return blob.MaxX();
          }
          const char *GetNom() const
          {
            return "CBlobGetMaxX";
          }
        };

        //! Classe per a calcular la y mínima
        //! Class to get the minimum y
        class CBlobGetMinY : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            return blob.MinY();
          }
          const char *GetNom() const
          {
            return "CBlobGetMinY";
          }
        };

        //! Classe per a calcular la y màxima
        //! Class to get the maximum y
        class CBlobGetMaxY : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            return blob.MaxY();
          }
          const char *GetNom() const
          {
            return "CBlobGetMax";
          }
        };


        //! Classe per calcular l'elongacio d'un blob
        //! Class to calculate the elongation of the blob
        class CBlobGetElongation : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const;
          const char *GetNom() const
          {
            return "CBlobGetElongation";
          }
        };

        //! Classe per calcular la rugositat d'un blob
        //! Class to calculate the roughness of the blob
        class CBlobGetRoughness : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const;
          const char *GetNom() const
          {
            return "CBlobGetRoughness";
          }
        };

        //! Classe per calcular la distància entre el centre del blob i un punt donat
        //! Class to calculate the euclidean distance between the center of a blob and a given point
        class CBlobGetDistanceFromPoint : public COperadorBlob
        {
        public:
          //! Standard constructor (distance to point 0,0)
          CBlobGetDistanceFromPoint()
          {
            m_x = m_y = 0.0;
          }
          //! Constructor (distance to point x,y)
          CBlobGetDistanceFromPoint(const double x, const double y)
          {
            m_x = x;
            m_y = y;
          }

          double operator()(const CBlob &blob) const;
          const char *GetNom() const
          {
            return "CBlobGetDistanceFromPoint";
          }

        private:
          // coordenades del punt on volem calcular la distància
          double m_x, m_y;
        };

        //! Classe per calcular el nombre de pixels externs d'un blob
        //! Class to get the number of extern pixels of a blob
        class CBlobGetExternPerimeter : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            return blob.ExternPerimeter();
          }
          const char *GetNom() const
          {
            return "CBlobGetExternPerimeter";
          }
        };

        //! Classe per calcular el ratio entre el perimetre i nombre pixels externs
        //! valors propers a 0 indiquen que la majoria del blob és intern
        //! valors propers a 1 indiquen que la majoria del blob és extern
        //! Class to calculate the ratio between the perimeter and the number of extern pixels
        class CBlobGetExternPerimeterRatio : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            if (blob.Perimeter() != 0)
              return blob.ExternPerimeter() / blob.Perimeter();
            else
              return blob.ExternPerimeter();
          }
          const char *GetNom() const
          {
            return "CBlobGetExternPerimeterRatio";
          }
        };

        //! Classe per calcular el ratio entre el perimetre convex i nombre pixels externs
        //! valors propers a 0 indiquen que la majoria del blob és intern
        //! valors propers a 1 indiquen que la majoria del blob és extern
        //! Class to calculate the ratio between the perimeter and the number of extern pixels
        class CBlobGetExternHullPerimeterRatio : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            CBlobGetHullPerimeter getHullPerimeter;
            double hullPerimeter;

            if ((hullPerimeter = getHullPerimeter(blob)) != 0)
              return blob.ExternPerimeter() / hullPerimeter;
            else
              return blob.ExternPerimeter();
          }
          const char *GetNom() const
          {
            return "CBlobGetExternHullPerimeterRatio";
          }
        };

        //! Classe per calcular el centre en el eix X d'un blob
        //! Class to calculate the center in the X direction
        class CBlobGetXCenter : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            return blob.MinX() + ((blob.MaxX() - blob.MinX()) / 2.0);
          }
          const char *GetNom() const
          {
            return "CBlobGetXCenter";
          }
        };

        //! Classe per calcular el centre en el eix Y d'un blob
        //! Class to calculate the center in the Y direction
        class CBlobGetYCenter : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            return blob.MinY() + ((blob.MaxY() - blob.MinY()) / 2.0);
          }
          const char *GetNom() const
          {
            return "CBlobGetYCenter";
          }
        };

        //! Classe per calcular la longitud de l'eix major d'un blob
        //! Class to calculate the length of the major axis of the ellipse that fits the blob edges
        class CBlobGetMajorAxisLength : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            CvBox2D elipse = blob.GetEllipse();

            return elipse.size.width;
          }
          const char *GetNom() const
          {
            return "CBlobGetMajorAxisLength";
          }
        };

        //! Classe per calcular el ratio entre l'area de la elipse i la de la taca
        //! Class
        class CBlobGetAreaElipseRatio : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            if (blob.Area() == 0.0) return 0.0;

            CvBox2D elipse = blob.GetEllipse();
            double ratioAreaElipseAreaTaca = ((elipse.size.width / 2.0)
              *
              (elipse.size.height / 2.0)
              *CV_PI
              )
              /
              blob.Area();

            return ratioAreaElipseAreaTaca;
          }
          const char *GetNom() const
          {
            return "CBlobGetAreaElipseRatio";
          }
        };

        //! Classe per calcular la longitud de l'eix menor d'un blob
        //! Class to calculate the length of the minor axis of the ellipse that fits the blob edges
        class CBlobGetMinorAxisLength : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            CvBox2D elipse = blob.GetEllipse();

            return elipse.size.height;
          }
          const char *GetNom() const
          {
            return "CBlobGetMinorAxisLength";
          }
        };

        //! Classe per calcular l'orientació de l'ellipse del blob en radians
        //! Class to calculate the orientation of the ellipse that fits the blob edges in radians
        class CBlobGetOrientation : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            CvBox2D elipse = blob.GetEllipse();

            if (elipse.angle > 180.0)
              return ((elipse.angle - 180.0)* DEGREE2RAD);
            else
              return (elipse.angle * DEGREE2RAD);

          }
          const char *GetNom() const
          {
            return "CBlobGetOrientation";
          }
        };

        //! Classe per calcular el cosinus de l'orientació de l'ellipse del blob
        //! Class to calculate the cosinus of the orientation of the ellipse that fits the blob edges
        class CBlobGetOrientationCos : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            CBlobGetOrientation getOrientation;
            return fabs(cos(getOrientation(blob)));
          }
          const char *GetNom() const
          {
            return "CBlobGetOrientationCos";
          }
        };


        //! Classe per calcular el ratio entre l'eix major i menor de la el·lipse
        //! Class to calculate the ratio between both axes of the ellipse
        class CBlobGetAxisRatio : public COperadorBlob
        {
        public:
          double operator()(const CBlob &blob) const
          {
            CvBox2D elipse = blob.GetEllipse();

            return elipse.size.height / elipse.size.width;
          }
          const char *GetNom() const
          {
            return "CBlobGetAxisRatio";
          }
        };


        //! Classe per calcular si un punt cau dins del blob
        //! Class to calculate whether a point is inside a blob
        class CBlobGetXYInside : public COperadorBlob
        {
        public:
          //! Constructor estàndard
          //! Standard constructor
          CBlobGetXYInside()
          {
            m_p = cvPoint(0, 0);
          }
          //! Constructor: indiquem el punt
          //! Constructor: sets the point
          CBlobGetXYInside(CvPoint p)
          {
            m_p = p;
          };
          double operator()(const CBlob &blob) const;
          const char *GetNom() const
          {
            return "CBlobGetXYInside";
          }

        private:
          //! punt que considerem
          //! point to be considered
          CvPoint m_p;
        };
      }
    }
  }
}

#endif
