/*
    Copyright (c) 2010, 
	Luis Alvarez <lalvarez@dis.ulpgc.es>
	Pedro Henriquez <phenriquez@ctim.es>
	All rights reserved.
	
	This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/**
 * \page code_contours.h \link contours.h \endlink
 * \brief Code of  \ref code_contours.h "contours.h"
 * \includelineno contours.h
 */

/**
 * \file contours.h
 * \brief contours class definition
 * \author Luis Alvarez \n \n
 * \brief \b Code: \n
 * - \ref code_contours.h "contours.h"
*/


#ifndef contours_H
#define contours_H

#ifndef NOMINMAX
#define NOMINMAX
#endif


#include <iostream>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <vector>
#include "point2d.h"


//#define CONTOURS_DEBUG

using namespace std;

namespace ami
{

/**
 * \class contours
 * \brief class to store 2D contours and basic methods
 * \author Luis Alvarez
 */

template <class T>
class contours{
  vector <point2d <T> > v; //VECTOR CONTAINING THE CONTOUR POINTS COORDINATES
  int id; //IDENTIFICATOR, USED TO MANAGE DIFFERENT CONTOURS IN THE SAME IMAGE OR SEQUENCE
  point2d<T> p_max,p_min; //POINTS WITH MAXIMUM AND MINIMUN CONTOUR COORDINATES
                          // p_max: max x, max y
                          // p_min: min x, min y

  public:

  string region_type,region_subtype;
  contours(){};
  ~contours(){};
  explicit contours(const int n);
  void push_back(const point2d<T> &p2d);
  point2d<T>& operator[](const int i);
  const point2d<T>& operator[](const int i) const;
  contours<T> & operator=(const contours<T> &c1);
  int size() const;
  void resize(const int n);
  point2d<T> min();
  const point2d<T> min() const;
  point2d<T> max();
  const point2d<T> max() const;
  vector<point2d<T> > get_points(){return v;}
  void pop_back(){v.pop_back();}
  void set_v(vector<point2d<T> > cnt){v=cnt;}
  void remove(const int pos_min,const int pos_max);
  void erase(unsigned int pos){v.erase(v.begin()+pos);}
  int get_id(){return id;}
  void set_id(int nid){id=nid;}
  void clear(){v.clear();}
  void print();
  void add_segment(const point2d<T> p0,const point2d<T> p1);
  void add_circle(point2d<T> center,float radius);
  void set_p_max(point2d<T> p1){p_max=p1;};
  void set_p_min(point2d<T> p1){p_min=p1;};
  void set_p_max_min(point2d<T> pmax,point2d<T> pmin){p_max=pmax; p_min=pmin;};
  point2d<T> get_p_max(){return p_max;};
  point2d<T> get_p_min(){return p_min;};
  void actualize_max_min(){p_max=max();p_min=min();};
  void smooth(int rad);

};


/**
 * \class contours_collection
 * \brief class to store a 2D contour collection and basic methods
 * \author Luis Alvarez
 */

template <class T>
class contours_collection{
  vector <contours <T> > v; //VECTOR WITH THE CONTOURS COLLECTION

  public:

  contours_collection(){};
  ~contours_collection(){};
  explicit contours_collection(const int n);

  void push_back(const contours<T> &c);
  contours<T>& operator[](const int i);
  const contours<T>& operator[](const int i) const;
  int size() const;
  void resize(const int n);
  void pop_back(){v.pop_back();}
  void erase(int eliminar){v.erase(v.begin()+eliminar);}
  int read(char name[300]);
  int write(char name[300]);
  int find_nearest_contour(point2d<T> point);
  bool empty(){return v.empty();}
  int find_id(int fid);
  void clear(){for(int i=0;i<v.size();i++)v[i].remove(0,v[i].size()); v.clear();}
};

}

/**
 * \fn template <class T> void ami::contours<T>::add_segment(const point2d<T> p0,const point2d<T> p1)
 * \brief function to add a segment at the end of a contour
 * \author Luis Alvarez
 */
