//texturemapping demo

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>
#include <fstream>
#include <iostream>
#include <string>
using namespace std;

/*  Create checkerboard texture  */
#define checkImageWidth 64
#define checkImageHeight 64
static GLubyte checkImage[checkImageHeight][checkImageWidth][4];

#define marioImageWidth 256
#define marioImageHeight 256
static GLubyte marioImage[marioImageHeight][marioImageWidth][4];

static GLuint texName;
static GLuint texName2;

//this function is just some algorithm to try to programmtically
//compute when the mario image should be visible or not
//it does this because the "background" is white and the
// mario begins with "black"
//going with width first, it blacks out everything up until the
//first "non-white" pixel and then after the last one
//then using height, it does the same. This gives a pretty
//good approximation of the mario

//this method might work for other "crappy" images that you
//pick up from random sources
void funtransparency() {

  int w = marioImageWidth;
  int h = marioImageHeight;
  int fb = -1,lb = -1;
  GLubyte threshold = (GLubyte)75;

  for(int j = h-1; j >= 0; j--) {
    fb = -1; lb = -1;
    for(int i = 0; i < w; i++) {
      if(fb != -1 && (marioImage[j][i][0] <= threshold ||
		      marioImage[j][i][1] <= threshold ||
		      marioImage[j][i][2] <= threshold )) {
	lb = i;
      }

      if(fb == -1 && (marioImage[j][i][0] <= threshold ||
		      marioImage[j][i][1] <= threshold ||
		      marioImage[j][i][2] <= threshold ) ){
	fb = i;
      }
    }


    
    for(int i = 0; i < fb; i++) {
      marioImage[j][i][3] = 0;
    }
    if(lb != -1)
      for(int i = lb; i < w; i++) {
	marioImage[j][i][3] = 0;
      }
    if(fb == lb && lb == -1) {
      for(int i = 0; i < w; i++) {
	marioImage[j][i][3] = 0;
      }
    }
    
  }



  fb = -1; lb = -1;
  for(int j = 0; j < w; j++) {
    fb = -1; lb = -1;
    for(int i = 0; i < h; i++) {
      if(fb != -1 && (marioImage[i][j][0] <= threshold ||
		      marioImage[i][j][1] <= threshold ||
		      marioImage[i][j][2] <= threshold ) ){
	lb = i;
      }
      
      if(fb == -1 && (marioImage[i][j][0] <= threshold ||
		      marioImage[i][j][1] <= threshold ||
		      marioImage[i][j][2] <= threshold ) 
	 ) {
	fb = i;
      }
    }

    for(int i = 0; i < fb; i++) {
      marioImage[i][j][3] = 0;
    }
    if(lb != -1)
      for(int i = lb; i < h; i++) {
	marioImage[i][j][3] = 0;
      }
    if(fb == lb && lb == -1) {
      for(int i = 0; i < h; i++) {
	marioImage[i][j][3] = 0;
      }
    }
  }

}

//another p6 helper
//get an entire line - for reading comments
void handleComment(ifstream &fin) {
  cout << "Comment: #";
  char c = fin.get();
  while(c != '\n') {
    cout << c;
    c = fin.get();
  }
  cout << c;
  
}

//this will eat up "whitespace" in the p6 format
//turns out that I can throw out spaces and all
//the things until I hit the next digit I'm looking for
//once I see it, TODO: handle comments
void eatNonDigitsUntilDigit(ifstream &fin) {
  char c = fin.get();
  while(c < '0' || c > '9') {
    if(c == '\n') { //assumes lines end with \n
      c = fin.get();
      if(c == '#') { //comments begin with #
	handleComment(fin);
      }
      else //wasn't a comment, put it back in case it was a digit and needs to be handled
	fin.putback(c);
    }
    c = fin.get();
  }
  fin.putback(c);

}


//tricky functions to help out
//eats the last character - so if you need that
//you'll need to put it back on the stream or use it
//turns out, I know the next byte after reading ascii
//numbers is thrown out in P6 format
//technically I should call this make unsigned integer
unsigned int makeUnsignedInteger(ifstream &fin) {

  unsigned int number = 0;
  char c = fin.get();
  while(c >= '0' && c <= '9') {
    number = 10*number + (int)(c-'0');
    c = fin.get();
  }
  fin.putback(c); //put the last nonnumerical digit back in case it is needed by somewhere else
  return number;
}


//reopen the file in binary format
//failing to use binary format
//results in some strange things
//so instead we have to deal with P6's
//mix of ascii and binary

void readPPMP6(char *file) {
  ifstream fin(file, ios::binary);
  cout <<"     [*] Reading P6" << endl;
  char junk;
  fin >> junk;
  fin >> junk;
  eatNonDigitsUntilDigit(fin);
  
  unsigned int w,h, maxval;

  w = makeUnsignedInteger(fin);
  eatNonDigitsUntilDigit(fin);
  h = makeUnsignedInteger(fin);
  eatNonDigitsUntilDigit(fin);
  maxval = makeUnsignedInteger(fin);
  fin.get(junk); //one white space after the last 
  

  cout << w << " " << h << " " << maxval << endl;

  for(int i = h-1; i >= 0; i--) {
    for(int j = 0; j < (int)w; j++) {
      if(!fin.good()) {
	cout << "fin failed ... ";
	cout << "bad, fail,eof: " << fin.bad() << " " << fin.fail() << " " << fin.eof() << endl;
	fin.clear();
      }
      char r,g,b;
      fin.get(r);
      fin.get(g);
      fin.get(b);

	
      marioImage[i][j][0] = (GLubyte)r;
      marioImage[i][j][1] = (GLubyte)g;
      marioImage[i][j][2] = (GLubyte)b;
      marioImage[i][j][3] = 255;
    }
  }
  
}


