//============================================================================
// skybox example program
// (C) 1999,2004   Bill Baxter
//   This code renders a skybox and optionally a ground plane using OpenGL, 
//   with no reliance on cube mapping hardware.  It should work on any 
//   OpenGL implementation at all as long as it can render textures.
//
//   I ripped this out of a much larger program of mine, and haven't actually
//   gotten it to compile by itself, but all the code you need is in here.
//============================================================================
// This code relies on some simple functionality in GLVU
//   http://www.cs.unc.edu/~walk/software/glvu,
// but you could easily replace the GLVU stuff with straight OpenGL and 
// other utility libraries.
// GLVU is only used for setting up the view matrix and for some image
// loading.
//============================================================================
// Permission to use, copy, modify, distribute and sell this software
// and its documentation for any purpose is hereby granted without
// fee, provided that the above copyright notice appear in all copies
// and that both that copyright notice and this permission notice
// appear in supporting documentation.  Binaries may be compiled with
// this software without any royalties or restrictions.
//
// The University of North Carolina at Chapel Hill makes no representations 
// about the suitability of this software for any purpose. It is provided 
// "as is" without express or implied warranty.
//============================================================================
// INCLUDES
#include <string>
using std::string;
#include <iostream>
#include <glvu.hpp>
#include <tga.hpp>
#include <text.hpp>
#include <thread.h>
#include <fileutils.h>
#include <quat.hpp>
#include <ppm.hpp>
#include <tga.hpp>
#include <vec3fv.hpp>
#include "skybox.h"

int MAIN_RES_X = 1024;
int MAIN_RES_Y = 1024;
int MAIN_POS_X = 530;
int MAIN_POS_Y = 50;

enum { UP_VECTOR_X, UP_VECTOR_Y, UP_VECTOR_Z };

enum { BKGND_SKYBOX, BKGND_BLACK, BKGND_WHITE, BKGND_BLUE, BKGND_NUM_MODES };
int g_backgroundMode = BKGND_BLACK;
bool g_drawGroundPlane = false;
unsigned int g_skyTexID[] = {0,0,0,0,0,0};
SimpImg g_skyImageData[] = {{0},{0},{0},{0},{0},{0}};



void drawAppSkyBox(Camera *cam, const Vec3f& upVec)
{
  bool lighting = glIsEnabled(GL_LIGHTING);
  if (lighting) glDisable(GL_LIGHTING);
  glClear( GL_DEPTH_BUFFER_BIT );
  glDepthMask(GL_FALSE);
  glDisable(GL_DEPTH_TEST);

  if (g_skyTexID[0] == 0) {
    // load textures
    UploadSkyTextures(g_skyTexID, g_skyImageData);
  }
  // fix the modelview matrix so that world center is 0,0,0
  float M[16];
  float VP[4];
  glGetFloatv(GL_VIEWPORT, VP);
  {
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    // all I really want to do here is modify the near/far dists from cam
    glLoadIdentity();
    //glLoadMatrixf( cam->GetProjectionMatrix(M) );
    const float near = 0.5f, far = 2.0f;
    float cvt = near / cam->Near;
    glFrustum(cam->wL*cvt, cam->wR*cvt,
              cam->wB*cvt, cam->wT*cvt,
              near, far);
    //gluPerspective( 45, VP[2]/VP[3], 0.5, 2.0 );
  }

  {
    glMatrixMode(GL_MODELVIEW);
    Viewing16fv(M, cam->X, cam->Y, cam->Z, Vec3f::ZERO);
    glLoadMatrixf( M );

    // skybox draws with y_up by default, fix if that's not the case here
    if (upVec.z > 0.5) {
      glRotatef( 90, 1,0,0);   // this no doubt makes lots of trig calls...
                               // Turn this into a hard-coded matrix when
                               // you have time.
    } else if (upVec.x > 0.5) {
      glRotatef(-90, 0,0,1);
    }
  }

  glEnable(GL_TEXTURE_2D);
  DrawSkyBox(g_skyTexID);
  GLerror("Error after drawing skybox");

  glDisable(GL_TEXTURE_2D);
  glDepthMask(GL_TRUE);
  glEnable(GL_DEPTH_TEST);
  if (lighting) glEnable(GL_LIGHTING);
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
}

