#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 "cement.h"
#include "pnmutils.h"
#include "file.h"
#include <jpeglib.h>

//#define JPEG_QUALITY 90 (now defined in cement.h)

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

static int verbose=0;
static double inverseLookup[256];

#ifndef OBJECT_COMPILE
/* Functions only needed in the executable */
void usage(char * string);
void parse_commandline(int argc, char ** argv,
                       char ** input_filename,
                       char ** output_filename,
                       char ** lut_filename);

void mystatus_lut(char *action, char *filename, char *lut_filename, double red, double green, double blue, double complete);
#endif

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

/* set one of the status functions to NULL when calling */
int plm2pnm_lut(char *input_filename,
            char *dest_filename,
            char *lut_filename,
            char *command,
            void (*status)(char *action, char *filename, double R,double G, double B, double complete),
            void (*status_lut)(char *action, char *filename, char *lut, double R,double G, double B, double complete)
           )
{
    struct image_params a_params={NULL,0,0,0,0,0.0};
//    char *dest_filename=NULL;
    File *a, *dest;
    double p=EXPONENT;  /* exponent for pow */
    double        a_pixel[ARRAY_SIZE];
    unsigned char out_pixel[ARRAY_SIZE];
    int i,j=0;
    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 pixels=0;
    unsigned int numberOfPixels;

    char string[MAX_STRING_LENGTH];
    int image_ptr;

    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);
    }

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

    get_image_type(a, &a_params);

    if (a_params.type=='A')
    {
        channels_per_pixel=3;
        sprintf(string,"P%c\n", '6');
        filewrite(string,sizeof(unsigned char),strlen(string),dest);
        get_image_params_preserve_comments(a,&a_params,dest);
    }
    else if (a_params.type=='9')
    {
        channels_per_pixel=1;
        sprintf(string,"P%c\n", '5');
        filewrite(string,sizeof(unsigned char),strlen(string),dest);
        get_image_params_preserve_comments(a,&a_params,dest);
    }
    else
    {
        fprintf(stderr, "Portable Lightspace map (plm) must be type 9 or A and it is type %c\n",a_params.type);
        exit(EXIT_FAILURE);
    }


    /* Use the exponent from the image file given, if there is one. */
    /* This determines the LUT file used. */
    /* Otherwise the default exponent and lut file are used. */
    /* If conflict between given LUT file and exponent, there will be a warning message. */
    if (a_params.exponent > 0) 
	p = (double) a_params.exponent;

    if (lut_filename == NULL) 
      {
	lut_filename = (char *) malloc (50*sizeof(char));
	if (lut_filename == NULL) {
	  printf("malloc failed in plm2pnm_lut\n");
	  exit(-1);
	}
	sprintf(lut_filename, "powLookup%2d.txt", (int)rint(p*10));
      }    


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


    /*    if ((a_params.exponent!=(float)EXPONENT))
    {
        fprintf(stderr, "Lightspace must have the same exponent.\n");
        exit(EXIT_FAILURE);
	}*/

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

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


    image_width=a_params.width;
    image_height=a_params.height;
    image_buffer=malloc(3*image_width*image_height*sizeof(unsigned char));
    if(image_buffer==NULL)
        printf("ERROR: Could not allocate buffer for image\n");
    image_ptr=0;

    
#ifdef SILENT_RUN
//    printf("Create %s \r",dest_filename);

#else
    printf("Generate ppm file: \"%s\", ",dest_filename);
    printf("P%c, %dx%d, Channels_per_pixel: %d ",a_params.type,a_params.width,a_params.height,channels_per_pixel);

    printf("from Lightspace file: \"%s\", ",a_params.filename);
    printf("P%c\n",a_params.type);
#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("Create", dest_filename,-1,-1,-1,(double)100*i/numberOfPixels);
            if(status_lut!=NULL)
                status_lut("Create", dest_filename,lut_filename, -1,-1,-1,(double)100*i/numberOfPixels);
        }

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

        fileread(a_pixel, sizeof(double), ARRAY_SIZE, a);

#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

/* it is ABSOLUTELY ESSENTIAL that anything less than zero go to zero
   and anything greater than 255 go to 255, otherwise unsightly contouring
   very subtle (e.g. sometimes only one or two
   pixels in the entire image sometimes) unpleasant artifacts appear
   from small roundoff errors that cause wraparound, or the like */
        for(j=0;j<ARRAY_SIZE;j+=3)
        {
            out_pixel[j  ]=binarySearch(a_pixel[j]);
            out_pixel[j+1]=binarySearch(a_pixel[j+1]);
            out_pixel[j+2]=binarySearch(a_pixel[j+2]);
        }

