//////////////////////////////
//Main Libary
/////////////////////////////
#define NO_SDL_GLEXT
#include <glew.h>
#include "SDL/SDL.H"
#include  "SDL/SDL_IMAGE.H"
#include "SDL/SDL_OPENGL.H"
#undef main
#include <iostream>
#include <cassert>
#include <string>
#include <algorithm>
#include <boost/iostreams/stream.hpp>
#include <libs/iostreams/example/container_device.hpp>
#include <boost/filesystem.hpp>
#include <boost/iostreams/categories.hpp>
#include <boost/algorithm/string.hpp>
#include <vector>
#include <fstream>
#include "GL/glu.h"
#include <glm/glm.hpp> 
#include <glm/gtc/type_ptr.hpp> 
#include <glm/gtc/matrix_transform.hpp> 
/////////////////////////////
using namespace std;
using namespace boost::filesystem;
namespace io = boost::iostreams;
namespace ex = boost::iostreams::example;
////////////////////////////

//for holding the string
typedef ex::container_source<string> string_source;
typedef std::vector< std::string > string_list;

struct Point {float x,  y , z; };
std::vector<int>faces;
std::vector<Point>points;
//Turn on FileReader
bool FileReader = false;

float
leftandright = 0.0,
             upanddown = 0.0,
             forwardandbackward = 0.0;
GLuint vertexbuffer;

///////////////////////////
//DEFINES
///////////////////////////
#define SCREEN_WIDTH  1240
#define SCREEN_HEIGHT 960
#define BPP 32
#define GL_GLEXT_PROTOTYPES
#define printOpenGLError() printOglError(__FILE__, __LINE__)

int printOglError(char *file, int line)
{

  GLenum glErr;
  int    retCode = 0;

  glErr = glGetError();
  if (glErr != GL_NO_ERROR)
  {
    printf("glError in file %s @ line %d: %s\n",
        file, line, gluErrorString(glErr));
    retCode = 1;
  }
  return retCode;
}

///////////////////////////

////////////////////////////
//Other Header Files
///////////////////////////
#include "SurfaceStorage.h"
#include "IndexAssignerFunction.h"
//////////////////////////

////////////////////////
//Global Variables
////////////////////////

/////////////////////////

bool MoveObjVertex = false;
bool modelloader = true;


int numfloats = 4;
float* point=reinterpret_cast<float*>(&points[0]);
int num_bytes=numfloats*sizeof(float);
GLuint matrixuniform;

GLuint vao[2];

GLuint translation;
GLuint xvalue = 600.0f;
GLuint objvbo;
GLuint objtextureID;
GLuint ModelTexture;



/////////////////
//EVENT CREATION
////////////////

//SDL Event
SDL_Event event;

int counter = 0;
int indexID;

float xm;
float ym;

int length;
char * buffer;


static GLfloat TranslationMatrix[] = {

  1.0, 0.0, 0.0, 0.0, 
  0.0, 1.0, 0.0, 0.0, 
  0.0, 0.0, 1.0, 0.0, 
  0.0, 0.0, 0.0, 1.0


};


//Obj Graphic
static const GLfloat ObjGraphicVertices[] = {
  //x      y        z
  200.0f,  720.0f,   0.0f,   1.0f,  1.0f,   0.0f, 
  320.0f,  720.0f,   0.0f,   1.0f,  0.0f,   0.0f, 
  320.0f,  640.0f,   0.0f,   1.0f,  0.0f,   1.0f, 
  200.0f,  640.0f,   0.0f,   1.0f,  1.0f,   1.0f  

};



////////////////////////

///////////////////////
//Structure Rectangle
//////////////////////

struct rectangle
{

  GLuint textureID;
  GLuint vbo;
  GLfloat matrix[16];

};

//////////////////////
//Rectangle Variables
/////////////////////

//////////////////////
rectangle menubar;
rectangle MainToolBox;
rectangle BoxStorage;
rectangle MenuContext;
rectangle boxstorage;
rectangle ObjGraphic;
//////////////////////

glm::mat4 ortho;

///////////////////////////////////
//Prototypes
//////////////////////////////////
void create_rectangle(const char * const path, rectangle *r, const GLfloat *vertices, size_t byte_count, const GLfloat * transformation);
void draw_rectangle(rectangle *r, const float *TranslationMatrix);
void render_rectangle(void);
void setup_sdl_glew (void);
GLuint LoadProgram(const char * const vertex_file_path, const char * Fragment_file_path);
int load_texture(const char * const path);
void draw (void);
/////////////////////////////////

