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

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

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


int plm2pnm(char *input_filename,
            char *dest_filename,
            char *command,
            void (*status)(char *action, char *filename, 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 p_inv=1.0/p;
    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
    {
        fprintf(stderr, "Portable Lightspace map (plm) must be type A and it is type %c\n",a_params.type);
        exit(EXIT_FAILURE);
    }

    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 ",'6',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);
        }

        
#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]=(unsigned char) createValue(a_pixel[0],p_inv);
        out_pixel[1]=(unsigned char) createValue(a_pixel[1],p_inv);
        out_pixel[2]=(unsigned char) createValue(a_pixel[2],p_inv);
        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);
#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 double *inverseLookup=powLookupTable;

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;

    parse_commandline(argc, argv, &input_filename, &dest_filename);
    return(plm2pnm(input_filename,dest_filename,argv[0],mystatus));
}

void mystatus(char *action, char *filename, double red, double green, double blue, double complete)
{
    printf("%s %s %d%%\r",action,filename,(int)complete);
    fflush(stdout);
    if((int)complete==100)
       printf("\n");
}

void usage(char * string)
{
  fprintf(stderr, "Use: %s [LightSpaceFile] -o [ImageSpaceFile]\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");
  exit(EXIT_SUCCESS);
}

void parse_commandline(int argc, char ** argv,
                       char ** input_filename,
                       char ** output_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]);
}
#endif

