#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <cstdlib>

#include <GL/gl.h>
#include <GL/glut.h>

#include <sh/shutil.hpp>

#include "Noise.hpp"

using namespace SH;
using namespace ShUtil;

NoiseShader::NoiseShader() {
}
void NoiseShader::makePhong() {
  phong = ShKernelLib::shPhong<ShColor3f>(); 
  phong = phong << coeffs << (bander & noise) << shDup(); 
}

SimpleWoodShader::SimpleWoodShader()
  : diffuseIn(0.35f, 0.177f, 0.07f), diffuseOut(0.8f, 0.3f, 0.1f),
    specularIn(0.3f, 0.3f, 0.3f), specularOut(1.0f, 1.0f, 1.0f) { 
  bandFreq = ShConstant3f(10.0f, 10.0f, 10.0f);
  noiseFreq = ShConstant3f(15.0f, 15.0f, 1.0f);
  noiseScale = ShConstant1f(0.3f);
  exp = ShConstant1f(40.0f);
  name = "Simple Wood";
  bander = SH_BEGIN_PROGRAM() {
    ShInputPoint3f SH_DECL(pm);

    pm *= bandFreq;
    ShOutputAttrib1f SH_DECL(inband) = sqrt(pm(0,1) | pm(0,1));  
  } SH_END;

  noise = SH_BEGIN_PROGRAM() {
    ShInputPoint3f SH_DECL(pm);
    ShOutputAttrib1f SH_DECL(noise) = noiseScale * snoise<1>(pm * noiseFreq);
  } SH_END;


  coeffs = SH_BEGIN_PROGRAM() {
    ShInputAttrib1f SH_DECL(inband);
    ShInputAttrib1f SH_DECL(noise);
    ShOutputColor3f SH_DECL(kd);
    ShOutputColor3f SH_DECL(ks);
    ShOutputAttrib1f SH_DECL(specExp);
    inband = frac(inband + noise);
    kd = lerp(inband, diffuseIn, diffuseOut); 
    ks = lerp(inband, specularIn, specularOut);
    specExp = exp; 
  } SH_END;

  makePhong();
}

ComplexWoodShader::ComplexWoodShader()
  : SimpleWoodShader() {
  octaveAmps = ShConstant3f(0.5f, 0.25f, 0.125f);
  name = "Complex Wood";
  noise = SH_BEGIN_PROGRAM() {
    ShInputPoint3f SH_DECL(pm);
    ShOutputAttrib1f SH_DECL(noise) = noiseScale * (sturbulence<1>(octaveAmps, pm * noiseFreq)
      + snoise<1>(10.0*sqrt(pm(0,1)|pm(0,1))) );
  } SH_END;

  makePhong();
}

MarbleShader::MarbleShader()   
  : octaveAmps(0.4f, 0.3f, 0.3f) {
  bandFreq = ShConstant3f(5.0f, 5.0f, 5.0f);
  noiseFreq = bandFreq * 2.0f;
  noiseScale = ShConstant1f(0.8f);
  exp = ShConstant1f(40.0f);
  name = "Marble";
  bander = SH_BEGIN_PROGRAM() {
    ShInputPoint3f SH_DECL(pm);

    pm *= bandFreq;
    ShOutputAttrib1f SH_DECL(inband) = pm(0); 
  } SH_END;

  noise = SH_BEGIN_PROGRAM() {
    ShInputPoint3f SH_DECL(pm);
    ShOutputAttrib1f SH_DECL(noise) = noiseScale * (sturbulence<1>(octaveAmps, pm * noiseFreq)
      +  snoise<1>(10.0*pm(0)));
  } SH_END;


  coeffs = SH_BEGIN_PROGRAM() {
    ShInputAttrib1f SH_DECL(inband);
    ShInputAttrib1f SH_DECL(noise);
    ShOutputColor3f SH_DECL(kd);
    ShOutputColor3f SH_DECL(ks);
    ShOutputAttrib1f SH_DECL(specExp);
    inband = frac(inband + noise);
    ShColor3f bandColor = lerp(smoothstep(ShConstant1f(0.3f), ShConstant1f(0.4f), inband), 
        ShConstant3f(0.0f, 0.2f, 0.0f), ShConstant3f(0.4f,0.4f,0.75f));
    inband = smoothstep(ShConstant1f(0.1f), ShConstant1f(0.3f), inband) 
      - smoothstep(ShConstant1f(0.5f), ShConstant1f(0.6f), inband); // black band
    kd = lerp(inband, bandColor, ShConstant3f(1.0f, 0.95f, 1.0f));
    ks = ShConstant3f( 0.5f, 0.5f, 0.5f ); 
    specExp = exp; 
  } SH_END;

  makePhong();
}

Noise::Noise(void) { 
  obj = 0;
  lightColour = ShColor3f(1.0, 1.0, 1.0);
  light = ShPoint3f(5.0, 5.0, 5.0); 
  shader = new SimpleWoodShader();
  displayList = -1;
  bandTransform = false;

  camera.move(0,0,-4);
}

Noise::~Noise(void) {
  if(obj) delete obj;
}


void Noise::init(int argc, char *argv[]) {
  Demo::init(argc, argv);
  
  // Load the molecule definition from a file.
  std::string filename = "";
 
  if (argc >= 2) {
    filename = argv[1];
  
    std::ifstream file(filename.c_str());
    if (!file) {
      std::cerr << "Error loading obj file from " << filename << std::endl;
    } else {
      obj = new ShObjFile;
      file >> (*obj);
    }
  }

  compileNoise(); 
}

