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

int verbose=0;

inline double createValue(double a,double p_inv);
void usage(char * string);
void parse_commandline(int argc, char ** argv,
                       struct image_params * a_params,
                       char ** output_filename);

unsigned char binarySearch(double value);
void initilizeLookup(double p);

int main(int argc, char ** argv)
{
    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;

    
    /* Compression variables */
    int destgz=0;
    int agz=0;
//    gzFile file;
    char outmode[20];
    char string[MAX_STRING_LENGTH];
    
    strcpy(outmode, "wb6 ");

    initilizeLookup(p);
    p_inv=1.0/p;

    parse_commandline(argc, argv, &a_params, &dest_filename);


    if(isGZIPFile(a_params.filename)==0)
    {
        if ((a=fopen(a_params.filename, "r"))==NULL)
        {
            fprintf(stderr, "Unable to open %s.\n", a_params.filename);
            exit(EXIT_FAILURE);
        }
    }
    else
    {
        if ((agz=open(a_params.filename,O_RDONLY))<0)
        {
            fprintf(stderr, "Unable to open %s. %d\n", ".gz",destgz);
            exit(EXIT_FAILURE);
        }
        compressFileInputA=1;
        inFileA = gzdopen(agz, "rb");
        a=(FILE*)&inFileA;
    }
    
/*    if ((a=fopen(a_params.filename, "r"))==NULL)
    {
        fprintf(stderr, "Unable to open %s.\n", a_params.filename);
        exit(EXIT_FAILURE);
    }*/

//    if (dest_filename==NULL)
//        dest=stdout;
//    else
//    {

    if(isGZIPFile(dest_filename)==0)
    {

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

        if ((destgz=creat(dest_filename,00600))<0)
        {
            fprintf(stderr, "Unable to open %s. %d\n", "out.ppm.gz",destgz);
            exit(EXIT_FAILURE);
        }
        compressFileOutput=1;
        outFileDest = gzdopen(destgz, outmode);
        dest=(FILE*)&outFileDest;
    }
//    }

    
    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);
//        fprintf(dest, "P%c\n", '6');
//        if (gzwrite(file, string, strlen(string))!=strlen(string))
//            printf("GZIP Write Error\n");

        get_image_params_preserve_comments(a,&a_params,dest);
    }
    else
    {
        fprintf(stderr, "Portable Lightspace map (pldm) 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 %s %s \n",argv[0],argv[1],argv[2],argv[3]);
/*    fprintf(dest, "# %s %s %s %s \n",argv[0],argv[1],argv[2],argv[3]);*/
    filewrite(string,sizeof(unsigned char),strlen(string),dest);



/*    fprintf(dest, "%d %d\n%d\n", a_params.width,
            a_params.height, a_params.max_val);*/
    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);

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

    //    while (!feof(a))

    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)
        {
            printf("Create %s, %5.2f%%\r",dest_filename,(double)100*i/numberOfPixels);
            fflush(stdout);
        }

        
#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_pixeld[j  ]=createValue(a_pixel[j],p_inv);
//            out_pixeld[j+1]=createValue(a_pixel[j],p_inv);
//            out_pixeld[j+2]=createValue(a_pixel[j],p_inv);
//            printf("%f %f %f\n",out_pixeld[j],out_pixeld[j+1],out_pixeld[j+2]);

            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]);
//            printf("%d %d %d\n",out_pixel[j],out_pixel[j+1],out_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);

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

    }

    if(isGZIPFile(a_params.filename)==0)
        fclose(a);
    else
    {
        if (gzclose(inFileA) != Z_OK)
            printf("failed gzclose\n");
        close(agz);
    }

    if(isGZIPFile(dest_filename)==0)
        fclose(dest);
    else
    {
        if (gzclose(outFileDest) != Z_OK)
            printf("failed gzclose\n");
        close(destgz);
    }

    
//    printf("\nTime measurements\n");
    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);
#endif
    }
    else
	printf("\n");

    exit(EXIT_SUCCESS);
}

double powLookup[256];
double *inverseLookup=powLookup;

void initilizeLookup(double p)
{
    int i;

    for(i=0;i<256;i++)
    {
        powLookup[i]=pow((double)i,p);
//        printf("%d %f\n",i,powLookup[i]);
    }
}

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

void usage(char * string)
{
  fprintf(stderr, "Use: %s [LightSpaceFile] -o [ImageSpaceFile]\n",string);
  fprintf(stderr, "\t[LightSpaceFile]\t-A plm (portable light space map) file that will be created.\n");
  fprintf(stderr, "\t[ImageSpaceFile]\t-A ppm file that will form the start of a lightspace.\n");
  exit(EXIT_SUCCESS);
}

void parse_commandline(int argc, char ** argv,
                       struct image_params * a_params,
                       char ** output_filename)
{
    int i;

    if (argc<3)
        usage(argv[0]);

    for (i=1;i<argc;i++)
    {
        if (!strcasecmp(argv[i], "-o"))
        {
            if ((i+1)>=argc)
                usage(argv[0]); /* e.g. if last argument not output file */
            *output_filename=(char *)strdup(argv[++i]);
        }
        else if (a_params->filename==NULL)
            a_params->filename=(char *)strdup(argv[i]); /* if file a still null */
    }

    if ((a_params->filename==NULL) || (*output_filename==NULL))
        usage(argv[0]);
}

unsigned char binarySearch(double value)
{
    int low=0;
    int high=0x100;
    int middle=0x7f;
    
//    printf("Looking for %f\n",value);
    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);
}