template <class T>
void ami::contours<T>::add_segment(
const point2d<T> p0 /** INITIAL POINT (INCLUDED IN THE SEGMENT)*/,
const point2d<T> p1 /** FINAL POINT (DO NOT INCLUDED IN THE SEGMENT) */){

  long x0=(long) p0.x;
  long x1=(long) p1.x;
  long y0=(long) p0.y;
  long y1=(long) p1.y;

  long incx =x1>x0?(x1-x0):(x0-x1);
  long incy =y1>y0?(y1-y0):(y0-y1);
  long  p, dx, dy;
  long x, y;
  long temp;


 if (incx>incy) {
   if (x0>x1){
     temp=x0; x0=x1; x1=temp;
     temp=y0; y0=y1; y1=temp;
   }
   x=x0; y=y0;
   p = 2*incy - incx;
   dy = (y1>y0)? 1 : -1;

   point2d<T> punto1((T) x,(T) y);
   v.push_back(punto1);


   do {
     if (p<0) {
       p += 2*incy;
     } else {
       p += 2*incy - 2*incx;
       y += dy;
     }
     x++;
     point2d<T> punto2((T) x,(T) y);
     v.push_back(punto2);

   } while (x<x1);
 } else {
     if (y0>y1) {
       temp=x0; x0=x1; x1=temp;
       temp=y0; y0=y1; y1=temp;
     }
     x=x0; y=y0;
     p = 2*incx - incy;
     dx = (x1>x0)? 1 : -1;

     point2d<T> punto3((T) x,(T) y);
     v.push_back(punto3);

     do {
       if (p<0) {
         p += 2*incx;
       } else {
         p += 2*incx - 2*incy;
	     x += dx;
       }
       y++;

       point2d<T> punto4((T) x,(T) y);
       v.push_back(punto4);

    } while (y<y1);
  }
}


/**
  * \fn template <class T> void ami::contours<T>::remove(const int pos_min,const int pos_max)
  * \brief function to remove contour points from pos_min to pos_max position
  * \author Luis Alvarez
*/
template <class T>
void ami::contours<T>::remove(const int pos_min,const int pos_max){
  if(pos_min>=0 && pos_max<v.size())
    v.erase(v.begin()+pos_min,v.begin()+pos_max);
}


/**
  * \fn template <class T> ami::contours<T>::contours(const  int n)
  * \brief constructor taking memory for contours
  * \author Luis Alvarez
*/
template <class T>
ami::contours<T>::contours(const  int n)
{
  #ifdef CONTOURS_DEBUG
    if(n<0){
      printf("contours class: constructor size error\n");
      printf("resize dimension = %d\n",n);
      int j; scanf("%d",&j);
    }
  #endif
  v.resize(n);
}


/**
  * \fn template <class T> ami::point2d<T>& ami::contours<T>::operator[](const int i)
  * \brief operator [] to access contour point
  * \author Luis Alvarez
*/
template <class T>
ami::point2d<T>& ami::contours<T>::operator[](const int i)
{
  #ifdef CONTOURS_DEBUG
    if(i<0 || i>=v.size()){
      printf("contours class: bounds error vector access\n");
      printf("v.size()=%d index to accces the vector =%d\n",v.size(),i);
      int j; scanf("%d",&j);
    }
  #endif
  return v[i];
}

/**
  * \fn template <class T> const ami::point2d<T>& ami::contours<T>::operator[](const int i) const
  * \brief operator [] to access contour point
  * \author Luis Alvarez
*/
template <class T>
const ami::point2d<T>& ami::contours<T>::operator[](const int i) const
{
  #ifdef CONTOURS_DEBUG
    if(i<0 || i>=v.size()){
      printf("contours class: bounds error vector access\n");
      printf("v.size()=%d index to accces the vector =%d\n",v.size(),i);
      int j; scanf("%d",&j);
  }
  #endif
  return v[i];
}

/**
  * \fn template <class T> ami::contours<T> & ami::contours<T>::operator=(const contours<T> &c1)
  * \brief Assignment operator
  * \author Pedro Henriquez
*/
template <class T>
ami::contours<T> & ami::contours<T>::operator=(const contours<T> &c1)
{
  v=c1.v;
  id=c1.id;
  p_max=c1.p_max;
  p_min=c1.p_min;
  region_type=c1.region_type;
  region_subtype=c1.region_subtype;
  return *this;
}

