#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <fcntl.h>
#include <assert.h>
#include <zlib.h>
#include <ctype.h>
#include "cement.h"
#include "pnmutils.h"
#include "file.h"
#include "jpeg-6b/jpeglib.h"

/* Functions need in the library and the executable */
static inline double createValue(unsigned char a,double weight);

static int verbose=0;
static double powLookupTable[256];
static double exponent = EXPONENT;

#ifndef OBJECT_COMPILE
/* Functions only needed in the executable */
void usage(char *string);
void parse_commandline(int argc, char ** argv,
                       char ** input_filename,
                       double *R,
                       double *G,
                       double *B,
                       char ** output_filename,
                       char ** lut_filename);
void mystatus_lut(char *action, char *filename, char *lut, double R,double G, double B, double complete);
#endif

void readlut(char *lutfilename);
int cementinit(char *input_filename,
               char *dest_filename,
               double R,
               double G,
               double B,
               char* command,
               void (*status)(char *action, char *filename, double R,double G, double B, double complete)
              )
{
  return cementinit_lut(input_filename, dest_filename, 
           (char*)NULL, R, G, B, command, status, NULL);
}

/* set one of the status functions to NULL when calling */
int cementinit_lut(char *input_filename,
               char *dest_filename,
               char *lut_filename,
               double R,
               double G,
               double B,
               char* command,
               void (*status)(char *action, char *filename, double R,double G, double B, double complete),
               void (*lutstatus)(char *action, char *filename, char*lut, double R,double G, double B, double complete)
              )
{
    struct image_params a_params={NULL,0,0,0,0};
    File * a, * dest;
    unsigned char a_pixel[ARRAY_SIZE];
    double out_pixel[ARRAY_SIZE];
    int i,j;
    int channels_per_pixel;

    /* Timing measurments */
    struct timeval timeValStart;
    struct timeval timeValEnd;
    struct timezone timeZone;
    int minutes;
    double seconds=0.0;
#ifdef GET_TIME_MEASUREMENTS
    struct timeval runTimeStart;
    struct timeval runTimeStop;
    double fileReadTime=0.0;
    double fileWriteTime=0.0;
    double computeTime=0.0;
#endif
    
    unsigned int numberOfPixels;
    char string[MAX_STRING_LENGTH];
    unsigned int image_ptr=0;
   
    a_params.filename=input_filename;

    if ((a=fileopen(a_params.filename, "r"))==NULL)
    {
        fprintf(stderr, "Unable to open %s.\n", a_params.filename);
        exit(EXIT_FAILURE);
    }

    /* maali may 9 2000 */
    if(lut_filename !=NULL) 
    {
        readlut(lut_filename);
    }

    if (dest_filename==NULL)
    {
        fprintf(stderr, "ERROR: There is no destination file.\n");
        exit(EXIT_FAILURE);
    }

    if ((dest=fileopen(dest_filename, "w"))==NULL)
    {
        fprintf(stderr, "Unable to open %s.\n", dest_filename);
        exit(EXIT_FAILURE);
    }


    if(isJpegFile(a)==0)
    {
        get_image_params(a, &a_params);

        if (a_params.type=='6')
            channels_per_pixel=3;
        else
	 if (a_params.type=='5')
            channels_per_pixel=1;
        else 
        {
            fprintf(stderr, "Input image must be type P5 or P6 and it is of type P%c\n",a_params.type);
            exit(EXIT_FAILURE);
        }


    }
    else
    {
        a_params.type='6';
        a_params.max_val=255;
        channels_per_pixel=3;
        
        if(read_JPEG_file (a_params.filename)==0)
        {
            printf("ERROR: File %s does not exist\n",a_params.filename);
            exit(0);
        }
        
        image_ptr=0;
        a_params.width=image_width;
        a_params.height=image_height;
    }

    if (a_params.type=='6')
      sprintf(string, "P%c\n", 'A');
    else if (a_params.type=='5')
      sprintf(string, "P%c\n", '9');

    filewrite(string,sizeof(unsigned char),strlen(string),dest);
//    fileclose(dest);
//    exit(0);

    sprintf(string, "# %s %s %f %f %f -o %s\n",command,input_filename,R,G,
            B,dest_filename);
    filewrite(string,sizeof(unsigned char),strlen(string),dest);

    sprintf(string, "%d %d\n%d\n%d\n%f\n",a_params.width,
            a_params.height, a_params.max_val,1,(float)exponent);
    filewrite(string,sizeof(unsigned char),strlen(string),dest);


#ifdef SILENT_RUN
//    printf("Init %s (%s) %d %d %d\r",a_params.filename, lut_filename, (int)R,(int)G,(int)B);
#else

    printf("Initilizing Lightspace from file: \"%s\",",a_params.filename);
    if (lut_filename!=NULL) printf("    Using LookUp Table from file: \"%s\",",lut_filename);
    printf(" P%c, %dx%d, Channels_per_pixel: %d, ",a_params.type,a_params.width,a_params.height,channels_per_pixel);

    printf("R=%7.3f ",R);
    printf("G=%7.3f ",G);
    printf("B=%7.3f\n",B);
#endif
    
    /* Start Timer */
    if(gettimeofday(&timeValStart,&timeZone)==-1)
    {
        fprintf(stderr,"Error getting the time\n");
        exit(0);
    }

    numberOfPixels=a_params.width*a_params.height;

    if(((float)numberOfPixels/NUMBER_PIXELS_PER_READ)!=(int)((float)numberOfPixels/NUMBER_PIXELS_PER_READ))
    {
        if(verbose)
            printf("\nERROR: The number of pixels %d is not evenly divisible by %d. ",numberOfPixels,NUMBER_PIXELS_PER_READ);

        numberOfPixels=(int)((float)numberOfPixels/NUMBER_PIXELS_PER_READ)*NUMBER_PIXELS_PER_READ;
        if(verbose)
            printf("We will go up to %d pixels.\n",numberOfPixels);
    }

    for(i=0;i<numberOfPixels;i+=NUMBER_PIXELS_PER_READ)
    {
        if(i%(3*NUMBER_PIXELS_PER_READ)==0)
	{
            if(status!=NULL)
                status("Init",a_params.filename,R,G,B,(double)100*i/numberOfPixels);
            if(lutstatus!=NULL)
                lutstatus("Init",a_params.filename,lut_filename,R,G,B,(double)100*i/numberOfPixels);
	}

        
#ifdef GET_TIME_MEASUREMENTS
        gettimeofday(&runTimeStart,&timeZone);
#endif

        if(isJpegFile(a)==0)
            fileread(a_pixel, sizeof(unsigned char), ARRAY_SIZE, a);
        else
        {
            memcpy(a_pixel,&image_buffer[image_ptr],ARRAY_SIZE);
            image_ptr+=ARRAY_SIZE;
        }

#ifdef GET_TIME_MEASUREMENTS
        gettimeofday(&runTimeStop,&timeZone);

        seconds=(double)runTimeStop.tv_sec+(double)runTimeStop.tv_usec/1000000;
        seconds-=(double)runTimeStart.tv_sec+(double)runTimeStart.tv_usec/1000000;
        fileReadTime+=seconds;

        gettimeofday(&runTimeStart,&timeZone);
#endif
        
        for(j=0;j<ARRAY_SIZE;j+=3)
        {
            out_pixel[j]=createValue(a_pixel[j],R);
            out_pixel[j+1]=createValue(a_pixel[j+1],G);
            out_pixel[j+2]=createValue(a_pixel[j+2],B);
        }

#ifdef GET_TIME_MEASUREMENTS
        gettimeofday(&runTimeStop,&timeZone);

        seconds=(double)runTimeStop.tv_sec+(double)runTimeStop.tv_usec/1000000;
        seconds-=(double)runTimeStart.tv_sec+(double)runTimeStart.tv_usec/1000000;

        computeTime+=seconds;

        gettimeofday(&runTimeStart,&timeZone);
#endif
        
        filewrite(out_pixel, sizeof(double), ARRAY_SIZE, dest);

#ifdef GET_TIME_MEASUREMENTS
        gettimeofday(&runTimeStop,&timeZone);

        seconds=(double)runTimeStop.tv_sec+(double)runTimeStop.tv_usec/1000000;
        seconds-=(double)runTimeStart.tv_sec+(double)runTimeStart.tv_usec/1000000;
        fileWriteTime+=seconds;
#endif
    }

    if(numberOfPixels!=(a_params.width*a_params.height))
    {
        if(verbose)
            printf("Cleanup on pixels %d to %d\n",numberOfPixels,a_params.width*a_params.height);

        for(i=numberOfPixels;i<a_params.width*a_params.height;i++)
        {
            if(isJpegFile(a)==0)
                fileread(a_pixel, sizeof(unsigned char),CHANNELS_PER_PIXEL, a);
            else
            {
                memcpy(a_pixel,&image_buffer[image_ptr],CHANNELS_PER_PIXEL);
                image_ptr+=CHANNELS_PER_PIXEL;
            }

            out_pixel[0]=createValue(a_pixel[0],R);
            out_pixel[1]=createValue(a_pixel[1],G);
            out_pixel[2]=createValue(a_pixel[2],B);
            filewrite(out_pixel, sizeof(double), CHANNELS_PER_PIXEL, dest);
	}
    }

    /* End timer */
    if(gettimeofday(&timeValEnd,&timeZone)==-1)
    {
        printf("Error getting the time\n");
        exit(0);
    }

    while (!fileeof(a))
    {
      if(isJpegFile(a)==0)
	fileread(a_pixel, sizeof(char),CHANNELS_PER_PIXEL, a);
      else
        {
	  memcpy(a_pixel,&image_buffer[image_ptr],ARRAY_SIZE);
	  image_ptr+=ARRAY_SIZE;
        }
      
      if(fileeof(a))
	break;

      out_pixel[0]=createValue(a_pixel[0],R);
      out_pixel[1]=createValue(a_pixel[1],G);
      out_pixel[2]=createValue(a_pixel[2],B);
      printf("EP A: %d %d %d\t",a_pixel[0],a_pixel[1],a_pixel[2]);
      printf("OUT: %f %f %f\n",out_pixel[0],out_pixel[1],out_pixel[2]);
      filewrite(out_pixel, sizeof(double), CHANNELS_PER_PIXEL, dest);
    }
    
    fileclose(a);
    fileclose(dest);

    seconds=(double)timeValEnd.tv_sec+(double)timeValEnd.tv_usec/1000000;
    seconds-=(double)timeValStart.tv_sec+(double)timeValStart.tv_usec/1000000;

#ifdef GET_TIME_MEASUREMENTS
    printf("%f s doing File Read %f%%\n",fileReadTime,100*fileReadTime/seconds);
    printf("%f s doing File Writes %f%%\n",fileWriteTime,100*fileWriteTime/seconds);
    printf("%f s doing Computation %f%%\n",computeTime,100*computeTime/seconds);
    printf("%f s doing Timing and looping\n",seconds-fileReadTime-fileWriteTime-computeTime);
#endif
    
    if(seconds>60)
    {
        minutes=seconds/60;
        seconds=seconds-minutes*60;
    }
    else
        minutes=0;

    if(verbose || GET_RUN_TIME)
    {
#ifndef SILENT_RUN
        if(minutes==1)
            printf("Run Time is about %d minute %f seconds.\n",minutes,seconds);
        else
            printf("Run Time is about %d minutes %f seconds.\n",minutes,seconds);
#else
            if(status!=NULL)
                status("Init",a_params.filename,R,G,B,(double)100);
            if(lutstatus!=NULL)
                lutstatus("Init",a_params.filename,lut_filename,R,G,B,(double)100);
//        printf("Init %s (%s) %d %d %d %4.2f(s)\n",a_params.filename,lut_filename,(int)R,(int)G,(int)B,minutes*60+seconds);
#endif
    }

    if(isJpegFile(a)==1)
        free(image_buffer);

    return(EXIT_SUCCESS);
}


