#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "mat_util.h"
#include "ppm.h"

// program gets passed ppm image filename  and blur amount
void usage();
void blurfunc( Var *image, int blursize, Image * filter);
int loadFilter( char * filename, Image ** myFilter );

/* A global - determines whether or not a gaussian filter is being used. */
int FILTER;

int main(int argc, char *argv[]) {
  char infilename[2048]; // hopefully path and filename not greater than 2K
  char outfilename[2048];
  char filtfilename[2048]; 
  char first;
  int val;

  int blursize;
  Var myImage;
  Image * myFilter = NULL; /* used for gaussian blur (or whatever blur). */

  if (argc != 4) {
	usage();
        exit(0);
  }
 
  /* Determine whether radius or filter filename is given. */
  sprintf(infilename, "%s", argv[1]);  
  sprintf(outfilename, "%s", argv[3]);
  first = argv[2][0];
  val = first - '0';

  printf("val: %d\n", val);

  if ((val <= 42 && val >= 17) || (val <= 74 && val >= 49) || (val == -2) || (val == -1))
    {
      FILTER = TRUE;
      sprintf(filtfilename, "%s", argv[2]);
      fprintf(stderr,"%s: infilename = %s,  filtfilename = %s;  outfilename = %s; ... \n",argv[0],infilename,filtfilename,outfilename);
      blursize = loadFilter(filtfilename, &myFilter);
    }


  else if (val < 10 && val > 0) 
    {
      FILTER = FALSE;
      blursize = atoi(argv[2]);
      fprintf(stderr,"%s: infilename = %s, blursize = %d;  outfilename = %s; ... \n",argv[0],infilename,blursize,outfilename);  
    }
  else
    {
      usage();
      exit(0);
    }

  //if blursize is not odd.
  if ((blursize % 2) == 0) {
    usage();
    exit(0);
  }  

  // load ppm file into Image
  load_ppm_file(infilename, &myImage); 

  switch( myImage.magic_number ) {
   case GREY_PGM  :fprintf(stderr,"%s is a pgm\n", infilename); break;
   case RGB_PPM   :fprintf(stderr,"%s is a ppm\n", infilename); break;
   case GREY_PDM  :fprintf(stderr,"%s is a grayscale pdm\n", infilename); break;
   case RGB_PDM   :fprintf(stderr,"%s is a color pdm\n", infilename); break;
   case GREY_PLM  :fprintf(stderr,"%s is a greyscale plm\n", infilename); break;
   case RGB_PLM   :fprintf(stderr,"%s is a color plm\n", infilename); break;
   default        :fprintf(stderr,"blur couldn't determine type of input\n");
                      return -1;
  }

  // blur it 
  blurfunc(&myImage, blursize, myFilter);

  // write image back to disk
  save_pgm_file(&myImage, outfilename);  

  return(1);
}

