/*
 cement.c  adds uchar image into total.pdm (double) file
 steve; (ppm file format from ndf's diff.c, with help of sbeck on
         parsing input images)

 bug reports, etc., to mann@eecg.toronto.edu (steve)

*/

#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"


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



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];
    double out_pixeld[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 pixels=0;
    unsigned int numberOfPixels;
    
    p_inv=1.0/p;

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

    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 ((dest=fopen(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;
        fprintf(dest, "P%c\n", '6');
        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);
    }

    fprintf(dest, "# %s %s %s %s \n",argv[0],argv[1],argv[2],argv[3]);
    fprintf(dest, "%d %d\n%d\n", a_params.width,
            a_params.height, a_params.max_val);

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


    /* 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("  %5.2f%% done.\r",(double)100*i/numberOfPixels);
            fflush(stdout);
        }

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

        fread(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+1],p_inv);
            out_pixeld[j+2]= createValue(a_pixel[j+2],p_inv);
            if (out_pixeld[j  ] <   0) out_pixeld[j  ]=  0;
            if (out_pixeld[j  ] > 255) out_pixeld[j  ]=255;
            if (out_pixeld[j+1] <   0) out_pixeld[j+1]=  0;
            if (out_pixeld[j+1] > 255) out_pixeld[j+1]=255;
            if (out_pixeld[j+2] <   0) out_pixeld[j+2]=  0;
            if (out_pixeld[j+2] > 255) out_pixeld[j+2]=255;
            out_pixel[j  ]=(unsigned char) out_pixeld[j  ];
            out_pixel[j+1]=(unsigned char) out_pixeld[j+1];
            out_pixel[j+2]=(unsigned char) out_pixeld[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
        
        fwrite(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++)
        {

            fread(a_pixel, sizeof(double),CHANNELS_PER_PIXEL, a);

            if (a_pixel[0] < 0) a_pixel[0]=0;
            if (a_pixel[0] > 255) a_pixel[0]=255;
            if (a_pixel[1] < 0) a_pixel[1]=0;
            if (a_pixel[1] > 255) a_pixel[1]=255;
            if (a_pixel[2] < 0) a_pixel[2]=0;
            if (a_pixel[2] > 255) a_pixel[2]=255;
            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);
            fwrite(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;

    if(!feof(a))
        printf("\nExtra Pixels?\n");

    /* deal with any remaining extra pixels??? */        
    while (!feof(a))
    {
        fread(a_pixel, sizeof(double),CHANNELS_PER_PIXEL, a);
        if(feof(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]);
        fwrite(out_pixel, sizeof(unsigned char), CHANNELS_PER_PIXEL, dest);
        pixels++;

    }

    fclose(a);
    fclose(dest);

//    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)
    {
        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("\n");

    exit(EXIT_SUCCESS);
}

double powLookup[256];

void initilizeLookup(double p)
{
    int i;

    for(i=0;i<256;i++)
        powLookup[i]=pow((double)i,p);
}

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 plsm (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]);
}