/**
  * \fn template <class T> int ami::contours<T>::size() const
  * \brief function to return contour point vector size
  * \author Luis Alvarez
*/
template <class T>
int ami::contours<T>::size() const { return(v.size()); }

/**
  * \fn template <class T> void ami::contours<T>::push_back(const ami::point2d<T> &p2d)
  * \brief function to add a contour point
  * \author Luis Alvarez
*/
template <class T>
void ami::contours<T>::push_back(const ami::point2d<T> &p2d){ v.push_back(p2d);}

/**
  * \fn template <class T> void ami::contours<T>::resize(const int n)
  * \brief function to resize contour point vector
  * \author Luis Alvarez
*/
template <class T>
void ami::contours<T>::resize(const int n)
{
  #ifdef CONTOURS_DEBUG
    if(n<0){
      printf("contours class: resize error\n");
      printf("resize dimension = %d\n",n);
      int j; scanf("%d",&j);
  }
  #endif
  v.resize(n);
}

/**
  * \fn template <class T> ami::point2d<T> ami::contours<T>::min()
  * \brief function to compute the minimun of contour coordinates
  * \author Luis Alvarez
*/
template <class T>
ami::point2d<T> ami::contours<T>::min()
{
  #ifdef CONTOURS_DEBUG
    if(v.size()==0){
      printf("contours::min() error : v.size()=0\n");
      int j; scanf("%d",&j);
    }
  #endif
  point2d<T> p2d=v[0];
  for(unsigned int i=1;i<v.size();i++){
    if(v[i].x<p2d.x) p2d.x=v[i].x;
    if(v[i].y<p2d.y) p2d.y=v[i].y;
  }
  return(p2d);
}

/**
  * \fn template <class T> ami::point2d<T> ami::contours<T>::max()
  * \brief function to compute the maximun of contour coordinates
  * \author Luis Alvarez
*/
template <class T>
ami::point2d<T> ami::contours<T>::max()
{
  #ifdef CONTOURS_DEBUG
    if(v.size()==0){
      printf("contours::min() error : v.size()=0\n");
      int j; scanf("%d",&j);
    }
  #endif
  point2d<T> p2d=v[0];
  for(unsigned int i=1;i<v.size();i++){
    if(v[i].x>p2d.x) p2d.x=v[i].x;
    if(v[i].y>p2d.y) p2d.y=v[i].y;
  }
  return(p2d);
}

/**
  * \fn template <class T> const ami::point2d<T>  ami::contours<T>::min() const
  * \brief function to compute the minimun of contour coordinates
  * \author Luis Alvarez
*/
template <class T>
const ami::point2d<T>  ami::contours<T>::min() const
{
  #ifdef CONTOURS_DEBUG
    if(v.size()==0){
      printf("contours::min() error : v.size()=0\n");
      int j; scanf("%d",&j);
    }
  #endif
  point2d<T> p2d=v[0];
  for(unsigned int i=1;i<v.size();i++){
    if(v[i].x<p2d.x) p2d.x=v[i].x;
    if(v[i].y<p2d.y) p2d.y=v[i].y;
  }
  return(p2d);
}

/**
  * \fn template <class T> const ami::point2d<T> ami::contours<T>::max()  const
  * \brief function to compute the maximun of contour coordinates
  * \author Luis Alvarez
*/
template <class T>
const ami::point2d<T> ami::contours<T>::max()  const
{
  #ifdef CONTOURS_DEBUG
    if(v.size()==0){
      printf("contours::min() error : v.size()=0\n");
      int j; scanf("%d",&j);
    }
  #endif
  point2d<T> p2d=v[0];
  for(unsigned int i=1;i<v.size();i++){
    if(v[i].x>p2d.x) p2d.x=v[i].x;
    if(v[i].y>p2d.y) p2d.y=v[i].y;
  }
  return(p2d);
}