void drawAppGroundPlane(const Vec3f& vup)
{
  // Draw one quad on the ground with ground texture so model doesn't
  // seem to float in space.
  // ... doesn't work right yet... and the results really don't need to 
  // be computed every frame... once outght to be enough.

  Vec3f ModelMin(g_tree.Root->Min), ModelMax(g_tree.Root->Max);
  Vec3f ModelCtr(ModelMin); ModelCtr+=ModelMax; ModelCtr*=0.5;
  Vec3f ModelDiam(ModelMax);ModelDiam-=ModelMin;
  ModelCtr -= vup * ( (vup * ModelDiam) * 0.5 ); // make it bottom center
  float mm = std::max(ModelDiam.x, ModelDiam.y);
  mm = std::max(mm, ModelDiam.z);
  ModelDiam.Set(mm,mm,mm);
  ModelDiam -= vup * ( (vup * ModelDiam) ); // make it bottom diag
  glPushMatrix();

  glTranslatef(ModelCtr.x, ModelCtr.y, ModelCtr.z);
  ModelDiam*=1.5;
  glScalef(ModelDiam.x,ModelDiam.y,ModelDiam.z);
  //glTranslatef(-ModelCtr.x/ModelDiam.x, -ModelCtr.y/ModelDiam.y, -ModelCtr.z/ModelDiam.z);
  // skybox draws with y_up by default, fix if that's not the case here

  if (vup.z > 0.5) {
    glRotatef( 90, 1,0,0);   // this no doubt makes lots of trig calls...
    // Turn this into a hard-coded matrix when
    // you have time.
  } else if (vup.x > 0.5) {
    glRotatef(-90, 0,0,1);
  }

  /*
  const GLfloat v[4][3] = 
    { { 1,-1, 1 },
      { 1,-1,-1 },
      {-1,-1,-1 },
      {-1,-1, 1 }
    };
  */
  const GLfloat v[][3] = 
    { { 1,-1, 1 },
      { 1,-1,-1 },
      {-1,-1,-1 },
      {-1,-1, 1 }
      /*
      ,
      { 1.4,-1.4, 1.4 },
      { 1.4,-1.4,-1.4 },
      {-1.4,-1.4,-1.4 },
      {-1.4,-1.4, 1.4 }
      */
    };


  bool lighting = glIsEnabled(GL_LIGHTING);
  if (lighting) glDisable(GL_LIGHTING);

  glEnable(GL_TEXTURE_2D);
  // glEnable(GL_BLEND);
  // glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
  if (g_skyTexID[0] == 0) {
    BindSkyTextures(g_skyTexID, g_skyImageData);
  }
  glBindTexture(GL_TEXTURE_2D, g_skyTexID[SKY_DOWN]);
  glBegin(GL_QUADS);
  {
    //glColor4f(1.0,1.0,1.0,1.0);
    glTexCoord2f(0,0);  glVertex3fv(&v[0][0]);
    glTexCoord2f(1,0);  glVertex3fv(&v[1][0]);
    glTexCoord2f(1,1);  glVertex3fv(&v[2][0]);
    glTexCoord2f(0,1);  glVertex3fv(&v[3][0]);

    /*
    glColor4f(1.0,1.0,1.0,1.0);
    glTexCoord2f(1,0);  glVertex3fv(&v[1][0]);
    glTexCoord2f(0,0);  glVertex3fv(&v[0][0]);
    glColor4f(1.0,1.0,1.0,0.0);
    glTexCoord2f(-.4,-.4);  glVertex3fv(&v[4][0]);
    glTexCoord2f(0.4,-.4);  glVertex3fv(&v[5][0]);

    glColor4f(1.0,1.0,1.0,1.0);
    glTexCoord2f(1,1);  glVertex3fv(&v[2][0]);
    glTexCoord2f(1,0);  glVertex3fv(&v[1][0]);
    glColor4f(1.0,1.0,1.0,0.0);
    glTexCoord2f(0.4,-.4);  glVertex3fv(&v[5][0]);
    glTexCoord2f(0.4,0.4);  glVertex3fv(&v[6][0]);

    glColor4f(1.0,1.0,1.0,1.0);
    glTexCoord2f(0,1);  glVertex3fv(&v[3][0]);
    glTexCoord2f(1,1);  glVertex3fv(&v[2][0]);
    glColor4f(1.0,1.0,1.0,0.0);
    glTexCoord2f(0.4,0.4);  glVertex3fv(&v[6][0]);
    glTexCoord2f(-.4,0.4);  glVertex3fv(&v[7][0]);

    glColor4f(1.0,1.0,1.0,1.0);
    glTexCoord2f(0,0);  glVertex3fv(&v[0][0]);
    glTexCoord2f(0,1);  glVertex3fv(&v[3][0]);
    glColor4f(1.0,1.0,1.0,0.0);
    glTexCoord2f(-.4,0.4);  glVertex3fv(&v[7][0]);
    glTexCoord2f(-.4,-.4);  glVertex3fv(&v[4][0]);
    */
  }
  glEnd();
  glDisable(GL_TEXTURE_2D);
  glDisable(GL_BLEND);
  /*
  glBegin(GL_LINES);
  glColor3ub(255,0,0); glVertex3f(0,0,0); glVertex3f(1000,0,0);
  glColor3ub(0,255,0); glVertex3f(0,0,0); glVertex3f(0,1000,0);
  glColor3ub(0,0,255); glVertex3f(0,0,0); glVertex3f(0,0,1000);
  glEnd();
  */
  if (lighting) glEnable(GL_LIGHTING);
  glPopMatrix();
  GLerror("After drawing ground plane");
}