//////////////////////////
//Setup Vbo Function
/////////////////////////
void setup_vbos(void)
{

  ///////////////////
  //Vertex Cordinates
  ///////////////////  

  ////////////////////////////
  //Graphical User Interface
  ///////////////////////////

  //1240 by 960


  //Start GUI Cordinates x y z w - NEEDS TEX CORDS
  static const GLfloat GUIVertices[] = { 

    //ToolBar
    1240.0f, 960.0f,  0.0f, 1.0f, 1.0f, 0.0f, 
    -1.0f,   960.0f,  0.0f, 1.0f, 0.0f, 0.0f, 
    -1.0f,   930.0f,  0.0f, 1.0f, 0.0f, 1.0f, 
    1240.0f, 930.0f,  0.0f, 1.0f, 1.0f, 1.0f

  };

  static const GLfloat MainToolBoxVertices[] = { 

    //Main ToolBox
    // x     y       z   w       X    Y
    1240.0f, 120.0f,   0.0f, 1.0f, 1.0f,  0.0f, 
    -1.0f,   120.0f,   0.0f, 1.0f, 0.0f,  0.0f, 
    -1.0f,    0.0f,    0.0f, 1.0f, 0.0f,  1.0f, 
    1240.0f,  0.0f,    0.0f, 1.0f, 1.0f,  1.0f  

  };

  //Menu Context
  static const GLfloat MenuContextVertices[] = {

    // x     y           z     w       X    Y
    -0.76f,   0.94f,   0.0f, 1.0f,   1.0f, 0.0f, 
    -1.10f, 0.94f,   0.0f,   1.0f,   0.0f, 0.0f, 
    -1.10f, 0.60f,   0.0f,   1.0f,   0.0f, 1.0f, 
    -0.76f,   0.60f,   0.0f, 1.0f,   1.0f, 1.0f  

  };

  //Box Storage
  static const GLfloat BoxStorageVertices[] = {

    1112.0f, 860.0f,   0.0f, 1.0f,        1.0f,  0.0f, 
    120.0f,   860.0f,   0.0f,   1.0f,     0.0f, 0.0f, 
    120.0f,   200.0f,   0.0f,   1.0f,    0.0f, 1.0f, 
    1112.0f, 200.0f,   0.0f, 1.0f,       1.0f,  1.0f  

  };

  ////////////////////////////////
  //Generate Vertex Array Object
  /////////////////////////////////

  glGenVertexArrays(1, &vao[0]);

  create_rectangle("GUI/FileMenu.tga", &menubar, GUIVertices, sizeof(GUIVertices), NULL);
  create_rectangle("GUI/MainToolBox.tga", &MainToolBox, MainToolBoxVertices, sizeof(MainToolBoxVertices), NULL);
  create_rectangle("GUI/MenuContext.tga", &MenuContext, MenuContextVertices, sizeof(MenuContextVertices), NULL);
  create_rectangle("GUI/BoxStorage.tga", &boxstorage, BoxStorageVertices, sizeof(BoxStorageVertices), NULL);
  create_rectangle("GUI/ObjGraphic.tga",  &ObjGraphic, ObjGraphicVertices,  sizeof(ObjGraphicVertices),  NULL);

}



