/*
    Copyright (c) 2011, 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/>.
*/


#include <math.h>
#include "snakes.h"
#include <iostream>
#include <sstream>
#include <vector>
#include <utility>
#include <string>
//#define __AMI_DEBUG__
#include "image.h"

#include "level_sets.h"
#include "point2d.h"
#include "snakes.h"
#include "utilities.h"

#include <stdio.h>
#include <stdlib.h>

using namespace std;

int str_len(char *str)
{
  int count = 0;
  while (*str++) count++;
  return count;
}

void print_warnings(vector< pair<int,int> > &vec)
{
    vector<string> messages;
    messages.push_back(" Invalid parameter.");
    messages.push_back(" Parameter redefinition.");
    messages.push_back(" Unused parameter token.");
    messages.push_back(" Unknown parameter token.");
    messages.push_back(" Undefined parameter, get default value.");
    messages.push_back(" Invalid snakes type (-T), must be 0, 1 or 2.");

    string parameters="IOCSTNBRPEF";

    cout << "There are " << vec.size() << " warnings." << endl;
    for(int i=0;i<(int)vec.size();i++)
    {
        if(vec[i].first-1==4)
          cout << " Parameter -" << parameters[vec[i].second] << messages[vec[i].first-1] << endl;
        else
            cout << " Argument " << vec[i].second << messages[vec[i].first-1] << endl;
    }
    return;
}

void print_errors(vector< pair<int,int> > &vec)
{
    vector<string> messages;
    messages.push_back(" Undefined input file (-I).");
    messages.push_back(" Undefined output file (-O).");
    messages.push_back(" Undefined contours file (-C).");
    messages.push_back(" Undefined output contours file (-F).");
    messages.push_back(" Input (-I) and output (-O) images must be different.");
    messages.push_back(" Input (-C) and output (-F) contours files must be different.\n");


    cout << "There are " << vec.size() << " errors." << endl;
    for(int i=0;i<(int)vec.size();i++)
    {
        cout << messages[vec[i].second] << endl;
    }
    return;
}

ami::contours<int> read_contours(const string &name)
{
  ami::contours<int> data;

  // OPEN FILE
  FILE *file_in;
  file_in = fopen(name.c_str(), "r");

  // VERIFY INFORMATION
  if (file_in == NULL)
  {
    printf("Can't open contours file\n");
    exit(-1);
  }

  // READ COORDENATES OF CONTOUR
  char cadena[51];
  fgets(cadena, 50, file_in);
  float x, y;
  if (2==sscanf(cadena, "%f%f", &x, &y))
  {
    ami::point2d<int> p((int)x,(int)y);
    data.push_back(p);
  }
  else
  {
     fgets(cadena, 50, file_in);
     fgets(cadena, 50, file_in);
     fgets(cadena, 50, file_in);
     fgets(cadena, 50, file_in);
  }
  while (!feof(file_in))
  {
    int value = fscanf(file_in, "%f%f", &x, &y);
    if (value != 2) continue;
    ami::point2d<int> p((int)x,(int)y);
    data.push_back(p);
  }


  return data;
}


void write_contours(const string &name, ami::contours<int> data)
{
  // OPEN FILE
  FILE *file_out;
  file_out = fopen(name.c_str(), "w");

  // VERIFY INFORMATION
  if (file_out == NULL)
  {
    printf("Can't open contours file\n");
    exit(-1);
  }

  // WRITE COORDENATES OF CONTOUR
  fprintf(file_out, "%d\n", data.size());
  for(int i = 0; i < data.size(); i++)
  {
    fprintf(file_out, "%d %d\n", data[i].x, data[i].y);
  }
}



