#include "WorleyShader.hpp"
#include <fstream>
#include "Globals.hpp"

using namespace SH;
using namespace ShUtil;

#define WORLEY_FREQ 8

WorleyShader::WorleyShader(bool useTexture, int whichW, ShWorleyMetric metric):
  worleyizer( WORLEY_FREQ, useTexture ), useTexture( useTexture ), metric( metric ), whichW(whichW)
{
}

WorleyShader::~WorleyShader()
{
}

char* WorleyShader::name(void) {
  char name[100];
  if( useTexture ) {
    sprintf (name, "Worley (Texture hash) %d", whichW);
  } else {
    sprintf (name, "Worley (Procedural hash) %d", whichW);
  }
  return strdup(name);
}

ShAttrib4f WorleyShader::getWorley (int i, ShTexCoord2f t)
{
  ShAttrib4f w, z;
  ShAttrib4f wC;
  ShAttrib1f p;

  p = Globals::param * ShAttrib1f(0.1);
  switch (i)
    {
    case 1:
      wC = ShAttrib4f(1,0,0,0);
      w(2) = worleyizer.worleyNoGradient(t, wC, L2_SQ);
      w(2) = (ShAttrib1f(1)-w(2) - p)*ShAttrib1f(30);
      w(2) = clamp(0, 1, w(2));
      w(0,1) = w(2,2);
      break;
    case 2:
      wC = ShAttrib4f (-1,1.2,0,0);
      w(0,1,2) = worleyizer.worleyNoGradient(t, wC, L1)(0,0,0);
      wC = ShAttrib4f (0,1,1,0);
      z(0,1,2) = worleyizer.worleyNoGradient(t*ShAttrib1f(2), wC, L2_SQ)(0,0,0);
      w = w-z;
      break;
    case 3:
      wC = ShAttrib4f (-1,1,0,0);
      w(2) = worleyizer.worleyNoGradient(t, wC, L1);
      w(2) = (ShAttrib1f(1)-w(2) - p)*ShAttrib1f(30);
      //w(2) = clamp(0, 1, w(2));

      wC = ShAttrib4f (0,-1,1,0);
      w(1) = worleyizer.worleyNoGradient(t, wC, L1);
      w(1) = (ShAttrib1f(1)-w(1) - p)*ShAttrib1f(10);
      //w(1) = clamp(0, 1, w(1));

      w(2) = clamp(0, 1, w(1)+w(2));
      w(0,1) = w(2,2);
      break;
    case 4:
      wC = ShAttrib4f (0,0,0,1);
      w(2) = worleyizer.worleyNoGradient(t, wC, L1) * ShAttrib1f(0.5);
      w(2) = (ShAttrib1f(1)-w(2) - p)*ShAttrib1f(30);
      w(2) = clamp(0, 1, w(2));

      w(0) = worleyizer.worleyNoGradient(t*ShAttrib1f(2), wC, L1) * ShAttrib1f(0.5);
      w(0) = (ShAttrib1f(1)-w(0) - p)*ShAttrib1f(30);
      w(0) = clamp(0, 1, w(0));
      w(1) = ShAttrib1f(1) - w(0);
      break;
    case 0:
    default:
      wC = ShAttrib4f(1,0,0,0);
      w(0,1,2) = worleyizer.worley(t*ShAttrib1f(2), wC, L2_SQ);
      break;
    }
  w(3) = ShAttrib1f(1) - w(2);
  return w;
}

void WorleyShader::init()
{
  ShVector3f lightDir = normalize(Globals::lightPos - ShVector3f(0,0,0));

  vsh = SH_BEGIN_VERTEX_PROGRAM {
    ShInputNormal3f normal;
    ShInputTexCoord2f texture;
    ShInputPosition4f p;

    ShOutputVector3f ohv;
    ShOutputNormal3f on;
    ShOutputTexCoord2f ot;
    ShOutputAttrib1f ec;    //irradiance
    ShOutputPosition4f opd;

    //Used for lighting
    ShNormal3f nvt = normalize(Globals::mv | normal);
    ShPoint3f pvt = (Globals::mv | p)(0,1,2);
    ShVector3f vvv = -normalize(pvt);
    ShVector3f lvv = normalize(Globals::lightPos - pvt);
    ShAttrib1f ect = (nvt | lvv);

    opd = Globals::mvp | p;
    on = nvt;
    ohv = normalize(lvv + vvv);
    ot = texture;
    ec = ect * (ect>0.0);

  } SH_END_PROGRAM;

  fsh = SH_BEGIN_FRAGMENT_PROGRAM {
    ShInputVector3f hv;
    ShInputNormal3f n;
    ShInputTexCoord2f t;
    ShInputAttrib1f ec;
    ShInputPosition4f p;

    ShOutputColor3f out;

    ShAttrib4f w = getWorley (whichW, t);

    ShAttrib1f spec;
    spec = dot(normalize(hv), normalize(n)) ^ Globals::exponent;

    ShColor3f col1;
    ShColor3f col2;

    switch (whichW)
    {
    case 0:
      col1 = ShColor3f(0.7,0.2,0.3)*w(3) + ShColor3f(0.4,0,0)*w(2);
      //col1 = w(2,3,2);
      spec*= w(3);
      break;
    case 1:
      col1 = ShColor3f(0.27,0.35,0.45)*w(3) + ShColor3f(1,0.7,0)*w(2);
      break;
    case 2:
      col1 = ShColor3f(0.5,0,0)*w(3) + ShColor3f(0,0,1)*w(2);
      break;
    case 3:
      col1 = ShColor3f(0.45,0.3,0)*w(3) + ShColor3f(1,0.95,0.8)*w(2);
      spec*= 0.2;
      break;
    case 4:
      //col1 = ShColor3f(0,0.45,0)*w(0) + ShColor3f(0,0.3,0.8)*w(2);
      col1 = ShColor3f(0,0.2,0.8)*w(0) + ShColor3f(0.6,0.6,0.7)*w(2);
      spec*= col1(1);
      break;
    case 5:
      col2(0) = (w(0)>ShAttrib1f(0));
      col1 = (ShColor3f(0,0.6,0)*w(3) + ShColor3f(0.5,0.5,0.5)*w(2))*(ShAttrib1f(1)-col2(0)) + col2(0)*(ShColor3f(0.2,0.2,0.9)*w(0));
      break;
    }
    col1 = (col1 + ShColor3f(1,1,1)*spec) * ec;
    col2 = w(1,0,1);

    out = Globals::toggle*col2 + (1.0-Globals::toggle)*col1;
  } SH_END_PROGRAM;

}

void WorleyShader::apply()
{
  shBindShader(vsh);
  shBindShader(fsh);
}

void WorleyShader::vertex(const SH::ShPoint4f& n, // normal
                          const SH::ShPoint4f& t, // tangent
                          const SH::ShPoint4f& tex, // texture
                                                    // coordinates
                          const SH::ShPoint4f& t2, // secondary
                                                   // tangent
                          const SH::ShPoint4f& v // vertex
                          )
{
  pushSmAttrib(n);
  pushSmAttrib(tex);
  pushSmVertex(v);
}

// Instantiate the shader
WorleyShader worleyShader0( false, 0 );
WorleyShader worleyShader1( false, 1 );
WorleyShader worleyShader2( false, 2 );
WorleyShader worleyShader3( false, 3 );
WorleyShader worleyShader0( false, 4 );