static inline double createValue(unsigned char a,double weight)
{
    return(weight*powLookupTable[a]);
}

#ifndef OBJECT_COMPILE
int main(int argc, char ** argv)
{
    char * dest_filename=NULL;
    char * input_filename=NULL;
    char * lut_filename=NULL;
    double R,G,B;

    parse_commandline(argc, argv, &input_filename, &R,&G,&B,&dest_filename,&lut_filename);
    return(cementinit_lut(input_filename,dest_filename,lut_filename,R,G,B,argv[0],NULL,mystatus_lut));
}

void mystatus_lut(char *action, char *filename, char*lut_filename, double R,double G, double B, double complete)
{
    if (lut_filename!=NULL) printf("%s %s (%s) %d %d %d %d%%\r",action,filename,lut_filename,(int)R,(int)G,(int)B,(int)complete);
    else printf("%s %s %d %d %d %d%%\r",action,filename,(int)R,(int)G,(int)B,(int)complete);
    fflush(stdout);
    if((int)complete==100)
	printf("\n");
}

void usage(char *string)
{
    fprintf(stderr, "Use: %s [Imagespace file] <R> <G> <B> -o [Lightspace file] \n"
		    "\t [-lut [Imaging Device Response file]]\n\n",string);
    fprintf(stderr, " [Imagespace file] is a ppm file that will start a lightspace.\n");
    fprintf(stderr, " <R>=Float for Red weight.\n");
    fprintf(stderr, " <G>=Float for Green weight.\n");
    fprintf(stderr, " <B>=Float for Blue weight.\n");
    fprintf(stderr, " [LightSpaceFile] is a Portable Lightspace Map (plm) that will be created.\n");
    fprintf(stderr, " [LookUp Table file] (optional, lut) specifies a map from \n");
    fprintf(stderr, " imagespace to lightspace; if none specified, a default map is used.\n");
    exit(EXIT_SUCCESS);
}