//This will read ASCII ppm files. P3
//This uses a C++ style of file read
//I wrote this quickly during class, and my image was square
//so there may be some errors
//This is an ascii / P3 reader
//I highly recommend using a binary P6 reader. 
void readPPM(char *file) {
  cout <<" [*] Reading PPM from file: \"" << file << "\"" << endl;
  ifstream fin(file); //an ascii ifstream
  if(!fin) {
    cout <<"Cannot open file!" << endl;
    return;
  }
  string a;
  fin >> a;
  cout << "     - Magic Number: " << a << endl; //this is called the magic number, this function only works with P3
  if(a != "P3") {
    if(a == "P6") {
      fin.close();
      readPPMP6(file);
      return;
    }
    cout <<"     - Not P3/P6 ppm format. Reported format: \"" << a << "\"" << endl;
    return;
  }
  int w, h;
  fin >> w >> h;
  cout << w << " " << h << endl;
  int maxval;
  fin >> maxval;
  cout << "Maxval: " << maxval << endl;
  for(int j = h-1; j >= 0; j--) { //for every row -- I start at h because ppm
    for(int i = 0; i < w; i++) { //for every column
      int c;
      fin >> c;
      //normally you'd have to do c/maxval to get the float color value.
      //But I knew my ppm had 255 as maxval and so I just cast it as a ubyte
      marioImage[j][i][0] = (GLubyte) c;
      fin >> c;
      marioImage[j][i][1] = (GLubyte) c;
      fin >> c;
      marioImage[j][i][2] = (GLubyte) c;
      marioImage[j][i][3] = 255;
    }
  }

  fin.close();

}


//this was the checkboard image 64x64 checkerboard
//much of this code is from ch9 of the redbook
//easily available online by googling opengl texture
void makeCheckImage(void)
{
  int i, j, c;

  for (i = 0; i < checkImageHeight; i++) {
    for (j = 0; j < checkImageWidth; j++) {
      c = (i/8)%2;
      if( (j/8)%2 == 0)
	c = !c;
    
      checkImage[i][j][0] = (GLubyte) 255*c;
      checkImage[i][j][1] = (GLubyte) 0;
      checkImage[i][j][2] = (GLubyte) 255;
      checkImage[i][j][3] = (GLubyte) 255;
    }
  }
}


//initialize the texture stuff!
void init(void)
{
  glClearColor (0.0, 0.0, 0.0, 0.0);
  glShadeModel(GL_FLAT);
  glEnable(GL_DEPTH_TEST);

  makeCheckImage();
  char filename[] = "dp7.ppm";
  //char filename[] = "Philae2.ppm";
  readPPM(filename);
  funtransparency();
  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

  glGenTextures(1, &texName);
  glBindTexture(GL_TEXTURE_2D, texName);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); //GL_REPEAT or GL_CLAMP
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
		  GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
		  GL_LINEAR);
  //  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, checkImageWidth, checkImageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,checkImage);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, marioImageWidth, marioImageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, marioImage);



  glGenTextures(1, &texName2);
  glBindTexture(GL_TEXTURE_2D, texName2);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); //GL_REPEAT or GL_CLAMP
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
		  GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
		  GL_LINEAR);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, checkImageWidth, checkImageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,checkImage);
  //  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, marioImageWidth, marioImageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, marioImage);

  
  glEnable(GL_BLEND); //if you want transparency, turn on blending
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //tells how to blend
}

void display(void)
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  //type of texturing
  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); //decal

  
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, texName2);
  glColor3f(1,0,0);
  glBegin(GL_QUADS);
  glTexCoord2f(0.0, 0.0); glVertex3f(-2.0, -2.0, -1.0);
  glTexCoord2f(0.0, 1.0); glVertex3f(-2.0, 2.0, -1.0);
  glTexCoord2f(1.0, 1.0); glVertex3f(2.0, 2.0, -1.0);
  glTexCoord2f(1.0, 0.0); glVertex3f(2.0, -2.0, -1.0);
  glEnd();




  //which texture we're going to be using
  glBindTexture(GL_TEXTURE_2D, texName);
  
  //the bg color
  glColor3f(0,0,0);
  glBegin(GL_QUADS);

  glTexCoord2f(0.0, 0.0); glVertex3f(-2.0, -1.0, 0.0);
  glTexCoord2f(0.0, 1.0); glVertex3f(-2.0, 1.0, 0.0);
  glTexCoord2f(1.0, 1.0); glVertex3f(0.0, 1.0, 0.0);
  glTexCoord2f(1.0, 0.0); glVertex3f(0.0, -1.0, 0.0);

  glTexCoord2f(0.0, 0.0); glVertex3f(1.0, -1.0, 0.0);
  glTexCoord2f(0.0, 1.0); glVertex3f(1.0, 1.0, 0.0);
  glTexCoord2f(1.0, 1.0); glVertex3f(2.41421, 1.0, -1.41421);
  glTexCoord2f(1.0, 0.0); glVertex3f(2.41421, -1.0, -1.41421);
  glEnd();

  glDisable(GL_TEXTURE_2D);


 
  
  glFlush();

}

void reshape(int w, int h)
{
  glViewport(0, 0, (GLsizei) w, (GLsizei) h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 30.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.0, 0.0, -3.6);
}

void keyboard (unsigned char key, int x, int y)
{
  switch (key) {
  case 27: case 'q':
    exit(0);
    break;
  default:
    break;
  }
}

int main(int argc, char** argv)
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
  glutInitWindowSize(250, 250);
  glutInitWindowPosition(100, 100);
  glutCreateWindow(argv[0]);
  init();
  glutDisplayFunc(display);
  glutReshapeFunc(reshape);
  glutKeyboardFunc(keyboard);
  glutMainLoop();
  return 0;
}

