#if defined(_MSC_VER)
#include <windows.h>
#endif
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <GL/gl.h>
#include <GL/glut.h>

#include <getopt.h>

char * progName = 0;

char optionStr[] = "hvt:";
int winWidth = 640, winHeight = 480;
int verbose = 0;
int threshold = 20;

/* Image Information */
GLubyte * image = 0;
int imagewidth;
int imageheight;
    
/*!
 */
static void printHelp()
{
  printf("Usage: %s [options]\n\
  -h \t\tprint this help message\n\
  -t <threshold\tset the filter threshold\n\
  -v \ttoggle verbose\n", progName);
}

/*!
 * \return code
 * error        - return code < 0
 * help         - return code > 0
 * otherwise    - return code = 0
 */
int parseCommandLine(int argc, char * argv[])
{
  int c;
  while ((c = getopt(argc, argv, optionStr)) != EOF) {
    switch (c) {
      case 'h': printHelp(); return 1;
      case 'v': verbose = !verbose; break;
      case 't': threshold = atoi(optarg); break;
        
      default:
#if defined(_MSC_VER)
        fprintf(stderr, "%s: invalid option -- %c\n", progName, c);
#endif
        fprintf(stderr, "Try `%s -h' for more information.\n", progName);
        return -1;
    }
  }

  return 0;
}

/*!
 */
void InitGfx()
{
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

/*!
 */
void reshape(GLint width, GLint height) 
{
  winWidth = width;
  winHeight = height;
  glViewport(0, 0, width, height);
}

/*!
 */
void display(void) 
{
  glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
  glClear(GL_COLOR_BUFFER_BIT);
  glDrawPixels(imagewidth, imageheight, GL_RGB, GL_UNSIGNED_BYTE, image);
  glutSwapBuffers();
}

void myExit(int rc)
{
  if (image) {
    free(image);
    image = 0;
  }
  exit(rc);
}

void swapImageLine(int i, int j)
{
  int k;
  unsigned char tmp;
  int lineSize = imagewidth * 3;
  unsigned char * s1 = &image[i * lineSize];
  unsigned char * s2 = &image[j * lineSize];
  for (k = 0; k < lineSize; ++k) {
    tmp = s1[k];
    s1[k] = s2[k];
    s2[k] = tmp;
  }
}

/*!
 */
void flipImage(void)
{
  int half = imageheight / 2;
  int i;
  clock_t time_start, time_end;
  float time_elapsed;
  
  time_start = clock();
  for (i = 0; i < half; i++) swapImageLine(i, imageheight - 1 - i);
  glutPostRedisplay();
  time_end = clock();
  time_elapsed = (float) (time_end - time_start) / (float) CLOCKS_PER_SEC;
  if (verbose) printf("elapsed time = %f\n", time_elapsed);
}

void grayImage()
{
  int i, j, k = 0;
  GLubyte r, g, b, x;
  for (j = 0; j < imageheight; j++) {
    for (i = 0; i < imagewidth; i++) {
      r = image[k];
      g = image[k+1];
      b = image[k+2];
      x = (r + g + b) / 3;
      image[k] = x;
      image[k+1] = x;
      image[k+2] = x;
      k += 3;
    }
  }
  glutPostRedisplay();
}

void filterImage()
{
  int cubeWidth = 9;
  int cubeHeight = 9;
  int cubeArea = cubeWidth * cubeHeight;
  int i,j,k,l;
  int left = cubeWidth/2;
  int bottom = cubeHeight/2;
  int right = imagewidth + left - cubeWidth;
  int top = imageheight + bottom - cubeHeight;
  GLubyte * newImage = (GLubyte *)malloc(imageheight * imagewidth * 3);

  for (j = bottom; j < top; j++) {
    for (i = left; i < right; i++) {
      unsigned int r = 0, g = 0, b = 0;
      int index;
      for (l = (j - bottom); l < (j - bottom + cubeHeight); l++) {
        for (k = (i - left); k < (i - left + cubeWidth); k++) {
          index = 3 * (l * imagewidth + k);
          r += image[index];
          g += image[index+1];
          b += image[index+2];
        }
      }
      index = 3 * (j * imagewidth + i);
      newImage[index] = r / cubeArea;
      newImage[index+1] = g / cubeArea;
      newImage[index+2] = b / cubeArea;
    }
  }
  free(image);
  image = newImage;
  glutPostRedisplay();
}

/*!
 */
void keyboard(unsigned char c, int x, int y)
{
  switch (c) {
    case 27: myExit(0); break;
    case 'r': flipImage(); break;
    case 'g': grayImage(); break;
    case 'f': filterImage(); break;
  }
}

/*!
 */
int getLine(FILE * stream, char * line, int size)
{
  do {
    if (!fgets(line, size, stream)) {
      fprintf(stderr, "Something is wrong!\n");
      return -1;
    }
  } while (line[0] == '#');
  return 0;
}

/*!
 */
int initImage(char * fileName)
{
#define MAXLINE 256
  char line[MAXLINE] = "";
  FILE * ppmfile = fopen (fileName, "r");
  if (!ppmfile) {
    fprintf(stderr, "Cannot read file %s\n!", fileName);
    return -1;
  }
  if (getLine(ppmfile, line, MAXLINE) < 0) return -1;
  sscanf(line, "P6\n");

  if (getLine(ppmfile, line, MAXLINE) < 0) return -1;
  sscanf(line, "%d %d\n", &imagewidth, &imageheight);

  if (getLine(ppmfile, line, MAXLINE) < 0) return -1;
  sscanf(line, "255\n");
  
  if (verbose) printf("width,height = %d,%d\n", imagewidth, imageheight);
  image = (GLubyte *) malloc (sizeof (GLubyte) * imagewidth * imageheight * 3);
  fread (image, imagewidth * 3, imageheight, ppmfile);
  fclose (ppmfile);
  return 0;
}

/*!
 */
int main(int argc, char * argv[])
{
  int rc;
#if defined(_MSC_VER)
  int dir_sep = '\\';
#else
  int dir_sep = '/';
#endif
  char * progName = strrchr(argv[0], dir_sep);
  progName = (progName) ? progName+1 : argv[0];

  // Command line:
  rc = parseCommandLine(argc, argv);
  if (rc != 0) return rc;

  if (argc <= optind) {
    fprintf(stderr, "Missing image file\n");
    return -1;
  }
  rc = initImage(argv[optind++]);
  if (rc < 0) return rc;  

  glutInit(&argc, argv);
  glutInitWindowSize(winWidth, winHeight);
  glutInitWindowPosition(0, 0);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
  glutCreateWindow(progName);

  InitGfx();
  // Scene->init();
    
  glutReshapeFunc(reshape);
  glutDisplayFunc(display);
  glutKeyboardFunc(keyboard);
  glutMainLoop();
  return 0;
}
