#pragma comment(lib,"opengl32.lib")
#pragma comment(lib,"glu32.lib")
#pragma comment(lib,"glut32.lib")
#pragma comment(lib,"glew32.lib")
#pragma comment(lib,"glui32.lib" )
#pragma warning(disable : 4786)

#include <GL/glew.h>
#include <GL/glut.h>
#include <glui.h>
#include <math.h>
#include <stdio.h>


//==============================================================================
class GLUTInteractor
//==============================================================================
{
public:
  GLUTInteractor() {}
  virtual ~GLUTInteractor() {}

  // These functions return true if they handle the event
  virtual bool mouse( int button, int state, int x, int y )    { return false; }
  virtual bool motion( int x, int y )                          { return false; } 
};

//==============================================================================
class ViewInteractor
//==============================================================================
{
public:
  float origin[2];  // The current view in world coordinates
  float size[2];
  int   screenWidth, screenHeight;

  bool panDragging;
  bool zoomDragging;
  int  lastX, lastY;          // tracks last mouse position

  //------------------------------------------------------------------------------
  ViewInteractor( float ox=0, float oy=0, float w=512, float h=512 )
  {
    origin[0] = ox;
    origin[1] = oy;
    size[0] = w;
    size[1] = h;
    screenWidth = w;
    screenHeight = h;
    panDragging = false;
    zoomDragging = false;
  }

  //------------------------------------------------------------------------------
  bool mouse( int button, int state, int x, int y )
  {
    bool handled = false;

    // save mouse position
    lastX = x;
    lastY = y;
   
    if( button == GLUT_LEFT_BUTTON &&               // handle panning
        state == GLUT_DOWN &&
        (glutGetModifiers() & GLUT_ACTIVE_SHIFT) )
    {
      panDragging = true;
      handled = true;
    }
    else if( button == GLUT_LEFT_BUTTON &&          // handle zooming
        state == GLUT_DOWN &&
        (glutGetModifiers() & GLUT_ACTIVE_CTRL) )
    {
      zoomDragging = true;
      handled = true;
    }
    else if( button == GLUT_LEFT_BUTTON && 
             state == GLUT_UP )
    {
      zoomDragging = false;
      panDragging = false;
    }

    return handled;
  }

  //------------------------------------------------------------------------------
  bool motion( int x, int y )
  {
    bool handled = false;
    int dx = x - lastX;
    int dy = lastY - y;
    if( panDragging )
    {
      float scale = size[0]/screenWidth; // assume same scale in y
      origin[0] -= dx * scale;  
      origin[1] -= dy * scale;
      handled = true;  
    }
    else if( zoomDragging )
    {
      float cx = origin[0] + size[0]/2 ;  // save center of screen
      float cy = origin[1] + size[1]/2 ;

      // compute a scale factor 
      float scale = 1 + fabs( float(dy)/screenHeight );
      if( dy < 0 )
        scale = 1 / scale;

      // scale size and reset origin
      size[0] *= scale;
      size[1] *= scale;
      origin[0] = cx - size[0]/2;
      origin[1] = cy - size[1]/2;

      handled = true;
    }
    lastX = x;
    lastY = y;
    return handled;
  }
  

  //------------------------------------------------------------------------------
  void setupView()
  {
    glViewport(0, 0, screenWidth, screenHeight);
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    gluOrtho2D( origin[0], origin[0]+size[0], origin[1], origin[1]+size[1] );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();

  }
};