/**
  * \fn template <class T> void ami::contours<T>::add_circle(point2d<T> center,float radius)
  * \brief Add a circle to the contour
  * \param [in] center Coordinates of the circle center.
  * \param [in] radius Radius of the circle.
  * \author Pedro Henriquez
*/
template <class T>
void ami::contours<T>::add_circle(point2d<T> center,float radius)
{
  int x, y, p;

  //DRAW OCTANT ++
  x = 0;
  y = radius;
  p = 1 - radius;
  this->push_back(point2d<float> (center.x+x,center.y+y));
  while (x < y)
  {
    x = x + 1;
    if (p < 0)p = p + 2*x + 1;
    else
    {
      y = y - 1;
      p = p + 2*(x - y) + 1;
    }
    this->push_back(point2d<float> (center.x+x,center.y+y));
  }
  //DRAW OCTANTS ++
  x = 0;
  y = radius;
  p = 1 - radius;
  this->push_back(point2d<float> (center.x+y,center.y+x));
  while (x < y)
  {
    x = x + 1;
    if (p < 0)p = p + 2*x + 1;
    else
    {
      y = y - 1;
      p = p + 2*(x - y) + 1;
    }
    this->push_back(point2d<float> (center.x+y,center.y+x));
  }
  //DRAW OCTANT +-
  x = 0;
  y = radius;
  p = 1 - radius;
  this->push_back(point2d<float> (center.x+y,center.y-x));
  while (x < y)
  {
    x = x + 1;
    if (p < 0)p = p + 2*x + 1;
    else
    {
      y = y - 1;
      p = p + 2*(x - y) + 1;
    }
    this->push_back(point2d<float> (center.x+y,center.y-x));
  }
  //DRAW OCTANT +-
  x = 0;
  y = radius;
  p = 1 - radius;
  this->push_back(point2d<float> (center.x+x,center.y-y));
  while (x < y)
  {
    x = x + 1;
    if (p < 0)p = p + 2*x + 1;
    else
    {
      y = y - 1;
      p = p + 2*(x - y) + 1;
    }
    this->push_back(point2d<float> (center.x+x,center.y-y));
  }

  //DRAW OCTANT --
  x = 0;
  y = radius;
  p = 1 - radius;
  this->push_back(point2d<float> (center.x-x,center.y-y));
  while (x < y)
  {
    x = x + 1;
    if (p < 0)p = p + 2*x + 1;
    else
    {
      y = y - 1;
      p = p + 2*(x - y) + 1;
    }
    this->push_back(point2d<float> (center.x-x,center.y-y));
  }

  //DRAW OCTANTS --
  x = 0;
  y = radius;
  p = 1 - radius;
  this->push_back(point2d<float> (center.x-y,center.y-x));
  while (x < y)
  {
    x = x + 1;
    if (p < 0)p = p + 2*x + 1;
    else
    {
      y = y - 1;
      p = p + 2*(x - y) + 1;
    }
    this->push_back(point2d<float> (center.x-y,center.y-x));
  }

  //DRAW OCTANTS -+
  x = 0;
  y = radius;
  p = 1 - radius;
  this->push_back(point2d<float> (center.x-y,center.y+x));
  while (x < y)
  {
    x = x + 1;
    if (p < 0)p = p + 2*x + 1;
    else
    {
      y = y - 1;
      p = p + 2*(x - y) + 1;
    }
    this->push_back(point2d<float> (center.x-y,center.y+x));
  }

  //DRAW OCTANTS -+
  x = 0;
  y = radius;
  p = 1 - radius;
  this->push_back(point2d<float> (center.x-x,center.y+y));
  while (x < y)
  {
    x = x + 1;
    if (p < 0)p = p + 2*x + 1;
    else
    {
      y = y - 1;
      p = p + 2*(x - y) + 1;
    }
    this->push_back(point2d<float> (center.x-x,center.y+y));
  }
}

