// sample problem discussed at the lecture 9/21/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 = 4*1.33;
  const float WorldHeight = 4;
  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);
unsigned int texture[3];
unsigned int tex_no = 0; 

void drawAxes() {  
  glLineWidth(3.0);
  glColor3fv(white);
  glBegin(GL_LINES); 
    glVertex2f(-5,0); 
    glVertex2f(5,0); 
    glVertex2f(0,-5); 
    glVertex2f(0,5); 
  glEnd();
}


const int checker_size = 64;
const int checker_step = 8;
GLubyte checker[checker_size*checker_size*checker_step*checker_step*4];
GLuint checker_id;
GLuint checkermip_id;


void makeChecker() { 
  bool iswhite = false; 
  for(int i =0; i < checker_size; i++) { 
    iswhite = !iswhite; 
    for(int j =0; j < checker_size; j++) {
       iswhite = !iswhite;
       for(int l = 0; l < checker_step; l++)
         for(int m = 0; m < checker_step; m++) {
           checker[(checker_size*checker_step*(i*checker_step+l) + j*checker_step+m)*4] = iswhite? 0xff:0x00;
           checker[(checker_size*checker_step*(i*checker_step+l) + j*checker_step+m)*4+1] = iswhite? 0xff:0x00;
           checker[(checker_size*checker_step*(i*checker_step+l) + j*checker_step+m)*4+2] = iswhite? 0xff:0x00;
           checker[(checker_size*checker_step*(i*checker_step+l) + j*checker_step+m)*4+3] = 0xff; 
         }
    }
  }
}

void drawQuad(const Vec2f p[4], const Vec2f tex[4]) { 
  glBegin(GL_QUADS);
  glTexCoord2fv(tex[0]);
 glVertex2fv(p[0]);
  glTexCoord2fv(tex[1]);
  glVertex2fv(p[1]);
  glTexCoord2fv(tex[2]);
  glVertex2fv(p[2]);
  glTexCoord2fv(tex[3]);
  glVertex2fv(p[3]);
  glEnd();
}

void drawGrid(const Vec2f p[4], const Vec2f tex[4], int N) { 
  for( int i = 0; i < N; i++)
  	for( int j=0; j < N; j++) {
	    float s = i/float(N), t = j/float(N); 
      glBegin(GL_POLYGON);
        glTexCoord2fv(tex[0]*(1-s)*(1-t) + tex[1]*(1-s)*t + tex[3]*s*(1-t)+tex[2]*s*t);
        glVertex2fv(p[0]*(1-s)*(1-t) + p[1]*(1-s)*t + p[3]*s*(1-t)+p[2]*s*t);
        s = (i+1)/float(N); t = j/float(N);

        glTexCoord2fv(tex[0]*(1-s)*(1-t) + tex[1]*(1-s)*t + tex[3]*s*(1-t)+tex[2]*s*t);
        glVertex2fv(p[0]*(1-s)*(1-t) + p[1]*(1-s)*t + p[3]*s*(1-t)+p[2]*s*t);

        s = (i+1)/float(N); t = (j+1)/float(N);
        glTexCoord2fv(tex[0]*(1-s)*(1-t) + tex[1]*(1-s)*t + tex[3]*s*(1-t)+tex[2]*s*t);
        glVertex2fv(p[0]*(1-s)*(1-t) + p[1]*(1-s)*t + p[3]*s*(1-t)+p[2]*s*t);

        s = i/float(N); t = (j+1)/float(N);
        glTexCoord2fv(tex[0]*(1-s)*(1-t) + tex[1]*(1-s)*t + tex[3]*s*(1-t)+tex[2]*s*t);
        glVertex2fv(p[0]*(1-s)*(1-t) + p[1]*(1-s)*t + p[3]*s*(1-t)+p[2]*s*t);

      glEnd();
	}
}


Image image[3];





void Draw() {
//  unsigned char* cptr = 
 // for(int i =0; i < 400; i++) { 

  //}
  // set color to initialize the window to; the 4th component 
  // is the alpha value and is not used unless blending is enabled
  glClearColor( 0, 0,0,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glMatrixMode(GL_MODELVIEW); 
  
  drawAxes();

  

  Vec2f p[] = {Vec2f(0,0),Vec2f(1,0),Vec2f(1,1),Vec2f(0,1)}; 
  Vec2f p_distort[] = {Vec2f(0,0),Vec2f(1,0),Vec2f(0.5+0.1,1),Vec2f(0.5-0.1,1)}; 
  Vec2f tex[] = {Vec2f(0,0),Vec2f(1,0),Vec2f(1,1),Vec2f(0,1)};

  // draw a square using non-mipmapped texture 
  // observe how it behaves when the window is resized
  glPushMatrix();
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D,checker_id);
  glTranslatef(-1.5,0.2,0);
  drawQuad( p,tex);
  glDisable(GL_TEXTURE_2D);
  glPopMatrix();

  // draw a square  using mipmapped texture 
  // compare to the previous one when resizing
  glPushMatrix();
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D,checkermip_id);
  glTranslatef(0.2,0.2,0);
  drawQuad( p,tex);
  glDisable(GL_TEXTURE_2D);
  glPopMatrix();

  // draw a quad with 1:5 ratio of opposite horizontal sides, using mipmapped texture 
  // observe the problems with linear texture interpolation
  glPushMatrix();
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D,checkermip_id);
  glTranslatef(-1.5,-1.2,0);
  drawQuad( p_distort,tex);
  glDisable(GL_TEXTURE_2D);
  glPopMatrix();

  // draw a quad with 1:5 ratio of opposite horizontal sides, split into 100 subquads, using a single non-mipmapped texture 
  // compare to the previous one
  glPushMatrix();
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D,checker_id);
  glTranslatef(0.2,-1.2,0);
  drawGrid(p_distort,tex,10);
  glDisable(GL_TEXTURE_2D);
  glPopMatrix();


  // draw a quad with 1:5 ratio of opposite horizontal sides, split into 100 subquads, using a single mipmapped texture 
  // compare to the previous one
  glPushMatrix();
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D,checkermip_id);
  glTranslatef(1.4,-1.2,0);
  drawGrid(p_distort,tex,10);
  glDisable(GL_TEXTURE_2D);
  glPopMatrix();


  glutSwapBuffers();
}

void InitTexture() { 
 
  makeChecker();
  glGenTextures(1,&checker_id);
     glBindTexture(GL_TEXTURE_2D,checker_id);
   glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
   glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
   glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
   glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
   glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, checker_size*checker_step,checker_size*checker_step, 0, GL_BGRA_EXT, 
       GL_UNSIGNED_BYTE, checker );
  

   glGenTextures(1,&checkermip_id);
   glBindTexture(GL_TEXTURE_2D,checkermip_id);
   glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
   glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
   glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
   glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
   glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

   gluBuild2DMipmaps(GL_TEXTURE_2D, 
	   GL_RGBA, checker_size*checker_step,checker_size*checker_step,
	   GL_BGRA_EXT, 
       GL_UNSIGNED_BYTE, checker );

}

int tick = 0; 

void Animate(int time) {
  // timer callback needs to be reinstalled each time 
  if( tick == 10) { tex_no = (tex_no + 1)%3; tick = 0;} 
  tick++; 
  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);

 

  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;
}