//LOAD SHADER FUNCTION
GLuint LoadProgram(const char * const vertex_file_path, const char * Fragment_file_path){

  GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
  GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);

  //READ THE VERTEX SHADER CODE
  string VertexShaderCode;
  ifstream VertexShaderStream(vertex_file_path, std::ios::in);
  if(VertexShaderStream.is_open())
  {
    string Line = "";
    while(getline(VertexShaderStream, Line))
      VertexShaderCode += "\n" + Line;
    VertexShaderStream.close();   
  }

  //READ THE FRAGMENT SHADER CODE
  string FragmentShaderCode;
  ifstream FragmentShaderStream(Fragment_file_path, std::ios::in);
  if(FragmentShaderStream.is_open())
  {

    string Line = "";
    while(getline(FragmentShaderStream, Line))
      FragmentShaderCode += "\n" + Line;
    FragmentShaderStream.close();

  }

  GLint Result = GL_FALSE;
  int InfoLogLength;

  //Compile Vertex Shader
  printf("Compiling Shader : %s\n", vertex_file_path);
  char const * VertexSourcePointer = VertexShaderCode.c_str();
  glShaderSource(VertexShaderID,  1,  &VertexSourcePointer,  NULL);
  glCompileShader(VertexShaderID);

  //Check Vertex Shader
  glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS,  &Result);
  glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
  vector<char> VertexShaderErrorMessage(InfoLogLength);
  glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
  fprintf(stdout, "%s\n",  &VertexShaderErrorMessage[0]);

  //Compile Fragment Shader
  printf("Compiling Shader : %s\n",  Fragment_file_path);
  char const * FragmentSourcePointer = FragmentShaderCode.c_str();
  glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer, NULL);
  glCompileShader(FragmentShaderID);

  //Check Fragment Shader
  glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
  glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
  vector<char> FragmentShaderErrorMessage(InfoLogLength);
  glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
  fprintf(stdout,  "%s\n",  &FragmentShaderErrorMessage[0]);

  fprintf(stdout,  "Linking Program\n");
  GLuint ProgramID = glCreateProgram();

  //Bind Attribute
  glBindAttribLocation(ProgramID, 0, "position");
  glBindAttribLocation(ProgramID, 1, "Texcoord0");

  //Link The Program
  glAttachShader(ProgramID, VertexShaderID);
  glAttachShader(ProgramID, FragmentShaderID);
  glLinkProgram(ProgramID);

  GLuint texture1;


  texture1 = glGetUniformLocation(ProgramID, "myTextureSampler");
  matrixuniform =  glGetUniformLocation(ProgramID, "myMatrix");
  translation = glGetUniformLocation(ProgramID, "TranslationMatrix");


  //Check The Program
  glGetProgramiv(ProgramID,  GL_LINK_STATUS, &Result);
  glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
  vector<char> ProgramErrorMessage( max(InfoLogLength, int(1)) );
  fprintf(stdout,  "%s\n", &ProgramErrorMessage[0]);

  //Delete Shader
  glDeleteShader(VertexShaderID);
  glDeleteShader(FragmentShaderID);

  glUseProgram(ProgramID);

  //Return ProgramID
  return ProgramID;
}

/////////////////////////////////
//Create Rectangle Function
/////////////////////////////////
void create_rectangle(const char * const path, rectangle *r, const GLfloat *vertices, size_t byte_count, const GLfloat * transformation)
{

  glGenBuffers(1, &r->vbo);
  glBindBuffer(GL_ARRAY_BUFFER, r->vbo);
  glBufferData(GL_ARRAY_BUFFER, byte_count, vertices, GL_DYNAMIC_DRAW);
  r->textureID = load_texture(path);

}

void draw_rectangle(rectangle *r, const float *TranslationMatrix)
{

  /////////////////////////////////////////////////////
  //Object Graphic <Vertex Attributes/Bindings/Drawing>
  /////////////////////////////////////////////////////

  //Bind array buffer to Vertex Buffer Object
  glBindBuffer(GL_ARRAY_BUFFER, r->vbo);

  // "Bind" the newly created texture : all future texture functions will modify this texture
  glBindTexture(GL_TEXTURE_2D, r->textureID);

  //MenuContext Vertices cordinates
  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float),reinterpret_cast<const GLvoid *>(0 * sizeof(float)));

  //MenuContext Texture cordinates
  glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 6 * sizeof(float),reinterpret_cast<const GLvoid *>(4 * sizeof(float)));

  //Enable Attribute for "index 1"
  glEnableVertexAttribArray(0);

  //Enable Attribute for "index 1"
  glEnableVertexAttribArray(1);

  //Get Matrix Values/Matrix
  glUniformMatrix4fv(matrixuniform, 1, TRUE, r->matrix);

  glUniformMatrix4fv(matrixuniform, 1, GL_FALSE, glm::value_ptr(ortho));

  glUniformMatrix4fv(translation, 1, TRUE, TranslationMatrix);

  glDrawArrays(GL_QUADS, 0, 4);

  //END OF MOVE OBJ VERTEX

}