/**
 * \fn template <class T> void ami::contours<T>::smooth(int rad)
 * \brief Make smooth the contour
 * \param [in] rad Radius used in the process
 * \author José Miguel Santana Núñez
*/
template <class T>
void ami::contours<T>::smooth(int rad)
{
    point2d<T> auxv[v.size()];
    for(int i = 0; i < v.size(); i++) auxv[i] = v[i];
    for(int q = 0; q < rad; q++){
        auxv[0].x = (auxv[v.size()-1].x + auxv[0].x + auxv[1].x) / 3.;
        auxv[0].y = (auxv[v.size()-1].y +  auxv[0].y + auxv[1].y) / 3.;

        auxv[v.size()-1].x = (auxv[0].x + auxv[v.size()-1].x + auxv[v.size()-2].x) / 3.;
        auxv[v.size()-1].y = (auxv[0].y + auxv[v.size()-1].y + auxv[v.size()-2].y) / 3.;

        for(int i = 1; i < v.size() - 1; i++){
            auxv[i].x = (v[i].x + v[i-1].x + v[i+1].x) / 3.;
            auxv[i].y = (v[i].y + v[i-1].y + v[i+1].y) / 3.;
        }

        for(int i = 0; i < v.size(); i++) v[i] = auxv[i];
    }
}


/***********************************************************************
     contours_collection METHODS
************************************************************************/

/**
  * \fn template <class T> ami::contours_collection<T>::contours_collection(const  int n)
  * \brief constructor taking memory for contours_collection
  * \author Luis Alvarez
*/
template <class T>
ami::contours_collection<T>::contours_collection(const  int n)
{
  #ifdef CONTOURS_DEBUG
    if(n<0){
      printf("contours_collection class: constructor size error\n");
      printf("resize dimension = %d\n",n);
      int j; scanf("%d",&j);
  }
  #endif
  v.resize(n);
}


/**
  * \fn template <class T> ami::contours<T>& ami::contours_collection<T>::operator[](const int i)
  * \brief operator [] to access contours
  * \author Luis Alvarez
*/
template <class T>
ami::contours<T>& ami::contours_collection<T>::operator[](const int i)
{
  #ifdef CONTOURS_DEBUG
    if(i<0 || i>=v.size()){
      printf("contours_collection class: bounds error vector access\n");
      printf("v.size()=%d index to accces the vector =%d\n",v.size(),i);
      int j; scanf("%d",&j);
    }
  #endif
  return v[i];
}

/**
  * \fn template <class T> const ami::contours<T>& ami::contours_collection<T>::operator[](const int i) const
  * \brief operator [] to access contours
  * \author Luis Alvarez
*/
template <class T>
const ami::contours<T>& ami::contours_collection<T>::operator[](const int i) const
{
  #ifdef CONTOURS_DEBUG
    if(i<0 || i>=v.size()){
      printf("contours_collection class: bounds error vector access\n");
      printf("v.size()=%d index to accces the vector =%d\n",v.size(),i);
      int j; scanf("%d",&j);
  }
  #endif
  return v[i];
}

/**
  * \fn template <class T> int ami::contours_collection<T>::size() const
  * \brief function to return contour_collection vector size
  * \author Luis Alvarez
*/
template <class T>
int ami::contours_collection<T>::size() const { return(v.size()); }

/**
  * \fn template <class T> void ami::contours_collection<T>::push_back(const ami::contours<T> &c)
  * \brief function to add a contours
  * \author Luis Alvarez
*/
template <class T>
void ami::contours_collection<T>::push_back(const ami::contours<T> &c){ v.push_back(c);}

/**
  * \fn template <class T> void ami::contours_collection<T>::resize(const int n)
  * \brief function to resize contour_collection vector
  * \author Luis Alvarez
*/
template <class T>
void ami::contours_collection<T>::resize(const int n)
{
  #ifdef CONTOURS_DEBUG
    if(n<0){
      printf("contours_collection class: resize error\n");
      printf("resize dimension = %d\n",n);
      int j; scanf("%d",&j);
    }
  #endif
  v.resize(n);
}