void parse_commandline(int argc, char ** argv,
                       char ** input_filename,
                       double *R,
                       double *G,
                       double *B,
                       char ** output_filename,
                       char ** lut_filename) 
{
  int diff, exp;
  char c;

    if (argc<7)
    {
        fprintf(stderr,"error: number of input arguments was only %d\n",argc-1);
        usage(argv[0]);
    }

    *input_filename=(char*)strdup(argv[1]);
    sscanf(argv[2],"%lf",R); /* sscanf does not recognize %g, must be %lf */
    sscanf(argv[3],"%lf",G); /* sscanf does not recognize %g, must be %lf */
    sscanf(argv[4],"%lf",B); /* sscanf does not recognize %g, must be %lf */

    if(argv[5][0]!='-' || argv[5][1] !='o')
        usage(argv[0]);

    *output_filename=(char *)strdup(argv[6]);

    /* maali may 9 2000 */
    if(argc>7)
    {      
      if((argv[7][0]=='-')&&(argv[7][1]=='l')&&(argv[7][2]=='u')&&(argv[7][3]=='t'))
	{
	  c = argv[8][0];
	  diff = c - '0';
	  if ( diff >= 0 || diff <= 9)
	    {
	      *lut_filename = (char*) malloc(50 * sizeof(char));
	      exponent = atof(argv[8]);
	      exp = rint(exponent);
	      exponent /= 10.0;
	      sprintf(*lut_filename, "powLookup%d.txt", exp);
	    }
	  else 
            *lut_filename=(char *)strdup(argv[8]);
	} 
    }
    else 
      {
	if (argc == 7)	      
	  {
	    *lut_filename = (char*) malloc(50 * sizeof(char));
	    exp = rint(exponent*10);
	    sprintf(*lut_filename, "powLookup%d.txt", exp);
	  }
	else
	  usage(argv[0]);
      }
}
#endif


/* ===================================================================== */
/* ===================================================================== */
/* ===================================================================== */
/* Readlut - reads a given lookup table. */

void readlut(char *lutfilename)
{
  /* some variables used in reading a text file lut */
  FILE *lutfile;
  int lutsize=256;
  float p;
  int i;

  if(lutfilename !=NULL) 
    {
      if ((lutfile = fopen(lutfilename, "r")) == NULL)
	{
	  fprintf(stderr, "Unable to open %s.\n", lutfilename);
	  fclose(lutfile);
	  exit(EXIT_FAILURE);
	  } 
      else 
	  {
	    fscanf(lutfile, "%f", &p);
	    for (i=0; i<lutsize; i++)
	      fscanf(lutfile, "%lf", &(powLookupTable[i]));
	    
	    if (i != lutsize) {
	      fprintf(stderr, "Incomplete read of LUT. Numread: %d\n", i);
	      fclose(lutfile);
	      exit(1);
	    }
	  }
	fclose(lutfile);
      }
}
