#include <stdio.h>
#include <png.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>

/* usage: eWav2png-4096 input.eWav output.png (1|2|3|4|5|6) */

int main(int argc, char* argv[])
{
    /***************************/
    /* Variable initialization */
    /***************************/
    FILE *ifp,*ofp;

    int DEBUG=0;
    /* eWav information */
    u_int16_t *Amplitude;
    typedef struct eWavHead 
    {
	u_int64_t Channels;            /* number of channels stored in file */
	u_int64_t WFD_Offset;          /* Offset in bytes from beginning of file to first eWav File Directory */
    } eWavHead;                     /* 16 bytes */
    
    typedef struct ChannelHeader 
    {
	float ResX;                 /* Image resolution in dots/cm */
	float RexY;                 /* Image resolution in dots/cm */
	double Scale_mm_sec;
	double Scale_mv_cm;
	u_int32_t Samples;
    } ChannelHeader;
    
    eWavHead FileHeader;
    ChannelHeader ch;
    u_int16_t max,min;

    /* image information */
    int data_width, image_width,image_height, x, y, row, image_float, image_size;
    int bottom, top, last, i, j, png_scale, amp;
    float xpixline,ypixline,xstep,ystep;

    /* png information */
    png_structp write_ptr;
    png_infop write_info_ptr;

    unsigned char **row_pointers, invisigrey, linegrey, background, signalblack;
    /*********************/
    /* Read in eWav file */
    /*********************/
    ifp = fopen(argv[1], "rb");
    fread(&FileHeader,sizeof(eWavHead),1,ifp);
    fread(&ch,sizeof(ChannelHeader),1,ifp);

    if (DEBUG) fprintf(stderr,"Channels:%d, Offset:%d\n",(int)FileHeader.Channels,(int)FileHeader.WFD_Offset);
    if (DEBUG) fprintf(stderr,"X resolution: %f, Y resolution: %f, cm/sec: %f, Millivolts/cm: %f, Samples:%d\n",
	   ch.ResX,ch.RexY,ch.Scale_mm_sec/10,ch.Scale_mv_cm,(int)ch.Samples);

    png_scale=atoi(argv[3]);
    data_width=ch.Samples;

    Amplitude=(u_int16_t *)calloc(data_width,sizeof(u_int16_t));

    fread(Amplitude,sizeof(u_int16_t),data_width,ifp);

    fclose(ifp);

    /*******************************/
    /* Find max and min amplitudes */
    /*******************************/
    max=min=Amplitude[0];

    for(i=0;i<data_width;i++)
    {
	if (Amplitude[i]>max)
	    max=Amplitude[i];
	if (Amplitude[i]<min)
	    min=Amplitude[i];
    }

    if (DEBUG) fprintf(stderr,"Max amp=%d, Min amp=%d\n",max,min);
    if (DEBUG) fprintf(stderr,"Pixels/sec:%f, Pixels/mVolt:%f\n",ch.ResX*ch.Scale_mm_sec/10,ch.RexY*ch.Scale_mv_cm);

    /*****************************************/
    /* Determine image size and grid spacing */
    /*****************************************/
    xpixline=((ch.ResX*ch.Scale_mm_sec/10)/5)/png_scale;  /* width in pixels between */
    ypixline=(ch.RexY*ch.Scale_mv_cm)/png_scale;

    if (DEBUG) fprintf(stderr,"xpixline=%f, ypixline=%f\n",xpixline,ypixline);
    image_height=1.2*(max-min)/png_scale;
    image_float=0.1*(max-min)/png_scale;

    if (4096>=data_width/png_scale)
	image_width=data_width/png_scale;
    else
	image_width=4096;

    xstep=image_width/xpixline;
    ystep=image_height/ypixline;
    if (DEBUG) fprintf(stderr,"xstep=%f, ystep=%f\n",xstep,ystep);

    /******************************/
    /* Allocate clear image space */
    /******************************/
    row_pointers=malloc(image_height*sizeof(unsigned char *));
    for (row = 0; row < image_height; row++)
    {
	row_pointers[row] = malloc(image_width*sizeof(unsigned char));
	memset(row_pointers[row],255,image_width*sizeof(unsigned char));
    }
    if (DEBUG) fprintf(stderr,"Created image space.\n");
    image_size=image_height*image_width;
    printf("%d %d %d\n",image_height,image_width,image_size);
    /*************/
    /* Draw Dots */
    /*************/
    background=255;
    invisigrey=240;
    linegrey=200;

    if (DEBUG) fprintf(stderr," Draw vertical guidelines\n");
    /* Draw vertical guidelines */
    for (x=0;x<=(int)(xstep*5);x++)
	for (y=0;y<image_height;y++)
	{
	    row_pointers[y][(int)floor(x*xpixline/5)]=invisigrey;
	    row_pointers[y][(int)floor(x*xpixline/5)+1]=invisigrey;
	}

    if (DEBUG) fprintf(stderr," Draw virtual horizontal guidelines marking intersections with vertical guidelines\n");
    /* Draw virtual horizontal guidelines marking intersections with vertical guidelines */
    for (y=0;y<=(int)(ystep*5);y++)
	for (x=0;x<image_width;x++)
	{
	    if (invisigrey==row_pointers[(int)floor(y*ypixline/5)][x])
		row_pointers[(int)floor(y*ypixline/5)][x]=linegrey;
	    if (invisigrey==row_pointers[((int)floor(y*ypixline/5)+1)][x])
		row_pointers[((int)floor(y*ypixline/5)+1)][x]=linegrey;
	}

    if (DEBUG) fprintf(stderr," Erase vertical guidelines\n"); 
    /* Erase vertical guidelines */ 
    for (x=0;x<=(int)(xstep*5);x++) 
        for (y=0;y<image_height;y++) 
        { 
	    if (invisigrey==row_pointers[y][(int)floor(x*xpixline/5)])
		row_pointers[y][(int)floor(x*xpixline/5)]=background;
	    if (invisigrey==row_pointers[y][(int)floor(x*xpixline/5)+1])
		row_pointers[y][(int)floor(x*xpixline/5)+1]=background;
        } 

    /*************/
    /* Draw grid */
    /*************/

    if (DEBUG) fprintf(stderr," Draw vertical grid lines\n");
    /* Draw vertical grid lines */
    for (x=0;x<=(int)(xstep);x++)
	for (y=0;y<image_height;y++)
	{
	    row_pointers[y][(int)floor(x*xpixline)]=linegrey;
	    row_pointers[y][(int)floor(x*xpixline)+1]=linegrey;
	}
    
    if (DEBUG) fprintf(stderr," Draw horizontal grid lines\n");
    /* Draw horizontal grid lines */
    for (y=0;y<=(int)(ystep);y++)
	for (x=0;x<image_width;x++)
	{
	    row_pointers[(int)floor(y*ypixline)][x]=linegrey;
	    row_pointers[((int)floor(y*ypixline)+1)][x]=linegrey;
	}


    /***************/
    /* Draw signal */
    /***************/

    signalblack=0;
    last=Amplitude[0]/png_scale;
    for (x=1;x<image_width*png_scale-png_scale;x+=png_scale)
    {
	bottom=-1;
	top=1;
	amp=0;
	for (i=0; i<png_scale;i++)
	{
	    j=x+i;
	    amp+=Amplitude[j];
	    if (DEBUG) fprintf(stderr,"Amplitude[%d+%d]=%d\n",x,i,Amplitude[j]);
	}
	amp/=png_scale; /* take average */
	if (DEBUG) fprintf(stderr," Average=%d   ",amp);
	amp/=png_scale; /* reduce scale */
	if (DEBUG) fprintf(stderr,"Scale=%d\n",amp);


	/* This makes the line at least 5 pixels wide. */
	/* if the datapoints are seperated by more than 2 pixels, the line is made */
	/* wider by the corresponding number of pixels. */
	if (last-amp<bottom) 
	    bottom=last-amp-1;
	if (last-amp>top)
	    top=last-amp+1;
	if (DEBUG) fprintf(stderr,"Last=%d, current=%d, Bottom=%d, Top=%d\n",last,amp,bottom,top);
	last=amp;
	for (y=bottom;y<=top;y++)
	    for (j=-1;j<=1;j++)
	    {
		if (DEBUG) fprintf(stderr,"row_pointers[(%d-(%d-%d+%d+%d))][%d+%d]\n",image_height,amp,min/png_scale,image_float,y,x/png_scale,j);
		row_pointers[(image_height-(amp-min/png_scale+image_float+y))][x/png_scale+j]=signalblack;
	    }
    }


    /*******************/
    /* Write PNG image */
    /*******************/
    
    /* Open output file */
    ofp = fopen(argv[2], "wb");
    if (!ofp)
	return(1); /* 1 = couldn't open output file */
    if (DEBUG) fprintf(stderr,"Opened output file.\n");


    /* Initialize image variables */
    write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL,
					(png_error_ptr)NULL, (png_error_ptr)NULL);
    if (!write_ptr)
	return(2); /* 2 = couldn't allocate write structure */
    if (DEBUG) fprintf(stderr,"Allocated write structure.\n");
    
    write_info_ptr = png_create_info_struct(write_ptr);
    if (!write_info_ptr)
    {
	png_destroy_write_struct(&write_ptr,(png_infopp)NULL);
	return(3); /* 3 = couldn't allocate info structure */
    }
    if (DEBUG) fprintf(stderr,"Allocated Info structure.\n");

    if (setjmp(write_ptr->jmpbuf))
    {    
        png_destroy_write_struct(&write_ptr, &write_info_ptr);
        fclose(ofp);
        return(4); /* 4 = libpng write error */
    }
    if (DEBUG) fprintf(stderr,"Tested longjmp\n");

    png_init_io(write_ptr, ofp);
    if (DEBUG) fprintf(stderr,"Initialized output pointer.\n");


    /* Define information about image */
    png_set_IHDR(write_ptr, write_info_ptr, image_width, image_height,
		 8, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE,
		 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
    if (DEBUG) fprintf(stderr,"Defined info about image.\n");


    /* Write info up to image data */
    png_write_info(write_ptr, write_info_ptr);
    if (DEBUG) fprintf(stderr,"Wrote preliminary image data.\n");

    png_write_image(write_ptr, row_pointers);

    png_write_end(write_ptr, write_info_ptr);
    if (DEBUG) fprintf(stderr,"Called end to data.\n");

    /* Tidy up */

    png_destroy_write_struct(&write_ptr, &write_info_ptr);
    if (DEBUG) fprintf(stderr,"Destroyed write structure.\n");

    exit(0);

}