#pragma once

#include <opencv2/opencv.hpp>

#if CV_MAJOR_VERSION >= 2 && CV_MAJOR_VERSION <= 3

namespace bgslibrary
{
  namespace algorithms
  {
    namespace dp
    {
      template <class T>
      class ImageIterator
      {
      public:
        ImageIterator(IplImage* image, int x = 0, int y = 0, int dx = 0, int dy = 0) :
          i(x), j(y), i0(0)
        {
          data = reinterpret_cast<T*>(image->imageData);
          step = image->widthStep / sizeof(T);

          nl = image->height;
          if ((y + dy) > 0 && (y + dy) < nl)
            nl = y + dy;

          if (y < 0)
            j = 0;

          data += step*j;

          nc = image->width;
          if ((x + dx) > 0 && (x + dx) < nc)
            nc = x + dx;

          nc *= image->nChannels;
          if (x > 0)
            i0 = x*image->nChannels;
          i = i0;

          nch = image->nChannels;
        }

        /* has next ? */
        bool operator!() const { return j < nl; }

        /* next pixel */
        ImageIterator& operator++()
        {
          i++;
          if (i >= nc)
          {
            i = i0;
            j++;
            data += step;
          }
          return *this;
        }

        ImageIterator& operator+=(int s)
        {
          i += s;
          if (i >= nc)
          {
            i = i0;
            j++;
            data += step;
          }
          return *this;
        }

        /* pixel access */
        T& operator*() { return data[i]; }

        const T operator*() const { return data[i]; }

        const T neighbor(int dx, int dy) const
        {
          return *(data + dy*step + i + dx);
        }

        T* operator&() const { return data + i; }

        /* current pixel coordinates */
        int column() const { return i / nch; }
        int line() const { return j; }

      private:
        int i, i0, j;
        T* data;
        int step;
        int nl, nc;
        int nch;
      };

      // --- Constants --------------------------------------------------------------

      const unsigned char NUM_CHANNELS = 3;

      // --- Pixel Types ------------------------------------------------------------

      class RgbPixel
      {
      public:
        RgbPixel() { ; }
        RgbPixel(unsigned char _r, unsigned char _g, unsigned char _b)
        {
          ch[0] = _r; ch[1] = _g; ch[2] = _b;
        }

        RgbPixel& operator=(const RgbPixel& rhs)
        {
          ch[0] = rhs.ch[0]; ch[1] = rhs.ch[1]; ch[2] = rhs.ch[2];
          return *this;
        }

        inline unsigned char& operator()(const int _ch)
        {
          return ch[_ch];
        }

        inline unsigned char operator()(const int _ch) const
        {
          return ch[_ch];
        }

        unsigned char ch[3];
      };

      class RgbPixelFloat
      {
      public:
        RgbPixelFloat() { ; }
        RgbPixelFloat(float _r, float _g, float _b)
        {
          ch[0] = _r; ch[1] = _g; ch[2] = _b;
        }

        RgbPixelFloat& operator=(const RgbPixelFloat& rhs)
        {
          ch[0] = rhs.ch[0]; ch[1] = rhs.ch[1]; ch[2] = rhs.ch[2];
          return *this;
        }

        inline float& operator()(const int _ch)
        {
          return ch[_ch];
        }

        inline float operator()(const int _ch) const
        {
          return ch[_ch];
        }

        float ch[3];
      };

      // --- Image Types ------------------------------------------------------------

      class ImageBase
      {
      public:
        ImageBase(IplImage* img = NULL) { imgp = img; m_bReleaseMemory = true; }
        ~ImageBase();

        void ReleaseMemory(bool b) { m_bReleaseMemory = b; }

        IplImage* Ptr() { return imgp; }
        const IplImage* Ptr() const { return imgp; }

        void ReleaseImage()
        {
          cvReleaseImage(&imgp);
        }

        void operator=(IplImage* img)
        {
          imgp = img;
        }

        // copy-constructor
        ImageBase(const ImageBase& rhs)
        {
          // it is very inefficent if this copy-constructor is called
          assert(false);
        }