//----------------------------------------------------------------------------
// @ placeSceneFixedLights
//----------------------------------------------------------------------------
void placeSceneFixedLights()
{
  for (int i=0; i<8; i++) 
  {
    if (g_lightStatus[i]) {
      glLightfv(GL_LIGHT0+i, GL_POSITION, g_lightPositions[i]);
    }
  }
  GLerror("Error after placing lights");
}



//----------------------------------------------------------------------------
// @ mainDisplayFunc
//----------------------------------------------------------------------------
void mainDisplayFunc( )
{
  //  g_glvu.BeginFrame();
  static int frame_number = 0;
  static unsigned int lastNumPolys;  // for display list mode
  static unsigned int lastNumObjs;   // for display list mode
  static Stopwatch elapsed_time;
  if (frame_number == 0)
  {
    elapsed_time.Start();
  }
  GLfloat M[16];
  unsigned int numPolys = 0, numObjs = 0;
  unsigned int trueVisibility = 0;

  int polyState[2];
  bool doSkyBox =
    (g_backgroundMode == BKGND_SKYBOX) && !outOfBodyCam && 
    polyState[0]==GL_FILL && !itemBuffer;

  int WW = glutGet(GLUT_WINDOW_WIDTH);
  int WH = glutGet(GLUT_WINDOW_HEIGHT);

  Camera cam;
  //------------------------------------------
    cam = *g_glvu.GetCurrentCam();

    if (doSkyBox) {
      drawAppSkyBox(&cam, g_glvu.GetViewUp());
    } else {
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    }

    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf( cam.GetProjectionMatrix(M) );
    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixf( cam.GetModelviewMatrix(M) );

    placeSceneFixedLights();
    if (doSkyBox && g_drawGroundPlane) {
      drawAppGroundPlane(g_glvu.GetViewUp());
    }


    // << RENDER YOUR SCENE HERE !!! >>

  //------------------------------------------

  GLerror("Error after main display");

  glutSwapBuffers();
}