void blurfunc( Var *image, int blursize, Image *filter )
{
  Var *ANS, *ANS2;
  int m,n,k,i,j,index;
  Image tmp;
  int channels = image->channels;
  int width, halfblur;

  ANS = (Var *)calloc(1, sizeof(Var));
  ANS2 = (Var *)calloc(1, sizeof(Var));
  init_var(ANS, image->name, image->M, image->N,
	   IMAGE, channels);
  init_var(ANS2, image->name, image->M, image->N,
           IMAGE, channels);


  /* This is a gaussian blur. */
  if (FILTER == TRUE) {
    if (channels == GREY_SCALE)
      {
	// blur in the across direction (easy even with small amount of cache):
	for( m=0 ; m<image->M ; m++ )
	  {
	    for( n=0 ; n<image->N ; n++ )
	      {
		tmp = 0;
		index = m*image->N +n;

		if( n<blursize/2 ) // left edge
		  {
		    for( k=-n, i=blursize/2 - n; k<=blursize/2; k++, i++ )
		      tmp+=image->image[index +k]*filter[i];
		    
		    ANS->image[index] = tmp;
		  }
		else if( n>=image->N-blursize/2 )  // right edge
		  {
		    for( k=-blursize/2, i=0 ; k<image->N-n ; k++, i++ )
		      tmp+=image->image[index +k]*filter[i];
		    
		    ANS->image[index] = tmp;
		  }
		else  
		  {  
		    for( k=-blursize/2, i=0 ; k<=blursize/2 ; k++, i++ )
		      tmp += image->image[index + k]*filter[i];
		    
		    ANS->image[index] = tmp;
		  }
	      }
	  }
	
	//blur in the down direction (same order, in case small number lines in cache):
	for( m=0 ; m<image->M ; m++ ) {
	  for( n=0 ; n<image->N ; n++ ) {

	    tmp = 0; // CLA (clear the accumulator)

	    if( m<blursize/2 ) // top edge
	      {
		for( k=-m, i=blursize/2 - m; k<=blursize/2; k++, i++ )
		  tmp+=ANS->image[(m+k)*image->N +n]*filter[i];
		
		ANS2->image[m*image->N + n] = tmp;
	      }
	    else if( m>=image->M-blursize/2 )  // bottom edge
	      {
		for( k=-blursize/2, i=0 ; k<image->M-m ; k++, i++ )
		  tmp+=ANS->image[(k+m)*image->N +n]*filter[i];
		
		ANS2->image[m*image->N + n] = tmp;
	      }
	    else   // not at edges
	      {
		tmp = 0;
		for( k=-blursize/2, i=0 ; k<=blursize/2 ; k++, i++ )
		  tmp += ANS->image[(m+k)*image->N + n]*filter[i];
		
		ANS2->image[m*image->N + n] = tmp;
	      }
	  } 
	} 
      }
    else if (channels == RGB24)
      {      
	width = image->N*channels;
	halfblur = blursize/2*channels;

	// blur in the across direction (easy even with small amount of cache):
	for( m=0 ; m<image->M ; m++ )
	  {
	    index = m*width;

	    for( n=0 ; n<width; n++ )
	      {
		tmp = 0;
		if( n<halfblur ) // left edge
		  {
		    /* Accumulate the pixels to the right of the starting point */
		    for( k=n, i=blursize/2, j=0; j<=halfblur; k+=channels, j+=channels, i++ )
		      tmp+=image->image[index +k]*filter[i];

		    /* Accumulate the pixels to the left of the starting point */
		    for( k=n-channels, i=(blursize/2)-1; k>=0; k-=channels, i-- )
		      tmp+=image->image[index + k]*filter[i];
		
		    ANS->image[index + n] = tmp;
		  }

		else if( n>=width-halfblur )  // right edge
		  {
		    for( k=-halfblur, i=0; k<width-n ; k+=channels, i++ )
		      tmp+=image->image[index +n +k]*filter[i];

		    ANS->image[index + n] = tmp;
		  }

		else  
		  {  
		    for( k=-halfblur, i=0 ; k<=halfblur ; k+=channels, i++ )
		      tmp+=image->image[index +n +k]*filter[i];
		   
		    ANS->image[index + n] = tmp;
		  }
	      }
	  }
	
	//blur in the down direction (same order, in case small number lines in cache):
	for( m=0 ; m<image->M ; m++ ) {
	  for( n=0 ; n<width ; n++ ) {
	    
	    tmp = 0; // CLA (clear the accumulator)
	    
	    if( m<blursize/2 ) // top edge
	      {
		for( k=-m, i=blursize/2 - m; k<=blursize/2; k++, i++ )
		  tmp+=ANS->image[(m+k)*width +n]*filter[i];
		
		ANS2->image[m*width + n] = tmp;
	      
	      }
	  else if( m>=image->M-blursize/2 )  // bottom edge
	    {
	      for( k=-blursize/2, i=0 ; k<image->M-m ; k++, i++ )
		tmp+=ANS->image[(k+m)*width +n]*filter[i];
	      
	      ANS2->image[m*width + n] = tmp;
	    }
	    else   // not at edges
	      {
		tmp = 0;
		for( k=-blursize/2, i=0 ; k<=blursize/2 ; k++, i++ )
		  tmp += ANS->image[(m+k)*width + n]*filter[i];
	      
	      ANS2->image[m*width + n] = tmp;
	    }
	  } 
	} 
      }
  }

  else 
    {  /* This is not a gaussian blur */
    
      if (channels == GREY_SCALE)
      {
	// blur in the across direction (easy even with small amount of cache):
	for( m=0 ; m<image->M ; m++ )
	  {
	    for( n=0 ; n<image->N ; n++ )
	      {
		tmp = 0;
		if( n<blursize/2 ) // left edge
		  {
		    for( k=-n; k<=blursize/2; k++ ){
		      tmp+=image->image[m*image->N +n +k];
		    }
		    
		    ANS->image[m*image->N + n] = (tmp/(n+1+blursize/2));
		  }
		else if( n>=image->N-blursize/2 )  // right edge
		  {
		    for( k=-blursize/2 ; k<image->N-n ; k++ )
		      tmp+=image->image[m*image->N +n +k];
		    
		    ANS->image[m*image->N + n] = (tmp/(image->N-n+blursize/2));
		  }
		else  
		  {  
		    for( k=-blursize/2 ; k<=blursize/2 ; k++ )
		      tmp += image->image[m*image->N + n + k];
		    
		    ANS->image[m*image->N + n] = (tmp/blursize);
		  }
	      }
	  }
	
	//blur in the down direction (same order, in case small number lines in cache):
	for( m=0 ; m<image->M ; m++ ) {
	  for( n=0 ; n<image->N ; n++ ) {
	    tmp = 0; // CLA (clear the accumulator)
	    if( m<blursize/2 ) // top edge
	      {
		for( k=-m; k<=blursize/2; k++ )
		  tmp+=ANS->image[(m+k)*image->N +n];
		
		ANS2->image[m*image->N + n] = (tmp/(m+1+blursize/2));
	      }
	    else if( m>=image->M-blursize/2 )  // bottom edge
	      {
		for( k=-blursize/2 ; k<image->M-m ; k++ )
		  tmp+=ANS->image[(k+m)*image->N +n];
		
		ANS2->image[m*image->N + n] = (tmp/(image->M-m+blursize/2));
	      }
	    else   // not at edges
	      {
		tmp = 0;
		for( k=-blursize/2 ; k<=blursize/2 ; k++ )
		  tmp += ANS->image[(m+k)*image->N + n];
		
		ANS2->image[m*image->N + n] = (tmp/blursize);
	      }
	  } 
	} 
      }
    else if (channels == RGB24)
      {      
	width = image->N*channels;
	halfblur = blursize/2*channels;
	
	// blur in the across direction (easy even with small amount of cache):
	for( m=0 ; m<image->M ; m++ )
	  {
	    for( n=0 ; n<width; n++ )
	      {
		tmp = 0;
		if( n<halfblur ) // left edge
		  {
		    /* Accumulate the pixels to the right of the starting point */
		    for( k=n, i=0, j=0; j<=halfblur; k+=channels, j+=channels, i++ )
		      tmp+=image->image[m*width +k];

		    /* Accumulate the pixels to the left of the starting point */
		    for( k=n-channels; k>=0; k-=channels, i++ )
		      tmp+=image->image[m*width + k];
		
		    ANS->image[m*width + n] = (tmp/i);
		  }
		else if( n>=width-halfblur )  // right edge
		  {
		    for( k=-halfblur, i=0; k<width-n ; k+=channels, i++ )
		      tmp+=image->image[m*width +n +k];

		    ANS->image[m*width + n] = (tmp/i);
		  }
		else  
		  {  
		    for( k=-halfblur ; k<=halfblur ; k+=channels )
		      tmp+=image->image[m*width +n +k];
		   
		    ANS->image[m*width + n] = (tmp/blursize);
		  }
	      }
	  }

	//blur in the down direction (same order, in case small number lines in cache):
	for( m=0 ; m<image->M ; m++ ) {
	  for( n=0 ; n<width ; n++ ) {
	    
	    tmp = 0; // CLA (clear the accumulator)

	    if( m<blursize/2 ) // top edge
	      {
		for( k=-m; k<=blursize/2; k++ )
		  tmp+=ANS->image[(m+k)*width +n];
		
		ANS2->image[m*width + n] = (tmp/(m+1+blursize/2));
		
	      }
	    else if( m>=image->M-blursize/2 )  // bottom edge
	      {
		for( k=-blursize/2 ; k<image->M-m ; k++ )
		  tmp+=ANS->image[(k+m)*width +n];
		
		ANS2->image[m*width + n] = (tmp/(image->M-m+blursize/2));
	      }
	    else   // not at edges
	      {
		tmp = 0;
		for( k=-blursize/2 ; k<=blursize/2 ; k++ )
		  tmp += ANS->image[(m+k)*width + n];
		
		ANS2->image[m*width + n] = (tmp/blursize);
	      }
	  } 
	} 
      }
  }

  /* - - Copy answer to original image - - */
  copy_image(ANS2, image);

  /* - - Free OLD image - - */
  free(ANS2->image);
  free(ANS->image);
  
}

void usage() {
  fprintf(stderr, "Usage: ./blur v001.ppm filter.ppm out.ppm\n");
  fprintf(stderr, "     : ./blur v001.ppm 7 out.ppm\n");
  fprintf(stderr, "only supports odd valued blur radii\n");
}


/* Given a filename and a 1-D array, loads the contents of the file into 
 * the array. Array can be no longer than 500 elements. 
 * Returns the number of elements read in (length of array). 
 */
int loadFilter( char * filename, Image ** myFilter )
{
  /* Assume the filter is no longer than 500 elements. */
  float buffer[500];
  FILE * file_ptr;
  int result = 0;
  int i = -1;
  int j;

  file_ptr = fopen(filename, "r");

  while (result != EOF){
    i++;
    result = fscanf(file_ptr, "%f", &(buffer[i]));
  }

  *myFilter = (Image *) malloc(i*sizeof(Image));
  if (myFilter == NULL) {
    fprintf(stderr, "Unable to malloc in loadFilter.\n");
    exit(0);
  }
  
  /* Copy filter from buffer. */
  for (j=0; j<i; j++)
    (*myFilter)[j] = (Image) buffer[j]; 

  return i;
}
