#include "Canvas.h"
#include "color.h"
#include "xmath.h"
#include <iostream>
#include <fstream>
#include <vector>
#include <GL/glut.h>


using namespace std;

namespace columbia
{

  void Canvas :: init() 
  { 
    int N = hReso*vReso;
    
    delete [] data; 
    data = new RGB [N]; 
    memset(data, 0, sizeof(RGB) * N);

    delete [] z_buffer; 
    z_buffer = new float [N];
    fill(z_buffer, z_buffer+N, -1.0E9);

    canvas_at = vReso / 2;     // default horizontal field of view = 90.
  }
  
  void Canvas :: Clear(RGB const& color)
  {
    for (int row = 0; row < vReso; row++) 
      for (int col = 0; col < hReso; col++) 
      {
	(*this)[row][col] = color;
      }
    fill(z_buffer, z_buffer + vReso * hReso, -1.0E9);
  }
  
  void Canvas :: Checkerboard(int sz, RGB col1, RGB col2)
  {
    for(int row = 0; row < vReso; row++)
      for(int col = 0; col < hReso; col++)
      {
	(*this)[row][col] = (col/sz + row/sz) % 2 ? col1 : col2;
      }
  }



  void Canvas :: WritePPM(const char *_filename) const
  {
    Canvas* This =  (Canvas*)this;
    
    if(!_filename) 
      _filename = filename.c_str();

    ofstream file;
    file.open (_filename, ios::out | ios::binary);
    
    if(file) 
    {
      file << "P6\n";   // color rawbits format
      file << hReso << " " << vReso << endl << 100 << endl;
  
      unsigned char r, g, b;
      for (int row = vReso-1; row >= 0; row--) 
	for (int col = 0; col < hReso; col++) 
	{
	  r = (unsigned char) (100.0*(*This)[row][col][0]);  
	  g = (unsigned char) (100.0*(*This)[row][col][1]);  
	  b = (unsigned char) (100.0*(*This)[row][col][2]);  

	  if(row==vReso-1 && col==0)
	    r = (unsigned char)77, g = (unsigned char)78, b = (unsigned char)79;
	  
	  if(r>100.0 || g>100.0 || b>100.0)  throw "RGB color overflow\n"; 

	  file << r << g << b;
	}

      file << endl;
      file.close();
    }
    else 
      cerr << "Can't open output file " << _filename << endl;
  }
  

  void Canvas :: ReadPPM(const char * _filename)
  {
    if(!_filename) 
      _filename = filename.c_str();

    ifstream file;
    file.open (_filename, ios::in | ios::binary);
    
    if(file) 
    {
      string id;
      file >> id;
      if(id != "P6") throw "not P6 ppm format\n";   // color rawbits format

      float scale;
      file >> hReso >> vReso >> scale;
      init();
      unsigned char r, g, b;
      file >> noskipws >> r;
      
      for (int row = vReso-1; row >= 0; row--) 
      {
	for (int col = 0; col < hReso; col++) 
	{
	  file >> noskipws >> r >> noskipws >> g >> noskipws >> b;
	  if(row==vReso-1 && col==0)
	    cout << int(r) << "___ " << int(g) << "___ " << int(b) << endl;

	  (*this)[row][col][0] = r / scale;  
	  (*this)[row][col][1] = g / scale;  
	  (*this)[row][col][2] = b / scale;  
	}
      }
      
      file.close();
    }
    else 
      cerr << "Can't open input file " << _filename << endl;
  }



  void Canvas :: _scanLineSegment(int x1, int y1, float z1, Point const* tex1, RGB const* col1, 
				  int x2, int y2, float z2, Point const* tex2, RGB const* col2, 
				  set<ScannedResult>* output)
  {
    assert( x2-x1 >= 1 && x2-x1 >= y2-y1 && y2-y1 >= 0);

    bool 
      has_tex = tex1 && tex2, 
      has_col = col1 && col2,
      horizontal =  y1==y2,
      diagonal = y2-y1==x2-x1;

    float scale = 1.0 / (x2 - x1);

    Point tex, tex_step; 
    if(has_tex)
    {
      tex = *tex1;
      tex_step = (*tex2 - *tex1) / (x2-x1);
    }
    
    RGB col, col_step;
    if(has_col)
    {
      col = *col1;
      col_step = (*col2 - *col1) * scale;
    }
    
    int y = y1; 
    float yy = y1, y_step = (y2 - y1) * scale;
    float z = z1,  z_step = (z2 - z1) * scale;
    
    for(int x = x1; x <= x2; ++x, z += z_step)
    {
      writePixel(x, y, z, has_tex? &tex : 0 , has_col? &col : 0, output);

      if(has_tex) tex += tex_step; 
      if(has_col) col += col_step; 

      if( ! horizontal )
	if( diagonal ) y++;
	else y = round( yy += y_step );
    }
  }