//Setup SDL/GLEW
void setup_sdl_glew (void)
{
  /////////////////////////////////////////////////////////////////////////////////////////////////

  //////////////////
  //SDL SETUP
  /////////////////

  //ENABLE SDL
  SDL_Init(SDL_INIT_EVERYTHING);

  //SCREEN POSITION
  SDL_putenv("SDL_VIDEO_CENTERED=center");

  //SCREEN SETUP
  Screen = SDL_SetVideoMode(SCREEN_WIDTH,SCREEN_HEIGHT,BPP,SDL_OPENGL|SDL_DOUBLEBUF);

  ortho = glm::ortho(0.0f, (float)SCREEN_WIDTH, 0.0f, (float)SCREEN_HEIGHT); 

  //////////////////
  //GLEW SETUP
  /////////////////

  //GLEW SETUP
  GLenum err = glewInit();
  glGetError();

  //GLEW ERROR CHECKING
  if (GLEW_OK != err)
  {
    /* Problem: GlewInit failed, something is seriously wrong. */
    fprintf(stderr, "Error: %s\n", glewGetErrorString(err));

  }
  fprintf(stdout, "Status: Using GLEW %s\n", glewGetString(GLEW_VERSION));
  /////////////////////////////////////////////////////////////////////////////////////    

}

///////////////////////
//ON AND OFF SWITCHES
//////////////////////

//ON AND OFF SWITCHES FOR APPLICATION
bool done = false;

bool MenuContextToggle = false;

bool BoxStorageToggle = false;

bool ObjGraphicToggle = false;

//Load Texture
int load_texture(const char * const path)
{

  GLuint textureID;

  SDL_Surface *surface = IMG_Load(path);

  // Create one OpenGL textureID
  glGenTextures(1, &textureID);

  // "Bind" the newly created textureID : all future textureID functions will modify r textureID
  glBindTexture(GL_TEXTURE_2D, textureID);

  // Give the image to OpenGL
  glTexImage2D(GL_TEXTURE_2D, 0,GL_RGBA, surface->w,surface->h, 0, GL_BGR, GL_UNSIGNED_BYTE, surface->pixels);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

  SDL_FreeSurface(surface);

  return textureID;
}

//Draw Function for arrays <VERTEX POSITION DRAWING>
void draw (void)
{
  draw_rectangle(&menubar, TranslationMatrix);
  draw_rectangle(&MainToolBox,  TranslationMatrix);

}




void ModelLoad(const char * path, int Index)
{

}