//==============================================================================
class Image
//==============================================================================
{
public:
  int w, h;
  unsigned char* rgbData;
  unsigned texID;

  // transformation stuff
  float translation[2];
  float rotation;
  float scale[2];
  float skew;
  

  //------------------------------------------------------------------------------
  Image()
    : w(0), h(0), rgbData(0), texID(0)
  {
    translation[0] = translation[1] = 0;
    rotation = 0;
    scale[0] = scale[1] = 1;
    skew = 0;
  }

  //------------------------------------------------------------------------------
  ~Image()
  {
    delete rgbData;
  }

  //------------------------------------------------------------------------------
  // returns false on failure
  bool loadPPM( const char* filename )
  {
    // This code lifted from GLVU
    FILE* fp = fopen(filename, "rb");
    if (fp==NULL) 
    { 
      printf("Unable to open %s!\n",filename);
      rgbData=NULL; w=0; h=0; 
      return false; 
    }
    int c,s;
    do{ do { s=fgetc(fp); } while (s!='\n'); } while ((c=fgetc(fp))=='#');
    ungetc(c,fp);
    fscanf(fp, "%d %d\n255\n", &w, &h);
    int numComponents = w*h*3;
    if (rgbData==NULL)
      rgbData = new unsigned char[numComponents];
    fread(rgbData,numComponents,1,fp);
    fclose(fp);

    return true;
  }


  //------------------------------------------------------------------------------
  void initTexture()
  {
    // generate a unique texture id
    glGenTextures( 1, &texID );

    // bind the texture so we can manipulate it
    glBindTexture(GL_TEXTURE_2D, texID);

    // set interpolation parameters
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

    // upload the data to the texture
    glTexImage2D(GL_TEXTURE_2D, 0, 3, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, rgbData );
  }

  //------------------------------------------------------------------------------
  void setupTransform()
  {
    float skewMatrix[] = 
    {  
      1, skew, 0, 0,
      0,    1, 0, 0,
      0,    0, 1, 0,
      0,    0, 0, 1
    };
    
    glTranslatef( translation[0], translation[1], 0);
    glScalef( scale[0], scale[1], 1);    
    glRotatef( rotation, 0, 0, 1);        
    glMultTransposeMatrixf( skewMatrix ); // OpenGL takes column major matrices. 
                                          //   Ours is row major
  }

  //------------------------------------------------------------------------------
  void draw()
  {
    glBindTexture(GL_TEXTURE_2D, texID);
    glBegin(GL_POLYGON);
      glTexCoord2d(0,1); glVertex2d(0,0);
      glTexCoord2d(1,1); glVertex2d(w, 0);
      glTexCoord2d(1,0); glVertex2d(w, h);
      glTexCoord2d(0,0); glVertex2d(0, h);
    glEnd();    
  }

  //------------------------------------------------------------------------------
  void drawBorder()
  {
    glBegin( GL_LINE_LOOP );
      glVertex2d(0,0);
      glVertex2d(w, 0);
      glVertex2d(w, h);
      glVertex2d(0, h);
    glEnd();    
  }
};



//==============================================================================
class ImageInteractor
//==============================================================================
{
public:
  bool imageDragging;
  int lastX, lastY;

  ImageInteractor();
  bool mouse( int button, int state, int x, int y );
  bool motion( int x, int y );
  int  pick( int x, int y );
};








// glut callbacks
void display();
void idle();
void keyboard(unsigned char k, int x, int y);
void mouse( int button, int state, int x, int y );
void motion( int x, int y );
void reshape( int w, int y );
void init();


// GUI stuff
void createGUI();
void GUICallback( int id );
void syncGUI();

void checkForGLError( char *msg );


// global variables
ViewInteractor view( 0, 0, 800, 600 );
ImageInteractor imageInteractor;
int imageCount;
Image* images;
int curImage = 0;   




//------------------------------------------------------------------------------
void main( int argc, char* argv[] )
{
  if( argc == 1 )
  {
    printf("usage: ImagePlay <image1> <image2> <image3> ...\n" );
    return;
  }
  

  // Create a window and an OpenGL context
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA );
  glutInitWindowSize( view.screenWidth, view.screenHeight );
  glutCreateWindow("Image Play");

  glewInit();

  // load and stack images
  imageCount = argc - 1;
  images = new Image[imageCount];
  for( int i=0; i < argc - 1; i++ )
  {
    images[i].loadPPM( argv[i+1] );
    images[i].initTexture();
    images[i].translation[0] += i*20;
    images[i].translation[1] += i*20;
  }
  curImage = 0;

  createGUI();

  glutDisplayFunc( display );
  glutMouseFunc( mouse );
  glutMotionFunc( motion );
  glutReshapeFunc( reshape );
  glutKeyboardFunc( keyboard );
  glutMainLoop();
}