/**
  * \fn template <class T> int ami::contours_collection<T>::write(char name[300])
  * \brief function to write in a file the contour collection
  * \param [in] name Contours collection filename.
  * \author Pedro Henríquez
*/
template <class T>
int ami::contours_collection<T>::write(char name[300])
{
   FILE *file_out;
   file_out=fopen(name,"w");
   if(file_out)
   {
    fprintf(file_out,"%d\n",this->size()); //NUMBER OF CONTOURS
    for(int i=0;i<this->size();i++)
    {
        //CLASS, SUBCLASS AND THE NUMBER OF POINTS OF THE CONTOUR
        fprintf(file_out,"%d\n%s\n%s\n%d\n",(*this)[i].get_id(),(*this)[i].region_type.c_str(),(*this)[i].region_subtype.c_str(),(*this)[i].size());
        for(int j=0;j<(*this)[i].size();j++)
        {
            fprintf(file_out,"%lf %lf\n",(double) (*this)[i][j].x,(double) (*this)[i][j].y);
        }
    }
    fclose(file_out);
    return 1;
   }
   else
        return -1; //CAN'T OPEN THE FILE
}

/**
  * \fn template <class T> int ami::contours_collection<T>::read(char name[300])
  * \brief function to read from a file a contour collection
  * \param [in] name Contours collection filename.
  * \author Pedro Henríquez
*/
template <class T>
int ami::contours_collection<T>::read(char name[300])
{
   FILE *file_in;
   file_in=fopen(name,"r");
   if(file_in)
   {
    int collection_size,contour_size;
    fscanf(file_in,"%d\n",&collection_size); //NUMBER OF CONTOURS
    for(int i=0;i<collection_size;i++)
    {
        //CLASS, SUBCLASS AND CONTOUR NUMBER OF POINTS
        contours<T> contorno;
        char type[200],subtype[200];
        int id0;
        fscanf(file_in,"%d\n",&id0);
        fgets(type,200,file_in);
        fgets(subtype,200,file_in);
        fscanf(file_in,"%d\n",&contour_size);
        contorno.region_type = type;
        contorno.region_subtype = subtype;
        contorno.set_id(id0);
        contorno.region_type= contorno.region_type.substr(0,contorno.region_type.length()-1);
        contorno.region_subtype= contorno.region_subtype.substr(0,contorno.region_subtype.length()-1);
        for(int j=0;j<contour_size;j++)
        {
            point2d<double> punto;
            fscanf(file_in,"%lf %lf\n",&punto.x,&punto.y);

            contorno.push_back(point2d<T>((T) punto.x,(T) punto.y ));
        }
        if(contour_size>0) this->push_back(contorno);
    }
    fclose(file_in);
    return 1;
   }
   else
   {
        return -1; //CAN'T OPEN FILE
   }
}

/**
 * \fn template <class T> void ami::contours<T>::print()
 * \brief method to print the contour coordinate points
 * \author Luis Alvarez
*/
template <class T>
void ami::contours<T>::print()
{
  for(unsigned int k=0,k_end=v.size();k!=k_end;++k) v[k].print();

  cout << "id : " << id << endl;
  cout << "region_type : " << region_type << endl;
  cout << "region_subtype : " << region_subtype << endl;
}

/**
 * \fn template <class T> int ami::contours_collection<T>::find_nearest_contour(point2d<T> point)
 * \brief Find the nearest contour to the point.
 * \param [in] point Point coordinates.
 * \author Pedro Henríquez
*/
template <class T>
int ami::contours_collection<T>::find_nearest_contour(point2d<T> point)
{
  //FIND THE NEAREST CONTOUR TO THE POINT
  float dist_min,dist;
  int min=0;
  dist_min=99999;
  for(int i=0;(int)i<v.size();i++)
  {
    for(int j=0;j<v[i].size();j++)
    {
      dist= (point - v[i][j]).norm2(); //DISTANCE
      if(dist<dist_min)
      {
        dist_min=dist;
        min=i;
      }
    }
  }
  return min;
}

/**
 * \fn template <class T> int ami::contours_collection<T>::find_id(int fid)
 * \brief Find an id in the contours collection
 * \param [in] fid Identificator to find.
 * \author Pedro Henríquez
*/
template <class T>
int ami::contours_collection<T>::find_id(int fid)
{
  for(int i=0;i<v.size();i++)
  {
    if(v[i].get_id()==fid) return i;
  }
  return -1;
}

#endif
