#include "GL/glew.h"
#include "hardware_renderer.h"

#include <cmath>
#include <vector>
#include <iostream>
#include <algorithm>

#include "triangulation.h"

using namespace std;

namespace CMU462 {

void HardwareRenderer::resize(size_t w, size_t h) {
  context_w = w;
  context_h = h;
}

void HardwareRenderer::begin2DDrawing() {
  glPushAttrib( GL_VIEWPORT_BIT );
  glViewport( 0, 0, context_w, context_h );

  glMatrixMode( GL_PROJECTION );
  glPushMatrix();
  glLoadIdentity();
  glOrtho( 0, context_w, context_h, 0, 0, 1 ); // Y flipped !

  glMatrixMode( GL_MODELVIEW );
  glPushMatrix();
  glLoadIdentity();
  glTranslatef( 0, 0, -1 );

}

void HardwareRenderer::leave2DDrawing() {

  glPopAttrib();
  glMatrixMode( GL_PROJECTION ); glPopMatrix();
  glMatrixMode( GL_MODELVIEW  ); glPopMatrix();

}


// Implements SoftwareRenderer //


void HardwareRenderer::draw_svg( SVG& svg ) {

  begin2DDrawing();

  // set top level transformation
  transformation = svg_2_screen;

  // draw all elements
  for ( size_t i = 0; i < svg.elements.size(); ++i ) {
    draw_element(svg.elements[i]);
  }

  // draw canvas outline
  Vector2D a = transform(Vector2D(    0    ,     0    )); a.x--; a.y--;
  Vector2D b = transform(Vector2D(svg.width,     0    )); b.x++; b.y--;
  Vector2D c = transform(Vector2D(    0    ,svg.height)); c.x--; c.y++;
  Vector2D d = transform(Vector2D(svg.width,svg.height)); d.x++; d.y++;

  rasterize_line((float)a.x, (float)a.y, (float)b.x, (float)b.y, Color::Black);
  rasterize_line((float)a.x, (float)a.y, (float)c.x, (float)c.y, Color::Black);
  rasterize_line((float)d.x, (float)d.y, (float)b.x, (float)b.y, Color::Black);
  rasterize_line((float)d.x, (float)d.y, (float)c.x, (float)c.y, Color::Black);

  leave2DDrawing();
}

void HardwareRenderer::draw_element( SVGElement* element ) {

  // push transformation matrix
  Matrix3x3 transform_save = transformation;

  // set object transformation
  transformation = transformation * element->transform;

  switch(element->type) {
    case POINT:
      draw_point(static_cast<Point&>(*element));
      break;
    case LINE:
      draw_line(static_cast<Line&>(*element));
      break;
    case POLYLINE:
      draw_polyline(static_cast<Polyline&>(*element));
      break;
    case RECT:
      draw_rect(static_cast<Rect&>(*element));
      break;
    case POLYGON:
      draw_polygon(static_cast<Polygon&>(*element));
      break;
    case ELLIPSE:
      draw_ellipse(static_cast<Ellipse&>(*element));
      break;
    case IMAGE:
      draw_image(static_cast<Image&>(*element));
      break;
    case GROUP:
      draw_group(static_cast<Group&>(*element));
      break;
    default:
      break;
  }

  // pop transformation matrix
  transformation = transform_save;

}


// Primitive Drawing //

void HardwareRenderer::draw_point( Point& point ) {

  Vector2D p = transform(point.position);
  rasterize_point( p.x, p.y, point.style.fillColor );

}

void HardwareRenderer::draw_line( Line& line ) { 

  Vector2D p0 = transform(line.from);
  Vector2D p1 = transform(line.to);
  rasterize_line( p0.x, p0.y, p1.x, p1.y, line.style.strokeColor );

}

void HardwareRenderer::draw_polyline( Polyline& polyline ) {

  Color c = polyline.style.strokeColor;

  if( c.a != 0 ) {
    int nPoints = polyline.points.size();
    for( int i = 0; i < nPoints - 1; i++ ) {
      Vector2D p0 = transform(polyline.points[(i+0) % nPoints]);
      Vector2D p1 = transform(polyline.points[(i+1) % nPoints]);
      rasterize_line( p0.x, p0.y, p1.x, p1.y, c );
    }
  }
}

void HardwareRenderer::draw_rect( Rect& rect ) {

  Color c;
  
  // draw as two triangles
  float x = rect.position.x;
  float y = rect.position.y;
  float w = rect.dimension.x;
  float h = rect.dimension.y;

  Vector2D p0 = transform(Vector2D(   x   ,   y   ));
  Vector2D p1 = transform(Vector2D( x + w ,   y   ));
  Vector2D p2 = transform(Vector2D(   x   , y + h ));
  Vector2D p3 = transform(Vector2D( x + w , y + h ));
  
  // draw fill
  c = rect.style.fillColor;
  if (c.a != 0 ) {
    rasterize_triangle( p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, c );
    rasterize_triangle( p2.x, p2.y, p1.x, p1.y, p3.x, p3.y, c );
  }

  // draw outline
  c = rect.style.strokeColor;
  if( c.a != 0 ) {
    rasterize_line( p0.x, p0.y, p1.x, p1.y, c );
    rasterize_line( p1.x, p1.y, p3.x, p3.y, c );
    rasterize_line( p3.x, p3.y, p2.x, p2.y, c );
    rasterize_line( p2.x, p2.y, p0.x, p0.y, c );
  }

}

void HardwareRenderer::draw_polygon( Polygon& polygon ) {

  Color c;

  // draw fill
  c = polygon.style.fillColor;
  if( c.a != 0 ) {

    // triangulate
    vector<Vector2D> triangles;
    triangulate( polygon, triangles );

    // draw as triangles
    for (size_t i = 0; i < triangles.size(); i += 3) {
      Vector2D p0 = transform(triangles[i + 0]);
      Vector2D p1 = transform(triangles[i + 1]);
      Vector2D p2 = transform(triangles[i + 2]);
      rasterize_triangle( p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, c );
    }
  }

  // draw outline
  c = polygon.style.strokeColor;
  if( c.a != 0 ) {
    int nPoints = polygon.points.size();
    for( int i = 0; i < nPoints; i++ ) {
      Vector2D p0 = transform(polygon.points[(i+0) % nPoints]);
      Vector2D p1 = transform(polygon.points[(i+1) % nPoints]);
      rasterize_line( p0.x, p0.y, p1.x, p1.y, c );
    }
  }
}

void HardwareRenderer::draw_ellipse( Ellipse& ellipse ) {

  // TODO

}

void HardwareRenderer::draw_image( Image& image ) {

  Vector2D p0 = transform(image.position);
  Vector2D p1 = transform(image.position + image.dimension);

  rasterize_image( p0.x, p0.y, p1.x, p1.y, image.tex );
}

void HardwareRenderer::draw_group( Group& group ) {

  for ( size_t i = 0; i < group.elements.size(); ++i ) {
    draw_element(group.elements[i]);
  }

}


// Rasterization //


void HardwareRenderer::rasterize_point(float x, float y, Color color) {
  
  // Task 1: 
  // Implement point rasterization

}

void HardwareRenderer::rasterize_line(float x0, float y0,
                                      float x1, float y1, 
                                      Color color) {

  // Task 1: 
  // Implement line rasterization

}

void HardwareRenderer::rasterize_triangle(float x0, float y0,
                                          float x1, float y1, 
                                          float x2, float y2, 
                                          Color color) {
  // Task 1: 
  // Implement triangle rasterization

}

void HardwareRenderer::rasterize_image(float x0, float y0,
                                       float x1, float y1,
                                       Texture& tex) {
  glColor4f(1, 1, 1, 1);
  
  size_t w = tex.mipmap[0].width;
  size_t h = tex.mipmap[0].height;
  unsigned char* texels = &tex.mipmap[0].texels[0];

  GLuint texid;
  glGenTextures(1, &texid);

  glBindTexture(GL_TEXTURE_2D, texid);
  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 
  
  // create texture and mipmap
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 
                              0, GL_RGBA, GL_UNSIGNED_BYTE, texels);
  glGenerateMipmap(GL_TEXTURE_2D);
  
  // enable texture and draw
  glEnable(GL_TEXTURE_2D);

  glBegin(GL_QUADS);
  glTexCoord2f(0.0, 1.0); glVertex2f( x0, y1 );
  glTexCoord2f(1.0, 1.0); glVertex2f( x1, y1 );
  glTexCoord2f(1.0, 0.0); glVertex2f( x1, y0 );
  glTexCoord2f(0.0, 0.0); glVertex2f( x0, y0 );
  glEnd();

  glDisable(GL_TEXTURE_2D);

  return;
}

} // namespace CMU462

