// sample problem discussed at the lecture 9/14/2004

#include <iostream>
#ifdef _WIN32
#include <windows.h>
#endif
#include <math.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#ifndef M_PI
#define M_PI            3.14159265358979323846
#endif 

#include "cvec2t.h"
#include "cvec3t.h"

typedef CVec2T<float> Vec2f;
typedef CVec3T<float> Vec3f;

namespace Params { 
  const float WorldWidth = 1.33;
  const float WorldHeight = 1;
  const int TimerStep = 16; // millisec

  const Vec3f BackgroundColor(0.5,0.5,0.5);

  const Vec2f CourtLowerLeftCorner(-WorldWidth*0.5,-WorldHeight*0.5);  
  const float CourtHSize = WorldWidth;
  const float CourtVSize = WorldHeight; 

  const Vec2f BallInitPos(0,0); 
  const Vec2f BallInitVelocity(0.008,0.008); // in vertical screen sizes per frame 
  const Vec3f BallColor(1,1,1); 
  const float BallRadius = 0.05;
  const int BallNumSegments = 25;
  
  const float PaddleHSize = 0.01;
  const float PaddleVSize = 0.1;
  const float PaddleLeftHPos = 0.1;
  const float PaddleRightHPos = 0.9;
  const Vec3f PaddleLeftColor(0,0,1); // blue
  const Vec3f PaddleRightColor(1,0,0); // red

};

namespace WindowParams {
  static int WindowWidth = 800;
  static int WindowHeight = 600;
  static int MainWindow; 
};

class Court { 
public: 
  Court(const Vec2f& lowerleft, const Vec2f& size): _ll(lowerleft), _ur(lowerleft+size) {}

  const Vec2f& ll() const { return _ll; }
  const Vec2f& ur() const { return _ur; }

private:
 Vec2f _ll, _ur; 

}; 


class Ball { 
public:
  Ball(float r, const Vec2f& c, const Vec2f& v): _radius(r), _center(c), _velocity(v) {} 
  void draw() {
    glColor3fv(Params::BallColor);
    glBegin(GL_POLYGON);    
    for(int i = 0; i < Params::BallNumSegments; i++)
      glVertex2f( _center.x() + _radius*cos(2*M_PI*i/float(Params::BallNumSegments)),  
      _center.y() + _radius*sin(2*M_PI*i/float(Params::BallNumSegments)));
    glEnd();  
  }

  bool intersectSegment(const Vec2f& p1, const Vec2f& p2) { 
    Vec2f v = p2 - p1; 
    Vec2f w = _center - p1; 
    float v_len = v.l2();
    // components of w parallel and perpendicular to the line from p1 to p2
    Vec2f w_parall = v*w.dot(v)/v_len/v_len; 
    Vec2f w_perp = w - w_parall; 
    float d = w_perp.l2();  // distance to the line through p1 and p2
    // approximate test, assume the ball does not go far past the line
    return (d <= _radius) && (w_parall.dot(v) >= 0) && (w_parall.l2() <= v_len);  
  }

  void updateVelocity(const Vec2f& p1, const Vec2f& p2) { 
    Vec2f v = p2 -p1; 
    Vec2f u_perp = _velocity - v*_velocity.dot(v)/v.l2()/v.l2();   
    _velocity = _velocity - 2*u_perp; 
  }

  void updatePosition() {
    _center += _velocity;  
  }

  void collideRectangle(const Vec2f& ll, const Vec2f& ur) { 
    Vec2f lr(ur.x(), ll.y()); 
    Vec2f ul(ll.x(), ur.y());
         if( intersectSegment(ll,lr) ) updateVelocity(ll,lr); 
    else if( intersectSegment(lr,ur) ) updateVelocity(lr,ur); 
    else if( intersectSegment(ur,ul) ) updateVelocity(ur,ul); 
    else if( intersectSegment(ul,ll) ) updateVelocity(ul,ll); 

  }

  float radius() const { return _radius; }
  const Vec2f& center() const { return _center; }

private:
  float _radius;
  Vec2f _center;
  Vec2f _velocity; 
};


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); 
}


void Keyboard( unsigned char key, int, int) { }

Ball ball(Params::BallRadius,Params::BallInitPos,Params::BallInitVelocity);
Court court(Params::CourtLowerLeftCorner,Vec2f(Params::CourtHSize,Params::CourtVSize));

void Draw() {
  // set color to initialize the window to; the 4th component 
  // is the alpha value and is not used unless blending is enabled
  glClearColor( 0.5, 0.5,0.5,0.0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  ball.draw(); 
  // all drawing is done in the back buffer; 
  // the next function swaps the back and front buffer
  glutSwapBuffers();
}


void Animate(int time) { 
  ball.collideRectangle(court.ll(),court.ur());
  // update the state of the game
  ball.updatePosition();
  // 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);

  // 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("Pong");

  // register all callbacks

  // gets called whenever the window needs to be redrawn
  glutDisplayFunc(Draw); 
  // gets called whenever the window changes shape
  glutReshapeFunc(Reshape);
  glutKeyboardFunc(Keyboard);
  // this insures that Animate is called the first time
  glutTimerFunc(Params::TimerStep,Animate,0);
  
  // this is an infinite loop get event - dispatch event which never returns
  glutMainLoop();
  return 0;
}