//-----------------------------------------------------------------------------
void keyboard(unsigned char k, int x, int y)
{
  printf("key:%c\n", k );
  
  // use numbers to select current image
  if( '1' <= k && k <= '9' )
  {
    int i = k - '1';
    printf("i:%d\n", i );  
    if( i < imageCount )  
      curImage = i;
    syncGUI();
  }
  
  switch( k )
  {
    case 'R':
      view.origin[0] = 0;
      view.origin[1] = 0;
      break;    

    case 'r':
      images[curImage].translation[0] = 0; 
      images[curImage].translation[1] = 0; 
      images[curImage].scale[0]       = 1; 
      images[curImage].scale[1]       = 1; 
      images[curImage].rotation       = 0;   
      images[curImage].skew           = 0;  
      syncGUI();
      break;
  }  

  glutPostRedisplay();
}

//-----------------------------------------------------------------------------
void mouse( int button, int state, int x, int y )
{
  bool handled = view.mouse( button, state, x, y );
  if( !handled ) 
    handled = imageInteractor.mouse( button, state, x, y );
  glutPostRedisplay(); // redraw
}

//------------------------------------------------------------------------------
void motion( int x, int y )
{
  bool handled = view.motion( x, y );
  if( !handled ) 
    handled = imageInteractor.motion( x, y );

  glutPostRedisplay(); // redraw
}

//------------------------------------------------------------------------------
void reshape( int w, int h )
{
  float scale = view.size[0] / view.screenWidth;
  float oldH = view.size[1];
  view.size[0] = w * scale;   // resize
  view.size[1] = h * scale;
  view.origin[1] -= view.size[1] - oldH;  
  view.screenWidth = w;
  view.screenHeight = h;

  // change view to have the same aspect ratio as the screen
  //float aspectRatio = float(w)/h;
  //view.size[0] = view.size[1] * aspectRatio;

  glutPostRedisplay(); // redraw
}

//-----------------------------------------------------------------------------
void display()
{
  checkForGLError( "display() - begin" );
  glClearColor(0,0,0,1);
  glClear( GL_COLOR_BUFFER_BIT );

  view.setupView();

  // Set texturing mode to take colors directly from texture
  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
  glEnable( GL_TEXTURE_2D );
  
  // draw images
  for( int i=0; i < imageCount; i++ )
  {
    glPushMatrix();
      images[i].setupTransform();  
      images[i].draw();      
      if( i == curImage )   
      {
        // draw border
        glDisable( GL_TEXTURE_2D );
        glColor3f(1,0,0);
        images[curImage].drawBorder();
        glEnable( GL_TEXTURE_2D );
      }
    glPopMatrix();
  }

  glutSwapBuffers();
  checkForGLError( "display() - end" );
}



//------------------------------------------------------------------------------
void checkForGLError( char *msg )
{
 GLenum errCode;
 const GLubyte *errStr;
 if ((errCode = glGetError()) != GL_NO_ERROR) 
 {
    errStr = gluErrorString(errCode);
    fprintf(stderr,"OpenGL ERROR: %s: %s\n", errStr, msg);
 }
}



////////////////////////////////////////////////////////////////////////////////
//
// ImageInteractor
//
////////////////////////////////////////////////////////////////////////////////

ImageInteractor::ImageInteractor()     
  {
    imageDragging = false;
  }

//------------------------------------------------------------------------------
  bool ImageInteractor::mouse( int button, int state, int x, int y )
{
  bool handled = false;

  // save mouse position
  lastX = x;
  lastY = y;
  
  if( button == GLUT_LEFT_BUTTON &&               // handle panning
      state == GLUT_DOWN  )
  {
    int i = pick( x, y );
    if( i >= 0 )
    {
      printf("Selected %d\n", i );
      curImage = i;
      syncGUI();
      imageDragging = true;
      handled = true;
    }
  }
  else if( button == GLUT_LEFT_BUTTON && 
            state == GLUT_UP )
  {  
    imageDragging = false;  
  }
  return handled;
}