#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(unsigned char), ARRAY_SIZE, dest);

        memcpy(&image_buffer[image_ptr],out_pixel,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;
        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++)
        {
            fileread(a_pixel, sizeof(double),CHANNELS_PER_PIXEL, a);

            out_pixel[j]=binarySearch(a_pixel[j]);
            out_pixel[j+1]=binarySearch(a_pixel[j+1]);
            out_pixel[j+2]=binarySearch(a_pixel[j+2]);

            filewrite(out_pixel, sizeof(unsigned char), CHANNELS_PER_PIXEL, dest);

            memcpy(&image_buffer[image_ptr],out_pixel,CHANNELS_PER_PIXEL);
            image_ptr+=CHANNELS_PER_PIXEL;
            
            pixels++;
        }
    }

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

    pixels=i;

    /* deal with any remaining extra pixels??? */        
    while (!fileeof(a))
    {
      fileread(a_pixel, sizeof(double),CHANNELS_PER_PIXEL, a);
      if(fileeof(a))
	break;
      
      out_pixel[0] = binarySearch(a_pixel[0]);
      out_pixel[1] = binarySearch(a_pixel[1]);
      out_pixel[2] = binarySearch(a_pixel[2]);

      printf("ExtraPixels A: %f %f %f\t",a_pixel[0],a_pixel[1],a_pixel[2]);
      printf("OUT: %d %d %d\n",out_pixel[0],out_pixel[1],out_pixel[2]);
      filewrite(out_pixel, sizeof(unsigned char), CHANNELS_PER_PIXEL, dest);
      pixels++;
    }
    
    fileclose(a);
    
    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
//        printf("Create %s, %f(s)\n",dest_filename,minutes*60+seconds);
           if(status!=NULL)
                status("Create", dest_filename,-1,-1,-1,(double)100);
           if(status_lut!=NULL)
                status_lut("Create", dest_filename,lut_filename, -1,-1,-1,(double)100);
#endif
    }
    else
	printf("\n");

    if(isJpegFile(dest)==1)
    {
        fileclose(dest);
        write_JPEG_file (dest_filename,JPEG_QUALITY);
    }
    else
        fileclose(dest);

    free(image_buffer);
    return(EXIT_SUCCESS);
}

static inline double createValue(double a, double p_inv)
{
    return(pow(a,p_inv));
}


static unsigned char binarySearch(double value)
{
    int low=0;
    int high=0x100;
    int middle=0x7f;
    
    while(1)
    {
        if(high==middle || low==middle)
            return(middle);

        if(value>=inverseLookup[middle])
        {
            low=middle;
            middle=((high-low)>>1)+middle;
        }
        else
            if(value<inverseLookup[middle])
            {
                high=middle;
                middle=((high-low)>>1)+low;
            }
    }
    return(middle);
}

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

    parse_commandline(argc, argv, &input_filename, &dest_filename,&lut_filename);
    return(plm2pnm_lut(input_filename,dest_filename,lut_filename,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 [LightSpaceFile] -o [ImageSpaceFile] "
		  "[-lut [LookUpTableFile]]\n",string);
  fprintf(stderr, "Example: %s trowel_out.plm -o pork.jpg\n",string);
//  fprintf(stderr, " [LightSpaceFile] is plm (portable light space map) input file.\n");
//  fprintf(stderr, " [ImageSpaceFile] is ppm or jpg output (autodetected by filename you specify).\n");
//  fprintf(stderr, " [LookUpTableFile] (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,
                       char ** output_filename,
                       char ** lut_filename)
{
    if (argc<3)
        usage(argv[0]);

    *input_filename=(char *)strdup(argv[1]);

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

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

    /* maali may 9 2000 */
    if(argc>4)
    {
        if((strlen(argv[4])>=4)
		&&(argv[4][0]=='-')&&(argv[4][1]=='l')
		&&(argv[4][2]=='u')&&(argv[4][3]=='t'))
        {
            *lut_filename=(char *)strdup(argv[5]);
        } 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", &(inverseLookup[i]));
		   
	    if (i != lutsize) {
	      fprintf(stderr, "Incomplete read of LUT. Numread: %d\n", i);
	      fclose(lutfile);
	      exit(1);
	    }
	  }
	fclose(lutfile);
      }
}