void Noise::keyboard(unsigned char k, int x, int y) {
  bool recompile = false;
  switch(k) {
    case '?':
      std::cout << "Usage:" << std::endl;
      std::cout << "m    Marble shader" << std::endl;
      std::cout << "w    Oak shader (smoothstepped rings, 3 octave turbulence)" << std::endl;
      std::cout << "o    Simple oak shader (lerped rings, perlin noise)" << std::endl;
      std::cout << "z    toggle transforming the bands rather than the model" << std::endl;
      std::cout << "B/b  inc/dec band frequency" << std::endl;
      std::cout << "F/f  inc/dec noise frequency" << std::endl;
      std::cout << "N/n  inc/dec noise scaling" << std::endl;
      return;

    case 'm': ShEnvironment::boundShaders().clear();
              shader = new MarbleShader();
              recompile = true;
              break;
    case 'w': ShEnvironment::boundShaders().clear();
              shader = new ComplexWoodShader(); 
              recompile = true;
              break; 
    case 'o': ShEnvironment::boundShaders().clear();
              shader = new SimpleWoodShader(); 
              recompile = true;
              break;
    case 'z': bandTransform = !bandTransform; break;
    case 'B': if( shader ) shader->bandFreq *= 1.2f; break;
    case 'b': if( shader ) shader->bandFreq *= 1.0f / 1.2f; break; 
    case 'F': if( shader ) shader->noiseFreq *= 1.2f; break; 
    case 'f': if( shader ) shader->noiseFreq *= 1.0f / 1.2f; break; 
    case 'N': if( shader ) shader->noiseScale += 0.2f; break;
    case 'n': if( shader ) shader->noiseScale -= 0.2f; break;
    case 'q':
      exit(0);
    default:
      return;
  }

  
  if(recompile) {
    compileNoise(); 
  }

  printStatus();

  glutPostRedisplay();
}

void Noise::printStatus() {
  std::cout << "Current Noise Shader: " << shader->name << std::endl; 
  std::cout << "Mouse Mode: " << ( bandTransform ? "Move Texture Bands" : "Move Camera") << std::endl;
  std::cout << "Band Frequency: (" << shader->bandFreq.node()->getValue(0) 
            << ", " << shader->bandFreq.node()->getValue(1) 
            << ", " << shader->bandFreq.node()->getValue(2)  << ")" << std::endl;
  std::cout << "Noise Frequency: (" << shader->noiseFreq.node()->getValue(0) 
            << ", " << shader->noiseFreq.node()->getValue(1) 
            << ", " << shader->noiseFreq.node()->getValue(2)  << ")" << std::endl;
  std::cout << "Noise Scaling: " << shader->noiseScale.node()->getValue(0) << std::endl;
  std::cout << std::endl;
}

void Noise::motion(int x, int y) {
  Camera &modcam = bandTransform ? bandCamera : camera;
  if (buttons[GLUT_LEFT_BUTTON] == GLUT_DOWN) {
    modcam.orbit(cur_x, cur_y, x, y, m_width, m_height);
  }
  if (buttons[GLUT_MIDDLE_BUTTON] == GLUT_DOWN) {
    modcam.move(0, 0, (y - cur_y)/20.0);
  }
  if (buttons[GLUT_RIGHT_BUTTON] == GLUT_DOWN) {
    modcam.move((x - cur_x)/20.0, (cur_y - y)/20.0, 0);
  }

  cur_x = x;
  cur_y = y;

  glutPostRedisplay();
}

void Noise::compileNoise()
{
  printf("Compiling Noise");
  ShEnvironment::boundShaders().clear();
  ShProgram specialVsh = ShKernelLib::shVsh(mv, mvp) >> shRange("normal","lightVec")("posh"); 
  ShProgram vsh = shExtract("pm")  
    << (specialVsh & ( transform<ShPoint3f>(bandTrans,"pm") << cast<ShPoint4f,ShPoint3f>("pm"))) 
    << (shKeep(3) & shDup());
  vsh = vsh << shExtract("lightPos") << light; 


  //ShProgram fsh;
  ShProgram fsh = shader->phong;
  shBindShader(vsh); 
  shBindShader(fsh); 
}

void Noise::idle() {
  glutPostRedisplay();
}

void Noise::display()
{
  // Render the object 
  ArbRenderer* arbrend = dynamic_cast<ArbRenderer*>(renderer);
  bool usingArb = ( arbrend != 0 );
  bool arbInit = (usingArb && displayList == -1); 

  mv = camera.shModelView();
  mvp = camera.shModelViewProjection(ShMatrix4x4f());
  bandTrans = bandCamera.shModelView(); 

  if( arbInit || !usingArb ) { // draw it out
    if( arbInit ) { 
      displayList = arbrend->startDisplayList();
    }
    if( !obj ) {
      glDisable(GL_CULL_FACE);
      glFrontFace(GL_CW);
      glutSolidTeapot(2.0);
      glFrontFace(GL_CCW);
    } else {
      renderer->begin( SH_TRIANGLES );
      for( int i = 0; i < obj->faces.size(); i++ ) {
        for ( int j = 0; j < 3; j++ ) {
          ShTexCoord2f texc = obj->texcoords[obj->faces[i].texcoords[j]];
          ShNormal3f nrm = obj->normals[obj->faces[i].normals[j]];
          ShVector3f tngt1 = obj->tangents[obj->faces[i].tangents[j]];
          ShVector3f tngt2 = cross( nrm, tngt1 );
          ShPoint3f vtx = obj->vertices[obj->faces[i].points[j]];

          renderer->normal( nrm );
          renderer->texcoord( 0, texc );
          renderer->texcoord( 1, tngt1 );
          renderer->texcoord( 2, -tngt2 );
          renderer->vertex( vtx );
        }
      }
      renderer->end();
    }
    if( arbInit ) { 
      arbrend->endDisplayList();
    }
  }
  if( usingArb ) { 
    arbrend->callDisplayList( displayList );
  }
}

