// Copyright (C) 2007 by Cristóbal Carnero Liñán
// grendel.ccl@gmail.com
//
// This file is part of cvBlob.
//
// cvBlob is free software: you can redistribute it and/or modify
// it under the terms of the Lesser GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// cvBlob is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// Lesser GNU General Public License for more details.
//
// You should have received a copy of the Lesser GNU General Public License
// along with cvBlob.  If not, see <http://www.gnu.org/licenses/>.
//

#include <cmath>
#include <iostream>
using namespace std;

#if (defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__) || defined(__WINDOWS__) || (defined(__APPLE__) & defined(__MACH__)))
#include <cv.h>
#include <highgui.h>
#elif (CV_MAJOR_VERSION == 2) && (CV_MINOR_VERSION < 2)
#include <opencv/cv.h>
#include <opencv/highgui.h>
#else
#include <opencv2/opencv.hpp>
#endif

#include "cvblob.h"

namespace cvb
{

  CvLabel cvGreaterBlob(const CvBlobs &blobs)
  {
    CvLabel label=0;
    unsigned int maxArea=0;

    for (CvBlobs::const_iterator it=blobs.begin();it!=blobs.end();++it)
    {
      CvBlob *blob=(*it).second;
      //if ((!blob->_parent)&&(blob->area>maxArea))
      if (blob->area>maxArea)
      {
	label=blob->label;
	maxArea=blob->area;
      }
    }

    return label;
  }

  void cvFilterByArea(CvBlobs &blobs, unsigned int minArea, unsigned int maxArea)
  {
    CvBlobs::iterator it=blobs.begin();
    while(it!=blobs.end())
    {
      CvBlob *blob=(*it).second;
      if ((blob->area<minArea)||(blob->area>maxArea))
      {
	cvReleaseBlob(blob);

	CvBlobs::iterator tmp=it;
	++it;
	blobs.erase(tmp);
      }
      else
	++it;
    }
  }

  void cvFilterByLabel(CvBlobs &blobs, CvLabel label)
  {
    CvBlobs::iterator it=blobs.begin();
    while(it!=blobs.end())
    {
      CvBlob *blob=(*it).second;
      if (blob->label!=label)
      {
	delete blob;
	CvBlobs::iterator tmp=it;
	++it;
	blobs.erase(tmp);
      }
      else
	++it;
    }
  }

  /*void cvCentralMoments(CvBlob *blob, const IplImage *img)
  {
    CV_FUNCNAME("cvCentralMoments");
    __CV_BEGIN__;
    if (!blob->centralMoments)
    {
      CV_ASSERT(img&&(img->depth==IPL_DEPTH_LABEL)&&(img->nChannels==1));

      //cvCentroid(blob); // Here?

      blob->u11=blob->u20=blob->u02=0.;

      // Only in the bounding box
      int stepIn = img->widthStep / (img->depth / 8);
      int img_width = img->width;
      int img_height = img->height;
      int img_offset = 0;
      if(0 != img->roi)
      {
	img_width = img->roi->width;
	img_height = img->roi->height;
	img_offset = img->roi->xOffset + (img->roi->yOffset * stepIn);
      }

      CvLabel *imgData=(CvLabel *)img->imageData + (blob->miny * stepIn) + img_offset;
      for (unsigned int r=blob->miny;
	  r<blob->maxy;
	  r++,imgData+=stepIn)
	for (unsigned int c=blob->minx;c<blob->maxx;c++)
	  if (imgData[c]==blob->label)
	  {
	    double tx=(c-blob->centroid.x);
	    double ty=(r-blob->centroid.y);
	    blob->u11+=tx*ty;
	    blob->u20+=tx*tx;
	    blob->u02+=ty*ty;
	  }

      blob->centralMoments = true;
    }
    __CV_END__;
  }*/