//------------------------------------------------------------------------------
bool ImageInteractor::motion( int x, int y )
{
  bool handled = false;
  int dx = x - lastX;
  int dy = lastY - y;
  if( imageDragging )
  {
    float scale = view.size[0]/view.screenWidth; // assume same scale in y
    images[curImage].translation[0] += dx * scale;
    images[curImage].translation[1] += dy * scale;
    syncGUI();
    handled = true;    
  }
  lastX = x;
  lastY = y;
  return handled;
}

//------------------------------------------------------------------------------
int ImageInteractor::pick( int x, int y )
{
  const int BUFSIZE = 256;
  int unsigned buffer[BUFSIZE];
  float M[16];
  int viewport[4];
  
  view.setupView();

  // get the projection matrix and viewport 
  glGetFloatv( GL_PROJECTION_MATRIX, M );
  glGetIntegerv( GL_VIEWPORT, viewport );
  
  // set the modified projection matrix
  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();
  gluPickMatrix( x, viewport[3] - y, 3, 3, viewport );
  glMultMatrixf( M );
  glMatrixMode( GL_MODELVIEW );

  // render in selection mode
  glSelectBuffer( BUFSIZE, buffer );
  glRenderMode( GL_SELECT );
  glInitNames();
  glPushName(0);
   
  for( int i=imageCount-1; i >= 0; i-- )
  {  
    glLoadName(i);
    glPushMatrix();
    images[i].setupTransform();
    images[i].draw();
    glPopMatrix();  
  }

  int hits = glRenderMode( GL_RENDER );
  printf("hits:%d\n", hits );
  if( hits )  
    return buffer[3]; // name of first primitive in the hit
  else
    return -1;
}



////////////////////////////////////////////////////////////////////////////////
// 
//  G U I    S T U F F
//
////////////////////////////////////////////////////////////////////////////////

//------------------------------------------------------------------------------
float tx=0, ty=0;
float sx=0, sy=0;
float skew=0;
float rot=0;
GLUI* glui = NULL;
int mainWindow;


void createGUI()
{
  mainWindow = glutGetWindow();

  GLUI_Spinner* spinner;
  glui = GLUI_Master.create_glui( "Transform parameters" );  
  spinner = glui->add_spinner( "Translate X:", GLUI_SPINNER_FLOAT, &tx, 0, GUICallback );
  spinner->set_float_limits( -2000, 2000 );
  spinner = glui->add_spinner( "Translate Y:", GLUI_SPINNER_FLOAT, &ty, 0, GUICallback );
  spinner->set_float_limits( -2000, 2000 );
  spinner = glui->add_spinner( "Rotation:", GLUI_SPINNER_FLOAT, &rot, 0, GUICallback );
  spinner->set_float_limits( -360, 360 );
  spinner = glui->add_spinner( "Scale X:", GLUI_SPINNER_FLOAT, &sx, 0, GUICallback );
  spinner->set_float_limits( .1, 10 );
  spinner = glui->add_spinner( "Scale Y:", GLUI_SPINNER_FLOAT, &sy, 0, GUICallback );
  spinner->set_float_limits( .1, 10 );
  spinner = glui->add_spinner( "Skew:", GLUI_SPINNER_FLOAT, &skew, 0, GUICallback );
  spinner->set_float_limits( -5, 5 );

  syncGUI();
}

//------------------------------------------------------------------------------
void syncGUI()
{
  tx = images[curImage].translation[0];
  ty = images[curImage].translation[1];
  sx = images[curImage].scale[0];
  sy = images[curImage].scale[1];
  rot = images[curImage].rotation;
  skew = images[curImage].skew;
  if( glui ) glui->sync_live();
}

//------------------------------------------------------------------------------
void GUICallback( int id )
{
  images[curImage].translation[0] = tx; 
  images[curImage].translation[1] = ty; 
  images[curImage].scale[0]       = sx; 
  images[curImage].scale[1]       = sy; 
  images[curImage].rotation       = rot;   
  images[curImage].skew           = skew;  
  
  // post redisplay to main window
  glutSetWindow( mainWindow );
  glutPostRedisplay();
}