#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <float.h>
#include <string.h>
#include <string.h>

#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif

#include "../../tinyexr.h"
#include "trackball.h"

static int mouse_x, mouse_y;
static int mouse_m_pressed;
static int mouse_r_pressed;
static int mouse_moving;
static int width = 512, height = 512;
static float view_org[3], view_tgt[3];
static float curr_quat[4], prev_quat[4];
static float color_scale = 1.0f;

DeepImage gDeepImage;

//
// --
//

static void reshape(int w, int h) {
 glViewport(0, 0, w, h);
 glMatrixMode(GL_PROJECTION);
 glLoadIdentity();
 gluPerspective(5.0, (float)w / (float)h, 0.1f, 1000.0f);
 glMatrixMode(GL_MODELVIEW);
 glLoadIdentity();

 width = w;
 height = h;
}

static void draw_samples() {
 glPointSize(1.0f);
 glColor3f(1.0f, 1.0f, 1.0f);

 glBegin(GL_POINTS);

 // find depth channel.
 // @todo { Do this only once. }
 int depthChan = 0;
 int rChan = -1;
 int raChan = -1;
 int gChan = -1;
 int gaChan = -1;
 int bChan = -1;
 int baChan = -1;

 for (int c = 0; c < gDeepImage.num_channels; c++) {
   if (strcmp("Z", gDeepImage.channel_names[c]) == 0) {
     depthChan = c;
   } else if (strcmp("R", gDeepImage.channel_names[c]) == 0) {
     rChan = c;
   } else if (strcmp("RA", gDeepImage.channel_names[c]) == 0) {
     raChan = c;
   } else if (strcmp("G", gDeepImage.channel_names[c]) == 0) {
     gChan = c;
   } else if (strcmp("GA", gDeepImage.channel_names[c]) == 0) {
     gaChan = c;
   } else if (strcmp("B", gDeepImage.channel_names[c]) == 0) {
     bChan = c;
   } else if (strcmp("BA", gDeepImage.channel_names[c]) == 0) {
     baChan = c;
   }
 }

 for (int y = 0; y < gDeepImage.height; y++) {
   float py = 2.0f * ((gDeepImage.height - y - 1) / (float)gDeepImage.height) -
              1.0f; // upside down?
   int sampleNum = gDeepImage.offset_table[y][gDeepImage.width - 1];

   int s_start = 0; // First pixel data starts at 0
   for (int x = 0; x < gDeepImage.width; x++) {
     float px = 2.0f * (x / (float)gDeepImage.width) - 1.0f;
     int s_end = gDeepImage.offset_table[y][x];
     if (s_start >= sampleNum || s_end >= sampleNum) {
       continue;
     }
     for (int s = s_start; s < s_end; s++) {
       float pz = -gDeepImage.image[depthChan][y][s];

       float red = 1.0f;
       float green = 1.0f;
       float blue = 1.0f;
       float red_alpha = 1.0f;
       float green_alpha = 1.0f;
       float blue_alpha = 1.0f;
       if (rChan >= 0) {
         red = gDeepImage.image[rChan][y][s];
       }
       if (raChan >= 0) {
         red_alpha = gDeepImage.image[raChan][y][s];
       }
       if (gChan >= 0) {
         green = gDeepImage.image[gChan][y][s];
       }
       if (gaChan >= 0) {
         green_alpha = gDeepImage.image[gaChan][y][s];
       }
       if (bChan >= 0) {
         blue = gDeepImage.image[bChan][y][s];
       }
       if (baChan >= 0) {
         blue_alpha = gDeepImage.image[baChan][y][s];
       }
       // unmultiply and apply scaling
       red *= color_scale / red_alpha;
       green *= color_scale / green_alpha;
       blue *= color_scale / blue_alpha;
       glColor3f(red, green, blue);
       glVertex3f(px, py, pz);
     }

     s_start = s_end;
   }
 }

 glEnd();
}