  void cvRenderBlob(const IplImage *imgLabel, CvBlob *blob, IplImage *imgSource, IplImage *imgDest, unsigned short mode, CvScalar const &color, double alpha)
  {
    CV_FUNCNAME("cvRenderBlob");
    __CV_BEGIN__;

    CV_ASSERT(imgLabel&&(imgLabel->depth==IPL_DEPTH_LABEL)&&(imgLabel->nChannels==1));
    CV_ASSERT(imgDest&&(imgDest->depth==IPL_DEPTH_8U)&&(imgDest->nChannels==3));

    if (mode&CV_BLOB_RENDER_COLOR)
    {
      int stepLbl = imgLabel->widthStep/(imgLabel->depth/8);
      int stepSrc = imgSource->widthStep/(imgSource->depth/8);
      int stepDst = imgDest->widthStep/(imgDest->depth/8);
      int imgLabel_width = imgLabel->width;
      int imgLabel_height = imgLabel->height;
      int imgLabel_offset = 0;
      int imgSource_width = imgSource->width;
      int imgSource_height = imgSource->height;
      int imgSource_offset = 0;
      int imgDest_width = imgDest->width;
      int imgDest_height = imgDest->height;
      int imgDest_offset = 0;
      if(imgLabel->roi)
      {
	imgLabel_width = imgLabel->roi->width;
	imgLabel_height = imgLabel->roi->height;
	imgLabel_offset = (imgLabel->nChannels * imgLabel->roi->xOffset) + (imgLabel->roi->yOffset * stepLbl);
      }
      if(imgSource->roi)
      {
	imgSource_width = imgSource->roi->width;
	imgSource_height = imgSource->roi->height;
	imgSource_offset = (imgSource->nChannels * imgSource->roi->xOffset) + (imgSource->roi->yOffset * stepSrc);
      }
      if(imgDest->roi)
      {
	imgDest_width = imgDest->roi->width;
	imgDest_height = imgDest->roi->height;
	imgDest_offset = (imgDest->nChannels * imgDest->roi->xOffset) + (imgDest->roi->yOffset * stepDst);
      }

      CvLabel *labels = (CvLabel *)imgLabel->imageData + imgLabel_offset + (blob->miny * stepLbl);
      unsigned char *source = (unsigned char *)imgSource->imageData + imgSource_offset + (blob->miny * stepSrc);
      unsigned char *imgData = (unsigned char *)imgDest->imageData + imgDest_offset + (blob->miny * stepDst);

      for (unsigned int r=blob->miny; r<blob->maxy; r++, labels+=stepLbl, source+=stepSrc, imgData+=stepDst)
	for (unsigned int c=blob->minx; c<blob->maxx; c++)
	{
	  if (labels[c]==blob->label)
	  {
	    imgData[imgDest->nChannels*c+0] = (unsigned char)((1.-alpha)*source[imgSource->nChannels*c+0]+alpha*color.val[0]);
	    imgData[imgDest->nChannels*c+1] = (unsigned char)((1.-alpha)*source[imgSource->nChannels*c+1]+alpha*color.val[1]);
	    imgData[imgDest->nChannels*c+2] = (unsigned char)((1.-alpha)*source[imgSource->nChannels*c+2]+alpha*color.val[2]);
	  }
	}
    }

    if (mode)
    {
      if (mode&CV_BLOB_RENDER_TO_LOG)
      {
	std::clog << "Blob " << blob->label << std::endl;
	std::clog << " - Bounding box: (" << blob->minx << ", " << blob->miny << ") - (" << blob->maxx << ", " << blob->maxy << ")" << std::endl;
	std::clog << " - Bounding box area: " << (1 + blob->maxx - blob->minx) * (1 + blob->maxy - blob->miny) << std::endl;
	std::clog << " - Area: " << blob->area << std::endl;
	std::clog << " - Centroid: (" << blob->centroid.x << ", " << blob->centroid.y << ")" << std::endl;
	std::clog << std::endl;
      }

      if (mode&CV_BLOB_RENDER_TO_STD)
      {
	std::cout << "Blob " << blob->label << std::endl;
	std::cout << " - Bounding box: (" << blob->minx << ", " << blob->miny << ") - (" << blob->maxx << ", " << blob->maxy << ")" << std::endl;
	std::cout << " - Bounding box area: " << (1 + blob->maxx - blob->minx) * (1 + blob->maxy - blob->miny) << std::endl;
	std::cout << " - Area: " << blob->area << std::endl;
	std::cout << " - Centroid: (" << blob->centroid.x << ", " << blob->centroid.y << ")" << std::endl;
	std::cout << std::endl;
      }

      if (mode&CV_BLOB_RENDER_BOUNDING_BOX)
	cvRectangle(imgDest, cvPoint(blob->minx, blob->miny), cvPoint(blob->maxx-1, blob->maxy-1), CV_RGB(255., 0., 0.));

      if (mode&CV_BLOB_RENDER_ANGLE)
      {
	double angle = cvAngle(blob);

	double x1,y1,x2,y2;
	double lengthLine = MAX(blob->maxx-blob->minx, blob->maxy-blob->miny)/2.;

	x1=blob->centroid.x-lengthLine*cos(angle);
	y1=blob->centroid.y-lengthLine*sin(angle);
	x2=blob->centroid.x+lengthLine*cos(angle);
	y2=blob->centroid.y+lengthLine*sin(angle);
	cvLine(imgDest,cvPoint(int(x1),int(y1)),cvPoint(int(x2),int(y2)),CV_RGB(0.,255.,0.));
      }

      if (mode&CV_BLOB_RENDER_CENTROID)
      {
	cvLine(imgDest,cvPoint(int(blob->centroid.x)-3,int(blob->centroid.y)),cvPoint(int(blob->centroid.x)+3,int(blob->centroid.y)),CV_RGB(0.,0.,255.));
	cvLine(imgDest,cvPoint(int(blob->centroid.x),int(blob->centroid.y)-3),cvPoint(int(blob->centroid.x),int(blob->centroid.y)+3),CV_RGB(0.,0.,255.));
      }
    }

    __CV_END__;
  }