int main(int argc, char* argv[])
{
    //GET PARAMS
    // -I INPUT IMAGE
    // -O OUTPUT IMAGE
    // -C CONTOURS FILE
    // -S SIGMA
    // -T SNAKE TYPE
    // -N NUMBER OF ITERATIONS
    // -B SNAKE BALLON
    // -R SNAKE BALLON DIFFERENCE RADIUS
    // -P EDGE DETECTOR THRESHOLD
    // -E EDGE THRESHOLD BALLON
    // -F OUTPUT CONTOURS FILE

    //DECLARATIONS AND INITIALIZATIONS
    int border_size=40;
    float sigma=3.; /*8*/
    int snake_type=0;
    int Niter=100;
    int snake_ballon=0; //1
    float snake_ballon_difference_radius=1000; // 48.;
    float edge_detector_treshold=0.5 ; /*0.8*/
    float edge_threshold_ballon=0.5;
    vector< pair<int,int> > warnings,errors;
    vector<bool> params_defined(11,false);
    string input_image_name="",output_image_name="",contours_file="",output_contours_file="";
    string numbers="0123456789";


    if(argc==1) //SHOW HELP IF THE USER HASN'T TYPED PARAMS
    {
        cout << "Ami Morphological Snakes parameters Help" << endl;
        cout << " -I INPUT IMAGE: Filename" << endl;
        cout << " -O OUTPUT IMAGE: Filename" << endl;
        cout << " -C CONTOURS FILE: Filename" << endl;
        cout << " -F OUTPUT CONTOURS FILE: Filename" << endl;
        cout << " -S SIGMA: float" << endl;
        cout << " -T SNAKE TYPE: int" << endl;
        cout << " -N NUMBER OF ITERATIONS: int" << endl;
        cout << " -B SNAKE BALLON: int" << endl;
        cout << " -R SNAKE BALLON DIFFERENCE RADIUS: int" << endl;
        cout << " -P EDGE DETECTOR THRESHOLD: float" << endl;
        cout << " -E EDGE THRESHOLD BALLON: float" << endl;
        return 0;
    }

    int i=1;
    while (i<argc)
    {
      while(argv[i][0]!='-') //SEARCH A PARAM TOKEN
      {
        pair<int,int> warning(1,i);
        warnings.push_back(warning);
        i++;
        if (i>=argc)break;
      }
      if (i>=argc)break;

      //argv[i][0]=='-'
      if(str_len(argv[i])>2 || str_len(argv[i])<2 )
      {
        pair<int,int> warning(4,i);
        warnings.push_back(warning);
        i++;
      }
      else
      {
          if((i+1)>=argc && argv[i][1]!='H' && argv[i][1]!='h')
          {
            pair<int,int> warning(3,i);
            warnings.push_back(warning);
            i++;
          }
          else
          {
              switch(argv[i][1])
              {
                case 'i':
                case 'I':
                    if(argv[i+1][0]=='-' && numbers.find(argv[i+1][1])==(size_t)-1)
                    {
                        pair<int,int> warning(3,i);
                        warnings.push_back(warning);
                    }
                    else
                    {
                      if(params_defined[0])
                      {
                          pair<int,int> warning(2,i);
                          warnings.push_back(warning);
                      }
                      params_defined[0]=true;
                      i++;
                      if (i>=argc)break;
                      input_image_name=argv[i];

                    }
                break;

                case 'o':
                case 'O':
                    if(argv[i+1][0]=='-' && numbers.find(argv[i+1][1])==(size_t)-1)
                    {
                        pair<int,int> warning(3,i);
                        warnings.push_back(warning);
                    }
                    else
                    {
                      if(params_defined[1])
                      {
                          pair<int,int> warning(2,i);
                          warnings.push_back(warning);
                      }
                      params_defined[1]=true;
                      i++;
                      if (i>=argc)break;
                      output_image_name=argv[i];
                    }
                break;

                case 'c':
                case 'C':
                    if(argv[i+1][0]=='-' && numbers.find(argv[i+1][1])==(size_t)-1)
                    {
                        pair<int,int> warning(3,i);
                        warnings.push_back(warning);
                    }
                    else
                    {
                      if(params_defined[2])
                      {
                          pair<int,int> warning(2,i);
                          warnings.push_back(warning);
                      }
                      params_defined[2]=true;
                      i++;
                      if (i>=argc)break;
                      contours_file=argv[i];
                    }
                break;

                case 's':
                case 'S':
                    if(argv[i+1][0]=='-' && numbers.find(argv[i+1][1])==(size_t)-1)
                    {
                        pair<int,int> warning(3,i);
                        warnings.push_back(warning);
                    }
                    else
                    {
                      if(params_defined[3])
                      {
                          pair<int,int> warning(2,i);
                          warnings.push_back(warning);
                      }
                      params_defined[3]=true;
                      i++;
                      if (i>=argc)break;
                      std::istringstream iss(argv[i]);
                      iss >> sigma;
                    }
                break;

                case 't':
                case 'T':
                    if(argv[i+1][0]=='-' && numbers.find(argv[i+1][1])==(size_t)-1)
                    {
                        pair<int,int> warning(3,i);
                        warnings.push_back(warning);
                    }
                    else
                    {
                      if(params_defined[4])
                      {
                          pair<int,int> warning(2,i);
                          warnings.push_back(warning);
                      }
                      params_defined[4]=true;
                      i++;
                      if (i>=argc)break;
                      std::istringstream iss(argv[i]);
                      int snake_type_temp;
                      iss >> snake_type_temp;
                      if ((snake_type_temp < 0)||(snake_type_temp > 2))
                      {
                          pair<int,int> warning(6,i);
                          warnings.push_back(warning);
                      }
                      else
                      {
                          snake_type = snake_type_temp;
                      }
                    }
                break;

                case 'n':
                case 'N':
                    if(argv[i+1][0]=='-' && numbers.find(argv[i+1][1])==(size_t)-1)
                    {
                        pair<int,int> warning(3,i);
                        warnings.push_back(warning);
                    }
                    else
                    {
                      if(params_defined[5])
                      {
                          pair<int,int> warning(2,i);
                          warnings.push_back(warning);
                      }
                      params_defined[5]=true;
                      i++;
                      if (i>=argc)break;
                      std::istringstream iss(argv[i]);
                      iss >> Niter;
                    }
                break;

                case 'b':
                case 'B':
                    if(argv[i+1][0]=='-' && numbers.find(argv[i+1][1])==(size_t)-1)
                    {
                        pair<int,int> warning(3,i);
                        warnings.push_back(warning);
                    }
                    else
                    {
                      if(params_defined[6])
                      {
                          pair<int,int> warning(2,i);
                          warnings.push_back(warning);
                      }
                      params_defined[6]=true;
                      i++;
                      if (i>=argc)break;
                      std::istringstream iss(argv[i]);
                      iss >> snake_ballon;
                    }
                break;

                case 'r':
                case 'R':
                    if(argv[i+1][0]=='-' && numbers.find(argv[i+1][1])==(size_t)-1)
                    {
                        pair<int,int> warning(3,i);
                        warnings.push_back(warning);
                    }
                    else
                    {
                      if(params_defined[7])
                      {
                          pair<int,int> warning(2,i);
                          warnings.push_back(warning);
                      }
                      params_defined[7]=true;
                      i++;
                      if (i>=argc)break;
                      std::istringstream iss(argv[i]);
                      iss >> snake_ballon_difference_radius;
                    }
                break;

                case 'p':
                case 'P':
                    if(argv[i+1][0]=='-' && numbers.find(argv[i+1][1])==(size_t)-1)
                    {
                        pair<int,int> warning(3,i);
                        warnings.push_back(warning);
                    }
                    else
                    {
                      if(params_defined[8])
                      {
                          pair<int,int> warning(2,i);
                          warnings.push_back(warning);
                      }
                      params_defined[8]=true;
                      i++;
                      if (i>=argc)break;
                      std::istringstream iss(argv[i]);
                      iss >> edge_detector_treshold;
                    }
                break;

                case 'e':
                case 'E':
                    if(argv[i+1][0]=='-' && numbers.find(argv[i+1][1])==(size_t)-1)
                    {
                        pair<int,int> warning(3,i);
                        warnings.push_back(warning);
                    }
                    else
                    {
                      if(params_defined[9])
                      {
                          pair<int,int> warning(2,i);
                          warnings.push_back(warning);
                      }
                      params_defined[9]=true;
                      i++;
                      if (i>=argc)break;
                      std::istringstream iss(argv[i]);
                      iss >> edge_threshold_ballon;
                    }
                break;

                case 'f':
                case 'F':
                    if(argv[i+1][0]=='-' && numbers.find(argv[i+1][1])==(size_t)-1)
                    {
                        pair<int,int> warning(3,i);
                        warnings.push_back(warning);
                    }
                    else
                    {
                      if(params_defined[10])
                      {
                          pair<int,int> warning(2,i);
                          warnings.push_back(warning);
                      }
                      params_defined[10]=true;
                      i++;
                      if (i>=argc)break;
                      output_contours_file=argv[i];
                    }
                break;

                case 'h':
                case 'H':
                    cout << "Ami Morphological Snakes parameters Help" << endl;
                    cout << " -I INPUT IMAGE: Filename" << endl;
                    cout << " -O OUTPUT IMAGE: Filename" << endl;
                    cout << " -C CONTOURS FILE: Filename" << endl;
                    cout << " -F OUTPUT CONTOURS FILE: Filename" << endl;
                    cout << " -S SIGMA: float" << endl;
                    cout << " -T SNAKE TYPE: int" << endl;
                    cout << " -N NUMBER OF ITERATIONS: int" << endl;
                    cout << " -B SNAKE BALLON: int" << endl;
                    cout << " -R SNAKE BALLON DIFFERENCE RADIUS: int" << endl;
                    cout << " -P EDGE DETECTOR THRESHOLD: float" << endl;
                    cout << " -E EDGE THRESHOLD BALLON: float" << endl;
                    return 0;
                break;

                default:
                    pair<int,int> warning(4,i);
                    warnings.push_back(warning);
                break;
              }
              i++;
          }
      }
    }

    //CHECK PARAMS DEFINED
    for(int j=0; j<3; j++) //CHECK FILENAMES
    {
      if(!params_defined[j])
      {
          pair<int,int> error(1,j);
          errors.push_back(error);
      }
    }
    if(!params_defined[10])//CHECK OUTPUT CONTOUR FILENAME
    {
        pair<int,int> error(1,3);
        errors.push_back(error);
    }

    if(input_image_name==output_image_name && input_image_name!="")
    {
        pair<int,int> error(1,4);
        errors.push_back(error);
    }
    if(contours_file==output_contours_file && contours_file!="")
    {
        pair<int,int> error(1,5);
        errors.push_back(error);
    }

    //CHECK OTHER PARAMETERS
    for(int j=3;j<(int)params_defined.size()-1;j++)
    {
      if(!params_defined[j])
      {
          pair<int,int> warning(5,j);
          warnings.push_back(warning);
      }
    }

    cout << "sigma " << sigma << endl;
    cout << "snake_type " << snake_type << endl;
    cout << "Niter "<<Niter<< endl;
    cout << "snake_ballon " <<snake_ballon<< endl;
    cout << "snake_ballon_difference_radius "<<snake_ballon_difference_radius<< endl;
    cout << "edge_detector_treshold "<<edge_detector_treshold<< endl;
    cout << "edge threshold ballon "<<edge_threshold_ballon<<endl;
    if ((snake_ballon == 1) && (Niter > 40) )border_size = Niter;
    cout << "border size "<< border_size << endl;
    cout << "input_image "<<input_image_name<< endl;
    cout << "output_image "<<output_image_name<< endl;
    cout << "contours_file "<<contours_file<< endl;
    cout << "output_contours_file "<<output_contours_file<<endl;

    print_warnings(warnings);
    print_errors(errors);


    if(errors.size()>0) return -1; //ERRORS IN PARAMETERS

    if(Niter<0)Niter*=-1;

    //LOAD FILES
    ami::image<unsigned char>  input_image(input_image_name.c_str());
    int width=input_image.width();
    int height=input_image.height();

    printf("width %d height %d\n",width,height);
    if (width == 0 || height == 0 )
    {
      printf("Can't read input image.\n");
      return -2;
    }

    ami::image<unsigned char>  input_contour(width,height,1,255);

    char name_contours[300];
    snprintf(name_contours, 299, "%s", contours_file.c_str());
    ami::contours_collection<int> cc;

    ami::contours<int> temp_contours = read_contours(contours_file);
    if(temp_contours.size()<3)
    {
      printf("The contour must has 3 points at least\n");
      return -3;
    }
    cc.push_back(temp_contours);


    //CONVERT COORDINATES FROM OWN FORMAT
    for(int k=0;k<cc[0].size();k++)
    {
      cc[0][k].y=height-(fabs(cc[0][k].y));
    }

    //FILL THE LINES TO OBTAIN ALL THE CONTOUR POINTS
    ami::contours<int> temp_contour;
    ami::point2d<int> p0,p1;
    for(int pun=0;pun<cc[0].size()-1;pun++)
    {
        p0.x=cc[0][pun].x;
        p0.y=cc[0][pun].y;
        p1.x=cc[0][pun+1].x;
        p1.y=cc[0][pun+1].y;
        temp_contour.add_segment(p0,p1);
    }
    int end=cc[0].size()-1;
    p0.x=cc[0][end].x;
    p0.y=cc[0][end].y;
    p1.x=cc[0][0].x;
    p1.y=cc[0][0].y;
    temp_contour.add_segment(p0,p1);

    cc[0].set_v(temp_contour.get_points());

    ami::level_sets lev_set;
    lev_set.clean_contours(cc[0]);
    cc[0].actualize_max_min();


    //CALCULATE SNAKE BALLON REFERENCE VALUE
    ami::point2d<int> p2d_offset;
    ami::contours<int> contour_temp;
    contour_temp =  cc[0];

    ami::snakes snake_procedure;
    if(params_defined[9]) //USING EDGE THRESHOLD BALLON
    {
        printf("Running morphological_snakes_fast_marching with edge threshold ballon\n");
        snake_procedure.morphological_snakes_fast_marching(
            input_image,
            cc,
            snake_type,
            border_size,
            sigma,
            Niter,
            snake_ballon,
            edge_threshold_ballon,
            edge_detector_treshold
        );
    }
    else
    {
        unsigned char sbrv=snake_procedure.ballon_reference_value_estimation(contour_temp,input_image,snake_ballon);

        printf("Running morphological_snakes_fast_marching with snake ballon difference radius\n");
        snake_procedure.morphological_snakes_fast_marching(
            input_image,
            cc,
            snake_type,
            border_size,
            sigma,
            Niter,
            snake_ballon,
            snake_ballon_difference_radius,
            sbrv,
            edge_detector_treshold
        );
    }

    printf("Number of Points of the Initial Contour : %d\n",cc[1].size());
    printf("Number of Points of the Final Contour : %d\n",cc[cc.size()-1].size());
    printf("Illustrative result image is stored in %s\n",output_image_name.c_str());


    // WE SAVE THE CONTOUR IMAGE EVOLUTION
    ami::image<unsigned char> img_debug(width,height,3,255);
    int size=width*height;
    for(int k=0;k<size;k++)
    {
        img_debug[k]=img_debug[k+size]=img_debug[k+2*size]=input_image[k];
    }

    if(cc.size()>0)
    {
        //LAST EVOLUTION
        for(int l=0;l<cc[cc.size()-1].size();l++)
        {
            int m=(cc[cc.size()-1][l].y)*width+(cc[cc.size()-1][l].x);
            img_debug[m]=0;
            img_debug[m+size]=0;
            img_debug[m+2*size]=255;
        }

        img_debug.write(output_image_name.c_str());
        ami::contours_collection<int> c_out;
        c_out.push_back( cc[cc.size()-1]);

        lev_set.clean_contours( c_out[0] );

        write_contours(output_contours_file,c_out[0]);

        printf("Contours result file is stored in %s\n\n",output_contours_file.c_str());
        return 0;
    }
    else
    {
        printf("Empty contour returned\n");
        return -4;
    }
}