//----------------------------------------------------------------------------
// @ SetupMainWindow
//----------------------------------------------------------------------------
void SetupMainWindow( bool doLighting, bool doMultisample, int upVec=2 )
{
  g_glvu.Init("Skybox",
              GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA | 
              (doMultisample?GLUT_MULTISAMPLE:0),
              MAIN_POS_X, MAIN_POS_Y, MAIN_RES_X,MAIN_RES_Y);
  g_glvu.StartFPSClock();

  if ( doLighting ) {
    glEnable(GL_LIGHTING); // (if model is not pre-lit)
  } else {
    glDisable(GL_LIGHTING); // model is pre-lit
  }
  glEnable(GL_LIGHT0);
  glEnable(GL_COLOR_MATERIAL);
  glEnable(GL_DEPTH_TEST);

  GLfloat light0_specular[] = { 1.0,1.0,1.0,1.0 };
  glLightfv(GL_LIGHT0, GL_SPECULAR, light0_specular);

  GLfloat light_diff[] = { 0.4, 0.4, 0.4, 1.0 };
  GLfloat light_spec[] = { 1.0, 1.0, 1.0, 1.0 };
  for (int i=1; i<7; i++) {
    glLightfv(GL_LIGHT0+i, GL_DIFFUSE, light_diff);
    glLightfv(GL_LIGHT0+i, GL_SPECULAR, light_spec);
  }

  GLfloat mat_specular[] = { 0.2,0.2,0.2,1.0 };
  GLfloat mat_shiny[] = {100.0};
  glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_specular);
  glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, mat_shiny);

  // SETUP FOG
  GLfloat fogColor[] = { 1.0,1.0,1.0,1.0 };
  glFogi(GL_FOG_MODE, GL_EXP);
  glFogfv(GL_FOG_COLOR, fogColor);
  glFogf(GL_FOG_DENSITY, 0.05);
  glHint(GL_FOG_HINT, GL_DONT_CARE);

  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
  glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);

  // CVA Setup
  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_COLOR_ARRAY);
  //  glEnable(GL_NORMAL_ARRAY);

  GLerror("Error after mainwin GL setup");

  glutDisplayFunc(mainDisplayFunc);
  // glutMouseFunc(appMouseFunc);
  //glutMotionFunc(appMotionFunc);
  //glutPassiveMotionFunc(appPassiveMotionFunc);
  //glutKeyboardFunc(appKeyboardFunc);
  //glutSpecialFunc(appKeyboardSpecialFunc);
  //glutIdleFunc(appIdleFunc);
  //glutReshapeFunc(appReshapeFunc);
  //glutMenuStatusFunc(MenuHandler::appMenuStatusFunc);
  //glutKeyboardUpFunc(appKeyboardUpFunc);
  //glutSpecialUpFunc(appSpecialKeyUpFunc);
  //glutIgnoreKeyRepeat(GLUT_DEVICE_IGNORE_KEY_REPEAT);

  // SETUP INITIAL CAMERA
  Vec3f ModelMin(g_tree.Root->Min), ModelMax(g_tree.Root->Max);
  Vec3f LookAtCntr((ModelMin + ModelMax) * 0.5);
  Vec3f Up;
  Vec3f Eye;
  if (upVec > 2) upVec = 2;
  if (upVec < 0) upVec = 0;
  if (upVec == 1) {
    Up.Set(0,1,0);
    Eye.Set((ModelMin.x+ModelMax.x)*0.5,
            (ModelMin.y+ModelMax.y)*0.5,
            ModelMin.z);
    Eye.z -= (LookAtCntr.z - ModelMin.z) * 1.1f ;
  }
  else {
    Up.Set(0,0,1);
    Eye.Set((ModelMin.x+ModelMax.x)*0.5,
            ModelMin.y,
            (ModelMin.z+ModelMax.z)*0.5);
    Eye.y -= (LookAtCntr.y - ModelMin.y) * 1.1f ;
  }
  
  float Yfov = 45;
  float Aspect = 1;  // WIDTH OVER HEIGHT
  float Near = 0.01f; // NEAR PLANE DISTANCE RELATIVE TO MODEL DIAGONAL LENGTH
  float Far = 10.0f; // FAR PLANE DISTANCE (ALSO RELATIVE)
  g_glvu.SetAllCams(ModelMin, ModelMax, 
                    Eye, LookAtCntr, Up, 
                    Yfov, Aspect, Near, Far);
}





//----------------------------------------------------------------------------
// @ main
//----------------------------------------------------------------------------
int main(int argc, char *argv[])
{
  // usage:  
  // args :
  // -skybox [shell_file_glob] (shell wildcard pattern for skybox .tga files)"

  ArgList argl;
  ArgList filel;
  for (int i=1; i<argc; i++) {
    argl.push_back(argv[i]);
  }
  // look for various recognized args
  ArgList::iterator it = argl.begin();
  int upVec = UP_VECTOR_Z; // z up by default
  bool doLighting = false;
  bool doMultisample = true;
  for(; it != argl.end(); it++) {
    if ( *it == "-skybox" )
    {
      ++it;
      cout << "Loading set of sky textures files: " << *it << endl;
      char* fname;
      int itex = 0;
      //char *prev_dir = GetDir();
      //if (ChDir(argv[a])) {
        FileFinder FF;
        FF.NewSearch(it->c_str(), false);
        while ( (fname = FF.Search()) ) {
          cout << "-- Found " << fname << endl;
          SimpImg &img = g_skyImageData[itex];
          int chan;
          LoadTGA(fname, img.data, img.W, img.H, chan);
          assert(chan == 3);
          delete [] fname;
          itex++;
        }
        if (itex == 6) {
          g_backgroundMode = BKGND_SKYBOX;
          g_drawGroundPlane = true;
        }
        else {
          cout << "Not enough textures for a sky box" << endl;
        }
        //}
        //ChDir(prev_dir);
        //delete [] prev_dir;
    }
  }
  
  SetupMainWindow( doLighting, doMultisample, upVec );

  glutMainLoop();
}