  ///////////////////////////////////////////////////////////////////////////////////////////////////
  // Based on http://en.wikipedia.org/wiki/HSL_and_HSV

  /// \def _HSV2RGB_(H, S, V, R, G, B)
  /// \brief Color translation between HSV and RGB.
#define _HSV2RGB_(H, S, V, R, G, B) \
  { \
    double _h = H/60.; \
    int _hf = (int)floor(_h); \
    int _hi = ((int)_h)%6; \
    double _f = _h - _hf; \
    \
    double _p = V * (1. - S); \
    double _q = V * (1. - _f * S); \
    double _t = V * (1. - (1. - _f) * S); \
    \
    switch (_hi) \
    { \
      case 0: \
	      R = 255.*V; G = 255.*_t; B = 255.*_p; \
      break; \
      case 1: \
	      R = 255.*_q; G = 255.*V; B = 255.*_p; \
      break; \
      case 2: \
	      R = 255.*_p; G = 255.*V; B = 255.*_t; \
      break; \
      case 3: \
	      R = 255.*_p; G = 255.*_q; B = 255.*V; \
      break; \
      case 4: \
	      R = 255.*_t; G = 255.*_p; B = 255.*V; \
      break; \
      case 5: \
	      R = 255.*V; G = 255.*_p; B = 255.*_q; \
      break; \
    } \
  }
  ///////////////////////////////////////////////////////////////////////////////////////////////////

  typedef std::map<CvLabel, CvScalar> Palete;

  void cvRenderBlobs(const IplImage *imgLabel, CvBlobs &blobs, IplImage *imgSource, IplImage *imgDest, unsigned short mode, double alpha)
  {
    CV_FUNCNAME("cvRenderBlobs");
    __CV_BEGIN__;
    {

      CV_ASSERT(imgLabel&&(imgLabel->depth==IPL_DEPTH_LABEL)&&(imgLabel->nChannels==1));
      CV_ASSERT(imgDest&&(imgDest->depth==IPL_DEPTH_8U)&&(imgDest->nChannels==3));

      Palete pal;
      if (mode&CV_BLOB_RENDER_COLOR)
      {

	unsigned int colorCount = 0;
	for (CvBlobs::const_iterator it=blobs.begin(); it!=blobs.end(); ++it)
	{
	  CvLabel label = (*it).second->label;

	  double r, g, b;

	  _HSV2RGB_((double)((colorCount*77)%360), .5, 1., r, g, b);
	  colorCount++;

	  pal[label] = CV_RGB(r, g, b);
	}
      }

      for (CvBlobs::iterator it=blobs.begin(); it!=blobs.end(); ++it)
	cvRenderBlob(imgLabel, (*it).second, imgSource, imgDest, mode, pal[(*it).second->label], alpha);

    }
    __CV_END__;
  }

  // Returns radians
  double cvAngle(CvBlob *blob)
  {
    CV_FUNCNAME("cvAngle");
    __CV_BEGIN__;

    return .5*atan2(2.*blob->u11,(blob->u20-blob->u02));

    __CV_END__;
  }

  void cvSaveImageBlob(const char *filename, IplImage *img, CvBlob const *blob)
  {
    CvRect roi = cvGetImageROI(img);
    cvSetImageROItoBlob(img, blob);
    cvSaveImage(filename, img);
    cvSetImageROI(img, roi);
  }

}

ostream& operator<< (ostream& output, const cvb::CvBlob& b)
{
  output << b.label << ": " << b.area << ", (" << b.centroid.x << ", " << b.centroid.y << "), [(" << b.minx << ", " << b.miny << ") - (" << b.maxx << ", " << b.maxy << ")]";

  return output;
}