        // assignment operator
        ImageBase& operator=(const ImageBase& rhs)
        {
          // it is very inefficent if operator= is called
          assert(false);

          return *this;
        }

        virtual void Clear() = 0;

      protected:
        IplImage* imgp;
        bool m_bReleaseMemory;
      };

      class RgbImage : public ImageBase
      {
      public:
        RgbImage(IplImage* img = NULL) : ImageBase(img) { ; }

        virtual void Clear()
        {
          cvZero(imgp);
        }

        void operator=(IplImage* img)
        {
          imgp = img;
        }

        // channel-level access using image(row, col, channel)
        inline unsigned char& operator()(const int r, const int c, const int ch)
        {
          return (unsigned char &)imgp->imageData[r*imgp->widthStep + c*imgp->nChannels + ch];
        }

        inline const unsigned char& operator()(const int r, const int c, const int ch) const
        {
          return (unsigned char &)imgp->imageData[r*imgp->widthStep + c*imgp->nChannels + ch];
        }

        // RGB pixel-level access using image(row, col)
        inline RgbPixel& operator()(const int r, const int c)
        {
          return (RgbPixel &)imgp->imageData[r*imgp->widthStep + c*imgp->nChannels];
        }

        inline const RgbPixel& operator()(const int r, const int c) const
        {
          return (RgbPixel &)imgp->imageData[r*imgp->widthStep + c*imgp->nChannels];
        }
      };

      class RgbImageFloat : public ImageBase
      {
      public:
        RgbImageFloat(IplImage* img = NULL) : ImageBase(img) { ; }

        virtual void Clear()
        {
          cvZero(imgp);
        }

        void operator=(IplImage* img)
        {
          imgp = img;
        }

        // channel-level access using image(row, col, channel)
        inline float& operator()(const int r, const int c, const int ch)
        {
          return (float &)imgp->imageData[r*imgp->widthStep + (c*imgp->nChannels + ch) * sizeof(float)];
        }

        inline float operator()(const int r, const int c, const int ch) const
        {
          return (float)imgp->imageData[r*imgp->widthStep + (c*imgp->nChannels + ch) * sizeof(float)];
        }

        // RGB pixel-level access using image(row, col)
        inline RgbPixelFloat& operator()(const int r, const int c)
        {
          return (RgbPixelFloat &)imgp->imageData[r*imgp->widthStep + c*imgp->nChannels * sizeof(float)];
        }

        inline const RgbPixelFloat& operator()(const int r, const int c) const
        {
          return (RgbPixelFloat &)imgp->imageData[r*imgp->widthStep + c*imgp->nChannels * sizeof(float)];
        }
      };

      class BwImage : public ImageBase
      {
      public:
        BwImage(IplImage* img = NULL) : ImageBase(img) { ; }

        virtual void Clear()
        {
          cvZero(imgp);
        }

        void operator=(IplImage* img)
        {
          imgp = img;
        }

        // pixel-level access using image(row, col)
        inline unsigned char& operator()(const int r, const int c)
        {
          return (unsigned char &)imgp->imageData[r*imgp->widthStep + c];
        }

        inline unsigned char operator()(const int r, const int c) const
        {
          return (unsigned char)imgp->imageData[r*imgp->widthStep + c];
        }
      };

      class BwImageFloat : public ImageBase
      {
      public:
        BwImageFloat(IplImage* img = NULL) : ImageBase(img) { ; }

        virtual void Clear()
        {
          cvZero(imgp);
        }

        void operator=(IplImage* img)
        {
          imgp = img;
        }

        // pixel-level access using image(row, col)
        inline float& operator()(const int r, const int c)
        {
          return (float &)imgp->imageData[r*imgp->widthStep + c * sizeof(float)];
        }

        inline float operator()(const int r, const int c) const
        {
          return (float)imgp->imageData[r*imgp->widthStep + c * sizeof(float)];
        }
      };

      // --- Image Functions --------------------------------------------------------

      //void DensityFilter(BwImage& image, BwImage& filtered, int minDensity, unsigned char fgValue);
    }
  }
}

#endif
