#define cimg_display 1
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <math.h>
#include "CImg.h"
#include "rgbe.h"
#include "rgb2lab.h"

using namespace cimg_library;
using namespace std;

unsigned char w( unsigned char z ) {
  if (z < 128){
    return z;
  }else {
    return 255-z;
  }
}

inline double log_function(const double x){

  static const double inv_log_base = 1.0 / log(10.0);

  return log(x) * inv_log_base;
}


inline double exp_function(const double x){

  return pow(10.0,x);
}



int main() {
  //[TODO ]IO reading a file list
  // ugly placeholder

  CImg<unsigned char> * imageList[12];

  for (int i = 0; i<12 ; i++){
    string filename;
    stringstream index;
    if (i<9){
      index << i+1;
      filename = "../pics/candy/Img0" + index.str() + ".jpg";
    }else {
      index << i+1;
      filename = "../pics/candy/Img" + index.str() + ".jpg";
    }
    imageList[i] = new CImg<unsigned char>(filename.c_str());
  }

  //hardcoded shutter speed
  double shutter_speed[12]={13.0, 6.0, 3.0,  1.6, 
                            0.77, 0.5, 0.25, 0.125, 
                            0.0625, 0.03125, 0.015625,
                            0.0078125
                             };


  const int P = 12;
  const int n = 256; 

  const int width  =  imageList[0]->width();
  const int height =  imageList[0]->height();

  // shutter speeds
  for( int i = 0 ; i < P ; i++ ){
    shutter_speed[i] = log( shutter_speed[i] );
    //speed /= 2;
  }


  // reading curves
  // --------------
  cout << "Reading curves..." << flush;

  FILE* r_file = fopen("R_curve.txt","r"); 
  FILE* g_file = fopen("G_curve.txt","r"); 
  FILE* b_file = fopen("B_curve.txt","r"); 

  double g_R[n],g_G[n],g_B[n]; // n = 256

  for(int i=0; i<n; i++)
  {
    fscanf(r_file,"%lf",&g_R[i]);
    fscanf(g_file,"%lf",&g_G[i]);
    fscanf(b_file,"%lf",&g_B[i]);
  }
  cout << "Done" <<endl;

//  float outArray[width][height][3]; 
  vector< vector< vector<float> > > outArray;

  outArray.resize(width);
  for( int i = 0 ; i < width ; i++ )
  {
    outArray[i].resize(height);
    for( int j = 0 ; j < height ; j++ )
    {
      outArray[i][j].resize(3);
    }
  }


  // Radiance map
  for( int i = 0 ; i < width ; i++ )
  {
    for( int j = 0 ; j < height ; j++ )
    {    
      int weight_R_sum = 0;
      int weight_G_sum = 0;
      int weight_B_sum = 0;
      double rad_map_R = 0.0;
      double rad_map_G = 0.0;
      double rad_map_B = 0.0;
      for(int k = 0 ; k < P ; k++)
      {
        unsigned char R_intensity = (*imageList[k])( i , j , 0);
        unsigned char G_intensity = (*imageList[k])( i , j , 1);
        unsigned char B_intensity = (*imageList[k])( i , j , 2);

        unsigned char wij;
        //R
        wij = w(R_intensity);
        weight_R_sum += wij;
        rad_map_R += wij * ( g_R[R_intensity] - shutter_speed[k] );
        //G 
        wij = w(G_intensity);
        weight_G_sum += wij;
        rad_map_G += wij * ( g_G[G_intensity] - shutter_speed[k] );
        //B
        wij = w(B_intensity);
        weight_B_sum += wij;
        rad_map_B += wij * ( g_B[B_intensity] - shutter_speed[k] );
//      printf( "Ri: %d Gi: %d Bi: %d  Rw: %d  Gw: %d  Bw: %d  R: %.0lf G: %.0lf B: %.0lf\n" , 
//        R_intensity , G_intensity , B_intensity , weight_R_sum , weight_G_sum , weight_B_sum , rad_map_R , rad_map_G , rad_map_B );
      }
      rad_map_R /= weight_R_sum;
      rad_map_G /= weight_G_sum;
      rad_map_B /= weight_B_sum;

      outArray[i][j][0]= exp( rad_map_R );
      outArray[i][j][1]= exp( rad_map_G );
      outArray[i][j][2]= exp( rad_map_B );

/*
      outImg(i, j, 0)= exp( rad_map_R );
      outImg(i, j, 0)= exp( rad_map_G );
      outImg(i, j, 0)= exp( rad_map_B );
*/
    }
  }
  //TODO: write rgbe file
  //

  cout << "writing out.hdr..." << flush;
  FILE* outfile = fopen("out.hdr","wb");
  RGBE_WriteHeader(outfile,width,height,NULL);
  unsigned char rgbe[4];
  for( int j = 0 ; j < height ; j++ )
  {
    for( int i = 0 ; i < width ; i++ )
    {    
      float2rgbe(rgbe,
      outArray[i][j][0],
      outArray[i][j][1],
      outArray[i][j][2] );
//      outImg(i, j, 0),
//      outImg(i, j, 1),
//      outImg(i, j, 2) );
      if (fwrite(rgbe, sizeof(rgbe), 1, outfile) < 1){
        printf("error in writing file");
        exit(1);
      }
    }
  }
  fclose(outfile);
  cout << "Done" <<endl;

  for (int i = 0; i<12 ; i++){
    delete imageList[i];
  }
  // tone mapping: FBF
  // ----------------
 /* 
  vector< vector< vector<float> > > bilateral;
  vector< vector< float > > detail;

//  lab.resize(width);
  bilateral.resize(width);
  detail.resize(width);
  for( int i = 0 ; i < width ; i++ )
  {
//    lab[i].resize(height);
    bilateral[i].resize(height);
    detail[i].resize(height);
    for( int j = 0 ; j < height ; j++ )
    {
//      lab[i][j].resize(3);
      bilateral[i][j].resize(3);
    }
  }

  float lab[width][height][3];

  float L, a, b;
  float LumiMax, LumiMin;

  for(int y=0;y<height;y++){
    for(int x=0; x<width;x++){
      RGB2Lab( outArray[x][y][0],
               outArray[x][y][1],
               outArray[x][y][2],
 	       &L, &a, &b);
      if( L > LumiMax)
        LumiMax = L;
      if( L < LumiMin)
        LumiMin = L;
      lab[x][y][0] = L;
      lab[x][y][1] = a;
      lab[x][y][2] = b;
    }
  }

  int helf_kernel = 5;
  float zigma = 2.0;
  float zigma2= 0.8;
  float sum_weight;
  float weight;
  float value;
  int yyy,xxx;
  for(int x=0; x<height; ++x){
    for(int y=0; y<width; ++y){
      sum_weight = 0.0;
      value = 0.0;
      for(int yy=-helf_kernel;yy<=helf_kernel;++yy){
        yyy = y + yy;
        if(yyy < 0)continue;
        if(yyy > (height-1))continue;
        for(int xx=-helf_kernel;xx<=helf_kernel;++xx){
          xxx = x + xx;
          if(xxx < 0)continue;
          if(xxx > (width-1)) continue;
          weight = gaussian(x,y,xxx,yyy,zigma);
          weight *= gaussian( lab[x][y][0] - lab[xxx][yyy][0],zigma2);
          sum_weight += weight;
          value += weight * lab[xxx][yyy][0];
        }
      }
      value /= sum_weight;
      bilateral[x][y][0] = value;
      bilateral[x][y][1] = lab[x][y][1];
      bilateral[x][y][2] = lab[x][y][2];
    }
  }
  for(int y=0;y<height;++y){
    for(int x=0;x<width;++x){
      detail[x][y] = lab[x][y][0] - bilateral[x][y][0];
    }
  }

  float MAX,MIN;
  float INTEVAL;
  float tmp, mu;
  RGB2Lab(255,255,255,&MAX,&tmp,&tmp);
  RGB2Lab(0,0,0,&MIN,&tmp,&tmp);
  INTEVAL = MAX - MIN;
  LumiMax=9999999.0;
  LumiMin=-9999999.0;
  mu = 0.0;
  for(int y=0;y<height;y++){
    for(int x=0; x<width;x++){
      if( log(bilateral[x][y][0]) > LumiMax)
        LumiMax = log(bilateral[x][y][0]);
        if( log(bilateral[x][y][0]) < LumiMin)
          LumiMin = log(bilateral[x][y][0]);
        mu += log(bilateral[x][y][0]);
    }
  }
  mu /= width*height;
  float V = 0.0;
  for(int y=0;y<height;y++){
    for(int x=0; x<width;x++){
      V += ( log(bilateral[x][y][0]) - mu )*( log(bilateral[x][y][0]) - mu );
    }
  }

  V /= width*height;
  V = sqrt(V);
  cout << "mu = " << mu << endl;
  cout << "V = " << V << endl;
  LumiMax = LumiMax>(mu + 2*V)?(mu + 2*V):LumiMax;
  LumiMin = LumiMin<(mu - 3*V)?(mu - 3*V):LumiMin;
  float LumiScal=LumiMax - LumiMin;
  float gamma2 = 0.9;
  for(int y=0;y<height;y++){
    for(int x=0; x<width;x++){
      if( log(bilateral[x][y][0])>LumiMax )  bilateral[x][y][0] = exp(LumiMax);
      if( log(bilateral[x][y][0])<LumiMin )  bilateral[x][y][0] = exp(LumiMin);
      bilateral[x][y][0] = INTEVAL * (  pow( ( ( log(bilateral[x][y][0]) - LumiMin) / LumiScal ),gamma2) ) + MIN;
      bilateral[x][y][0] += detail[x][y];
      if( bilateral[x][y][0] > MAX)bilateral[x][y][0] = MAX;
      if( bilateral[x][y][0] < MIN)bilateral[x][y][0] = MIN;
    }
  }
  
  CImg<unsigned char> outImg(width,height,1,3);

  float R,G,B;
  for(int y=0;y<height;y++){
    for(int x=0; x<width;x++){
      Lab2RGB( bilateral[x][y][0], bilateral[x][y][1], bilateral[x][y][2], &R, &G, &B);
      outImg(x,y,0) = (int)(R>255?255:R);
      outImg(x,y,1) = (int)(G>255?255:G);
      outImg(x,y,2) = (int)(B>255?255:B);
    }
  }

  outImg.save_jpeg("out_fbf.jpeg");
*/
  return 0;
}