//Main Entry Point
int main(int argc , char *argv[])
{ 



  /////////////////
  //SETUP 
  ////////////////

  //SDL and Glew 
  setup_sdl_glew();

  /////////////////

  objsurface = IMG_Load("GUI/ObjGraphic.tga");
  ModelSurface = IMG_Load("GUI/Textures/model.tga");

  //Keyboard Setup
  Uint8 *keystate = SDL_GetKeyState(NULL);

  SDL_EnableKeyRepeat(100, SDL_DEFAULT_REPEAT_INTERVAL);

  //Setup Vertex Buffer Object
  setup_vbos();

  /////////////////////////////
  // OpenGL - Viewport Setup
  /////////////////////////////

  //Clear Color
  glClearColor(0.0,0.0,1.0,0.0);

  //View Port Setup
  glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

  ////////////////////////////////////////////////////////////////////////////////////////////////////

  ////////////////
  //Load Shaders
  ////////////////

  //Load Shader Program
  LoadProgram("graphicaluserinterface.v", "graphicaluserinterface.f");

  ////////////////////////////////////////////////////////////////////////////////////////////////////

  int Mouse_X, Mouse_Y;

  //Main While Loop
  while(!done)
  { 
    //Get the X and Y Positions on Mouse Cursor and put it into Storage
    SDL_GetMouseState(&Mouse_X,&Mouse_Y);


    //Clear
    glClear(GL_COLOR_BUFFER_BIT);

    //Logic

    //draw <Basic GUI>
    draw();

    if(BoxStorageToggle == true)
    {

      draw_rectangle(&boxstorage, TranslationMatrix);

      //////////////////////////////
      //Directory Reading Proccess
      /////////////////////////////

      //File Path 
      path p("Box Storage");

      //String vector container
      std::vector < std::string > list;

      ////////////////////////////

      //Iteration Loop
      try
      {  
        for (directory_iterator it(p); it != directory_iterator(); ++it)
        {

          //Output it contents to CMD window
          //   cout << *it << endl;

          //find the it path names store it in s
          string s = (*it).path().string();

          //then push back list
          list.push_back( s );

        }

      }

      catch (const filesystem_error& ex)
      {
        //  cout << ex.what() << endl;
      }

      //Float Containers
      float row=0,column=0;
      float quad_width = -146.0f , quad_height = -100.0f;

      //iteration Loop
      for(int i=0;i<list.size();++i)
      {
        //if list is not at its full capacity
        if(list[i].find(".obj") != std::string::npos)
        {
          //every 6th quad, change row
          if(i % 6 == 0)
          {
            //beginning of a row 
            column = 0;

            //row iteration
            ++row;
          }

          //Translation Process
          GLfloat TranslationMatrix[] = {
            1.0, 0.0, 0.0, 720.0+column*quad_width,
            0.0, 1.0, 0.0, 82.0+row*quad_height,
            0.0, 0.0, 1.0, 0.0,
            0.0, 0.0, 0.0, 1.0
          };

          //Send Translation Matrix up to the vertex shader
          glUniformMatrix4fv(translation, 1, TRUE, TranslationMatrix);

          //Draw Rectangle to frustum
          draw_rectangle(&ObjGraphic, TranslationMatrix);
        }

        //column itearation
        ++column;



        //Index Assigners
        if(event.type == SDL_MOUSEBUTTONDOWN && SDL_BUTTON(SDL_BUTTON_LEFT))
        { 
          //use Index Assigner
          int index = IndexAssigner(1, 1);

          //make a fileobject and store list and the index of that list in a c string
          ifstream file (list[index].c_str() );

          //Make another string
          //string line;

          points.push_back(Point());
          Point p;

          int face[4]; 
          //While FileObject not equal end of file
          while (!file.eof() ) 
          {


            char  modelbuffer[10000];

            //Get lines and store it in line string
            //getline(file, line);

            file.getline(modelbuffer, 10000);


            switch(modelbuffer[0])
            {

              case 'v' :

                sscanf(modelbuffer, "v %f %f %f",  &p.x, &p.y, &p.z);



                points.push_back(p);

                break;



              case 'f':
                sscanf(modelbuffer, "f %d %d %d %d", face, face+1, face+2,  face+3 );




                faces.push_back(face[0]);
                faces.push_back(face[1]);
                faces.push_back(face[2]);
                faces.push_back(face[3]);


            }

            //Turn on FileReader
            FileReader = true;


          }

        }

      }

    }


    //If File Reader is On
    if(FileReader == true)
    {




      glGenBuffers(1, &vertexbuffer);

      glGenTextures(1, &ModelTexture);

      glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);

      glBindTexture(GL_TEXTURE_3D, ModelTexture);

      glTexImage2D(GL_TEXTURE_2D, 0,GL_RGBA,  ModelSurface->w, ModelSurface->h, 0, GL_BGR, GL_UNSIGNED_BYTE, ModelSurface->pixels);

      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

      glBufferData(GL_ARRAY_BUFFER, sizeof(points), points.data(), GL_STATIC_DRAW);

      glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE,num_bytes ,points.data());

      glEnableVertexAttribArray(3);

      //Translation Process
      GLfloat TranslationMatrix[] = {
        1.0, 0.0, 0.0, 0.0, 
        0.0, 1.0, 0.0, 0.0, 
        0.0, 0.0, 1.0, 1.0,
        0.0, 0.0, 0.0, 1.0
      };

      //Send Translation Matrix up to the vertex shader
      glUniformMatrix4fv(translation, 1, TRUE, TranslationMatrix);


      //THIS IS FINE
      // glDrawElements(GL_QUADS, points.size(), GL_UNSIGNED_INT,  points.data());
      glDrawElements( GL_QUADS, faces.size(), GL_UNSIGNED_INT,  faces.data());


    }



    //Turn of Menu Context Toggle
    if(keystate[SDLK_SPACE])
    {

      MenuContextToggle = false;

    }

    //Turn on Box Storage
    if(keystate[SDLK_b])
    {

      BoxStorageToggle = true;

    }




    /////////////////////////////////////////////////////////////////

    ///////////////////////
    //Menu Context <SETUP>
    //////////////////////

    ///////////////////////////////////////////////////////////////

    //SDL EVENT QUEUE
    while(SDL_PollEvent(&event))
    {
      switch(event.type)
      {

        //if User Presses Quit Button
        case SDL_QUIT:
          return 0;
          break;    

          /////////////////////////////////////////////

          ////////////////
          //MOUSE EVENTS
          ///////////////

        case SDL_MOUSEBUTTONDOWN:

          if(Mouse_X <= 15, Mouse_Y <= 30)
          {

            MenuContextToggle = true;

          }

          ///////////////////////////////////////////////

      }

    }
    //Update
    SDL_GL_SwapBuffers(); 
  }

}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////