static void display() {
 GLfloat mat[4][4];

 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

 glEnable(GL_DEPTH_TEST);

 glMatrixMode(GL_MODELVIEW);
 glLoadIdentity();

 // camera & rotate
 gluLookAt(view_org[0], view_org[1], view_org[2], view_tgt[0], view_tgt[1],
           view_tgt[2], 0.0, 1.0, 0.0);

 build_rotmatrix(mat, curr_quat);
 glMultMatrixf(&mat[0][0]);

 draw_samples();

 // glBegin(GL_POLYGON);
 //    glTexCoord2f(0 , 0); glVertex2f(-0.9 , -0.9);
 //    glTexCoord2f(0 , 1); glVertex2f(-0.9 , 0.9);
 //    glTexCoord2f(1 , 1); glVertex2f(0.9 , 0.9);
 //    glTexCoord2f(1 , 0); glVertex2f(0.9 , -0.9);
 // glEnd();

 glutSwapBuffers();
}

static void keyboard(unsigned char key, int x, int y) {
 switch (key) {
 case 'q':
 case 27:
   exit(0);
   break;
 case 'c':
   color_scale += 1.0f;
   break;
 case 'x':
   color_scale -= 1.0f;
   if (color_scale < 1.0f)
     color_scale = 1.0f;
   break;
 default:
   break;
 }

 glutPostRedisplay();
}

static void mouse(int button, int state, int x, int y) {
 int mod = glutGetModifiers();

 if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
   if (mod == GLUT_ACTIVE_SHIFT) {
     mouse_m_pressed = 1;
   } else if (mod == GLUT_ACTIVE_CTRL) {
     mouse_r_pressed = 1;
   } else {
     trackball(prev_quat, 0, 0, 0, 0);
   }
   mouse_moving = 1;
   mouse_x = x;
   mouse_y = y;
 } else if (button == GLUT_LEFT_BUTTON && state == GLUT_UP) {
   mouse_m_pressed = 0;
   mouse_r_pressed = 0;
   mouse_moving = 0;
 }
}

static void motion(int x, int y) {
 float w = 1.0;
 float mw = 0.1;

 if (mouse_moving) {
   if (mouse_r_pressed) {
     view_org[2] += mw * (mouse_y - y);
     view_tgt[2] += mw * (mouse_y - y);
   } else if (mouse_m_pressed) {
     view_org[0] += mw * (mouse_x - x);
     view_org[1] -= mw * (mouse_y - y);
     view_tgt[0] += mw * (mouse_x - x);
     view_tgt[1] -= mw * (mouse_y - y);
   } else {
     trackball(prev_quat, w * (2.0 * mouse_x - width) / width,
               w * (height - 2.0 * mouse_y) / height,
               w * (2.0 * x - width) / width, w * (height - 2.0 * y) / height);
     add_quats(prev_quat, curr_quat, curr_quat);
   }

   mouse_x = x;
   mouse_y = y;
 }

 glutPostRedisplay();
}

static void init() {
 trackball(curr_quat, 0, 0, 0, 0);

 view_org[0] = 0.0f;
 view_org[1] = 0.0f;
 view_org[2] = 3.0f;

 view_tgt[0] = 0.0f;
 view_tgt[1] = 0.0f;
 view_tgt[2] = 0.0f;
}

int main(int argc, char **argv) {
 const char *input = "input.exr";

 if (argc < 2) {
   printf("Usage: deepview <input.exr>\n");
   exit(1);
 }

 input = argv[1];
 const char *err;
 int ret = LoadDeepEXR(&gDeepImage, input, &err);
 if (ret != 0) {
   if (err) {
     fprintf(stderr, "ERR: %s\n", err);
   }
   exit(-1);
 }

 glutInit(&argc, argv);
 glutInitWindowSize(512, 512);
 glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA);

 init();

 glutCreateWindow("deepimage viewer");

 glutReshapeFunc(reshape);
 glutDisplayFunc(display);
 glutKeyboardFunc(keyboard);
 glutMouseFunc(mouse);
 glutMotionFunc(motion);
 glutMainLoop();

 return 0;
}