/*
 * 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 ascii format, and used the glut library to open a
 * window and display the image.
 *
 * To compile the program, type:
 *   gcc -Wall -lglut -o photo photo.c
 */

#if defined(_MSC_VER)
#include <windows.h>
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <GL/gl.h>
#include <GL/glut.h>

#if defined(__GNUC__)
#include <unistd.h>
#else
char * optarg = NULL;
int opterr = 1;
int optind = 1;
int optopt = 0;

int _sp = 1;

int getopt(int argc, char * argv[], const char * opts)
{
  char c;
  char * cp;

  if (_sp == 1) {
    if (optind >= argc || argv[optind][0] != '-' || argv[optind][1] == '\0')
      return EOF;
    else if (strcmp(argv[optind], "--") == 0) {
      optind++;
      return EOF;
    }
  }
  
  optopt = c = (unsigned char)argv[optind][_sp];
  if (c == ':' || (cp = strchr(opts, c)) == NULL) {
    /* If 'getopt' detects a missing option it returns a question-mark (?)
     * character, if the application has not set the variable 'opterr' to
     * zero and the first character of 'opts' is not a colon, 'getopt' also
     * prints a diagnostic message to 'stderr'.
     */
    if (opterr && *opts != ':')
      fprintf(stderr, "%s: llegal option -- %c\n", argv[0], c);

    if (argv[optind][++_sp] == '\0') {
      optind++;
      _sp = 1;
    }
    return('?');
  }
  if (*++cp == ':') {
    if (argv[optind][_sp+1] != '\0')
      optarg = &argv[optind++][_sp+1];
    else if(++optind >= argc) {
    /* If 'getopt' detects a missing option-argument, it returns the colon
     * character (:) if the first character of 'opts' was a colon, or a
     * question-mark (?) character otherwise.  If the application has not set
     * the variable 'opterr' to zero and the first character of 'opts' is not
     * a colon, 'getopt' also prints a diagnostic message to 'stderr'.
     */
    optarg = NULL;
    if (*opts == ':') {
      _sp = 1;
      return(':');
    }
    if (opterr)
      fprintf(stderr, "%s: Option requires an argument -- %c\n", argv[0], c);
      _sp = 1;
      return('?');
    } else
      optarg = argv[optind++];
    _sp = 1;
  } else {
    if (argv[optind][++_sp] == '\0') {
      _sp = 1;
      optind++;
    }
    optarg = NULL;
  }
  return(c);
}

/*
 * reset everyting.
 * We opterr alone since thats already under user control
 */
void getoptreset(void)
{
  optind = 1;
  optarg = NULL;
  optopt = 0;
  _sp = 1;
}
#endif

#if defined(_MSC_VER)
#define DIR_SEP '\\'
#else
#define DIR_SEP '/'
#endif

/* 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], DIR_SEP);
  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 view port
 */
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;
  }
}

/*! Rotate the image 180 degrees
 */
void reflext_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 colour 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': reflext_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, "rb");
  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;
}

/*!
 */
int main(int argc, char * argv[])
{
  int rc;
  char * prog_name = strrchr(argv[0], DIR_SEP);
  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;
}