  void Canvas :: scanLineSegment(int x1, int y1, float z1, Point const* tex1, RGB const* col1, 
				 int x2, int y2, float z2, Point const* tex2, RGB const* col2, 
				 set<ScannedResult>* output)
  {
    if(x1==x2 && y1==y2) return;

    static RGB C1, C2;
    RGB *c1 = 0, *c2 = 0;
    if(col1 && col2)
    {
      C1 = *col1, C2 = *col2;
      c1 = &C1, c2 = &C2;
    }

    static Point T1, T2;
    Point *t1 = 0, *t2 = 0;
    if(tex1 && tex2)
    {
      T1 = *tex1, T2 = *tex2;
      t1 = &T1, t2 = &T2;
    }

    if(x1 > x2)                                       // always scan convert from left to right.
    {
      swap(x1, x2); swap(y1, y2); swap(z1, z2);
      if(c1 && c2) swap(*c1, *c2);
      if(t1 && t2) swap(*t1, *t2);
    }
    if( (flip_y = y1 > y2) )                          // always scan convert from down to up.
    {
      y1 = -y1, y2 = -y2;
    }
    if( (swap_xy = y2-y1 > x2-x1) )                   // and always scan convert a line with <= 45 deg to x-axis.
    {
      swap(x1, y1), swap(x2, y2);
    }
    _scanLineSegment(x1, y1, z1, t1, c1, x2, y2, z2, t2, c2, output);
  }


  void Canvas :: SolidTriangle(Point const& P1, Point const* T1, RGB const* col1, 
			       Point const& P2, Point const* T2, RGB const* col2, 
			       Point const& P3, Point const* T3, RGB const* col3)
			       
  {
    Point p1 = perspectiveProjection(P1);
    Point p2 = perspectiveProjection(P2);
    Point p3 = perspectiveProjection(P3);

    set<ScannedResult> scanned_pnts;                 // sorted in y val, and in x val if y val the same.
    scanLineSegment(round(p1.x), round(p1.y), p1.z, T1, col1, round(p2.x), round(p2.y), p2.z, T2, col2, &scanned_pnts);
    scanLineSegment(round(p1.x), round(p1.y), p1.z, T1, col1, round(p3.x), round(p3.y), p3.z, T3, col3, &scanned_pnts);
    scanLineSegment(round(p3.x), round(p3.y), p3.z, T3, col3, round(p2.x), round(p2.y), p2.z, T2, col2, &scanned_pnts);
    
    int cur_yval = vReso / 2 + 1;                    // initialize to an invalid value.
    set<ScannedResult> same_yval;                    // of the scanned result.
    
    bool 
      has_tex = T1 && T2 && T3, 
      has_col = col1 && col2 && col3;
    
    
    for (set<ScannedResult>::iterator it = scanned_pnts.begin(); it != scanned_pnts.end(); ++it)
    {
      int y = it->y;
      if(y != cur_yval) 
      {
	if(same_yval.size())
	{
	  set<ScannedResult>::iterator it1 = same_yval.begin(), it2 = --same_yval.end();
 	  scanLineSegment(it1->x, cur_yval, it1->z, has_tex? &it1->tex : 0, has_col? &it1->col : 0, 
			  it2->x, cur_yval, it2->z, has_tex? &it2->tex : 0, has_col? &it2->col : 0);
	  
	  same_yval.clear();
	}
	cur_yval = y;
      }
      same_yval.insert(*it);
    }
  }


  void Canvas :: Write2GLbuffer()
  {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0.0, (GLdouble) 10, 0.0, (GLdouble) 10);

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    {
      glLoadIdentity();
      glRasterPos2i(0, 0);
      glDrawPixels(hReso, vReso, GL_RGB, GL_FLOAT, data);
    }
    glPopMatrix();
    
    glFlush();
  }


}


/*
  void Canvas :: generate_gl_data()
  {
    delete [] gl_data;
    gl_data = new unsigned char [vReso * hReso * 3];
    unsigned char* ptr = gl_data;
    
    for(int r=0; r<vReso; r++)
      for(int c=0; c<hReso; c++)
	for(int i=0; i<3; i++)
	{
	  *ptr++ = (unsigned char) (255 * (*this)[r][c][i]);  
	}
  }
  
  
*/

