/*
 * This program displays an input image. It can be enhanced to perform various
 * operations on the image interactively, as a response to user key-strokes, or
 * non-interactively, as a response to command line options.
 *
 * It reads an image in ppm binary format, and uses the glut library to open a
 * window and display the image.
 *
 * To compile the program, type:
 *   gcc -Wall -lglut -o photo photo.c
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <GL/gl.h>
#include <GL/glut.h>

/* Program information */
char option_str[] = "hv";
int verbose = 0;

/* Window information */
int win_width = 640, win_height = 480;

/* Image Information */
unsigned char * image = 0;
int image_width;
int image_height;
    
/*! Print a concise message describing the command line and the various
 * command line options
 */
static void print_help(char * prog_name)
{
  printf("Usage: %s [options] file\n\
  -h \t\tprint this help message\n\
  -v \ttoggle verbose\n", prog_name);
}

/*!
 * \return code
 * error        - return code < 0
 * help         - return code > 0
 * otherwise    - return code = 0
 */
int parse_command_line(int argc, char * argv[])
{
  int c;
  char * prog_name = strrchr(argv[0], '/');
  prog_name = (prog_name) ? prog_name+1 : argv[0];

  while ((c = getopt(argc, argv, option_str)) != EOF) {
    switch (c) {
     case 'h': print_help(prog_name); return 1;
     case 'v': verbose = !verbose; break;
      
      default:
        fprintf(stderr, "Try `%s -h' for more information.\n", prog_name);
        return -1;
    }
  }

  return 0;
}

/*! Initialize the graphics system */
void init_gfx()
{
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

/*! Reshape the viewport */
void reshape(GLint width, GLint height) 
{
  win_width = width;
  win_height = height;
  glViewport(0, 0, width, height);
}

/*! Display the scene */
void display(void) 
{
  glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
  glClear(GL_COLOR_BUFFER_BIT);
  glDrawPixels(image_width, image_height, GL_RGB, GL_UNSIGNED_BYTE, image);
  glutSwapBuffers();
}

/*! Exit the program after deallocating used memory */
void my_exit(int rc)
{
  if (image) {
    free(image);
    image = 0;
  }
  exit(rc);
}

/*! Swap lines i and j of the image */
void swap_image_line(int i, int j)
{
  int k;
  unsigned char tmp;
  int lineSize = image_width * 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;
  }
}

/*! Reflect the image about a central horizontal line */
void reflect_hor_image(void)
{
  int half = image_height / 2;
  int i;
  
  for (i = 0; i < half; i++) swap_image_line(i, image_height - 1 - i);
}

/*! Convert color to gray scale */
void gray_image()
{
  int i, j, k = 0;
  GLubyte r, g, b, x;
  for (j = 0; j < image_height; j++) {
    for (i = 0; i < image_width; 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;
    }
  }
}

/*! Handle keyword strokes */
void keyboard(unsigned char c, int x, int y)
{
  switch (c) {
    case 27: my_exit(0); break;
    case 'h': reflect_hor_image(); glutPostRedisplay(); break;
    case 'g': gray_image(); glutPostRedisplay(); break;
  }
}

/*! Read an entire line of the image from the input stream */
int get_line(FILE * stream, char * line, int size)
{
  do {
    if (!fgets(line, size, stream)) {
      fprintf(stderr, "ERROR: Something is wrong!\n");
      return -1;
    }
  } while (line[0] == '#');
  return 0;
}

/*! Read the image in ppm format from the input file into memory */
int init_image(char * file_name)
{
#define MAXLINE 256
  char line[MAXLINE] = "";
  FILE * ppmfile = fopen (file_name, "r");
  if (!ppmfile) {
    fprintf(stderr, "ERROR: Cannot read file %s!\n", file_name);
    return -1;
  }
  if (get_line(ppmfile, line, MAXLINE) < 0) return -1;
  sscanf(line, "P6\n");

  if (get_line(ppmfile, line, MAXLINE) < 0) return -1;
  sscanf(line, "%d %d\n", &image_width, &image_height);

  if (get_line(ppmfile, line, MAXLINE) < 0) return -1;
  sscanf(line, "255\n");
  
  if (verbose) printf("width,height = %d,%d\n", image_width, image_height);
  image =
    (GLubyte *) malloc (sizeof (GLubyte) * image_width * image_height * 3);
  if (!image) {
    fprintf(stderr, "ERROR: Insufficient memory!\n");
    return -1;
  }
  if (fread(image, image_width * 3, image_height, ppmfile) != image_height) {
    fprintf(stderr, "ERROR: Read failed!\n");
    return -1;
  }
  fclose(ppmfile);
  return 0;
}

/*! main entry */
int main(int argc, char * argv[])
{
  int rc;
  char * prog_name = strrchr(argv[0], '/');
  prog_name = (prog_name) ? prog_name+1 : argv[0];

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

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

  glutInit(&argc, argv);
  glutInitWindowSize(win_width, win_height);
  glutInitWindowPosition(0, 0);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
  glutCreateWindow(prog_name);

  init_gfx();
    
  glutReshapeFunc(reshape);
  glutDisplayFunc(display);
  glutKeyboardFunc(keyboard);
  glutMainLoop();
  return 0;
}
