// sample problem discussed at the lecture 9/28/2004

#define GLUT_API_VERSION 4

#include <iostream>

#ifdef _WIN32
#include <windows.h>
#endif
#include <math.h>
#include <strstream>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#ifndef M_PI
#define M_PI            3.14159265358979323846
#endif 
#include <Magick++.h>
#include "cvec2t.h"
#include "cvec3t.h"

typedef CVec2T<float> Vec2f;
typedef CVec3T<float> Vec3f;

using namespace Magick;

namespace WindowParams {
  static int WindowWidth = 800;
  static int WindowHeight = 600;
  static int MainWindow; 
};

namespace Params { 
  const float WorldWidth = 5*1.33;
  const float WorldHeight = 5;
  const int TimerStep = 16; // millisec

  const Vec3f BackgroundColor(0,0,0);

};


void Reshape(int width, int height) {

  // glViewport defines part of the window we are going to use 
  WindowParams::WindowWidth = width;
  WindowParams::WindowHeight = height;
  glViewport(0,0,width,height);

  // initialize the viewing transformation (camera position) to identity; 
  // this corresponds to camera looking down negative z axis
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  // specify the projection transformation (camera parameters);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  // this defines a camera with orthogonal projection that captures the 
  // part of the virtual world inside the box from 
  // -WorldWidth/2 to WorldWidth/2 in the X direction 
  // -WorldHeight/2 to WorldHeight/2 in the Y direction 
  // and from 0 to 1 (default in Z direction 
  gluOrtho2D(-Params::WorldWidth/2, Params::WorldWidth/2,-Params::WorldHeight/2,Params::WorldHeight/2); 
}



const Vec3f red(1,0,0); 
const Vec3f blue(0,0,1); 
const Vec3f green(0,1,0); 
const Vec3f white(1,1,1);
Image image[3];
unsigned int texture[3];
unsigned int tex_no = 0; 
int tick = 0; 

void drawAxes() {  
  glLineWidth(3.0);
  glColor3fv(white);
  glBegin(GL_LINES); 
    glVertex2f(-5,0); 
    glVertex2f(5,0); 
    glVertex2f(0,-5); 
    glVertex2f(0,5); 
  glEnd();
}

void Draw() {
  // set color to initialize the window to; the 4th component 
  // is the alpha value and is not used unless blending is enabled and the blending mode uses destimation alpha
  glClearColor( 0, 0,0,1);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glMatrixMode(GL_MODELVIEW); 
  
  drawAxes();

  // displaying images using texture mapping
  // the image scales and moves with the polygon; 
  // if the window is resized, the image is resized
  // draw a texture mapped rectangle with animated texture 
  glPushMatrix();
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D,texture[tex_no]);
  glTranslatef(0,0,0);
  glScalef(3,3,1);
  glColor3f(0,0,1);
  glBegin(GL_POLYGON);
  glTexCoord2f(0,0);
  glVertex2f(0,0);
  glTexCoord2f(2,0);
  glVertex2f(1,0);
  glTexCoord2f(2,1);
  glVertex2f(1,1);
  glTexCoord2f(0,1);
  glVertex2f(0,1);
  glEnd();
  glDisable(GL_TEXTURE_2D);
  glPopMatrix();

  // displaying images directly drawing on screen
  // the image retains the original pixel dimensions and does not scale with window size
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); 
  glPushMatrix();
  glRasterPos2f(0,0);
  glDrawPixels(image[0].columns(),image[0].rows(), GL_BGRA_EXT, GL_UNSIGNED_SHORT, image[0].getPixels(0,0,image[0].columns(),image[0].rows() ));
  glDisable(GL_BLEND);
  glPopMatrix();
  
  glutSwapBuffers();
}

void InitTexture() { 
   // get 3 texture ids recorded in the array 
   glGenTextures(3,texture);


   for( int i = 0; i < 3; i++) {
     // set current texture
     glBindTexture(GL_TEXTURE_2D,texture[i]);
	 // filter used to resample texture when the texture pixel screen size is smaller than pixel
     glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
     // filter used to resample texture when the texture pixel screen size is larger than pixel
     glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
     // how the texture behaves when the texture coordinates are outside 0..1 range
     glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
     glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
     glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

     glTexImage2D(GL_TEXTURE_2D, 
	   0, // resolution level 0 = base image, other values used only when defining mipmaps manually 
	   GL_RGBA, // internal format for storing texture; keep RGBA
	   image[i].columns(), // must be power of 2 + 2*border  
	   image[i].rows(), // must be power of 2 (the power may be different)
	   0, // border 
	   GL_BGRA_EXT, // format in which pixels are stored in ImageMagick 
	   GL_UNSIGNED_SHORT, // 16 bits per color per pixel as used in ImageMagick
      image[i].getPixels(0,0,image[i].columns(),image[i].rows() ) // pointer to image data
	  );
   }
}



void Animate(int time) {
  // cycle textures every 3 frames to animate
  if( tick == 7) { tex_no = (tex_no + 1)%3; tick = 0;} 
  tick++; 
  // timer callback needs to be reinstalled each time 
  glutTimerFunc(Params::TimerStep,Animate,0);
  glutPostRedisplay();
}

int main(int argc, char* argv[]) {

  // initialize glut and parse command-line aguments that glut understands
  glutInit(&argc, argv);

  InitializeMagick(*argv);
  // initialize dislay mode: 4 color components, double buffer and depth buffer
 
  glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE|GLUT_DEPTH);

  glutInitWindowSize(WindowParams::WindowWidth,WindowParams::WindowHeight);

  for(int i= 0; i < 3; i++) { 
    char c[] = "1";
    c[0] += i; 
  
    try {image[i].read( (string("vinni-eyes-") + string(c) + string(".png")).c_str() );
 
    }        catch( exception &error_ ) {
       cout << "Caught exception: " << error_.what() << endl;
       return 1;
    }
    image[i].flip();
  }

  WindowParams::MainWindow = glutCreateWindow("Transforms");

  // register all callbacks

  // gets called whenever the window needs to be redrawn
  glutDisplayFunc(Draw); 
  // gets called whenever the window changes shape
  glutReshapeFunc(Reshape);

  // this insures that Animate is called the first time
  glutTimerFunc(Params::TimerStep,Animate,0);

  InitTexture();
  // this is an infinite loop get event - dispatch event which never returns
  glutMainLoop();
  return 0;
}











