
#include "Scene.hxx"
#include "PerspectiveCamera.hxx"
#include "Sphere.hxx"
#include "InfinitePlane.hxx"
#include "TexturedSmoothTriangle.hxx"
#include "BumpedTriangle.hxx"
#include "ReflectiveEyeLightShader.hxx"
#include "PhongShader.hxx"
#include "TexturedEyeLightShader.hxx"
#include "DirectionalLight.hxx"
#include "PointLight.hxx"
#include "SpotLight.hxx"

#define LINESIZE 1000

void Scene::ParseNFF(char *fileName)
{
  // parse file, add all items to 'primitives'
  ParseNFF(NULL,fileName,&primitives);
}

void Scene::ParseNFF(FILE *fileToUse, char *fileName, Group *groupToAddTo)
{
  char line[LINESIZE+1];
  char token[LINESIZE+1];
  char *str;

  /* open file */

  FILE *file = fileToUse;
  if (!file) {
    file = fopen(fileName,"r");
    if (!file) {
      std::cerr << "could not open input file " << fileName << std::endl;
      exit(1);
    }
  }
  
  // just to have a default shader, in case the file doesn't define one !
  static Shader *currentShader = new EyeLightShader(Vec3f(1,1,1));
  
  /* parse lines */
  
  while ((str = fgets(line,LINESIZE,file)) && !feof(file)) {
    if (str[0] == '#') // '#' : comment
      continue;
    
    int numtokens = sscanf(line,"%s",token);
    if (numtokens <= 0) // empty line, except for whitespaces
      continue; 
    
    /* start new group */
    
    if (!strcmp(token,"begin_hierarchy")) {
      line[strlen(line)-1] = 0; // remove trailing eol indicator '\n'
      Group *subGroup = new Group;
      groupToAddTo->Add(subGroup);
      ParseNFF(file,fileName,subGroup);
      continue;
    }

    /* end group */

    if (!strcmp(token,"end_hierarchy")) {
      return;
    }
    
    /* camera */

    if (!strcmp(token,"v")) {
      // FORMAT:
      //     v
      //     from %g %g %g
      //     at %g %g %g
      //     up %g %g %g
      //     angle %g
      //     hither %g
      //     resolution %d %d
      Vec3f pos, at, up;
      float angle, hither;
      int resX, resY;
      fscanf(file,"from %g %g %g\n",&pos.x,&pos.y,&pos.z);
      fscanf(file,"at %g %g %g\n",&at.x,&at.y,&at.z);
      fscanf(file,"up %g %g %g\n",&up.x,&up.y,&up.z);
      fscanf(file,"angle %g\n",&angle);
      fscanf(file,"hither %g\n",&hither);
      fscanf(file,"resolution %d %d\n",&resX,&resY);

      if (camera)
	delete camera;
      camera = new PerspectiveCamera(pos,at-pos,up,angle,resX,resY);
      
      continue;
    }
    
    /* sphere */

    if (!strcmp(token,"s")) {
      Vec3f pos;
      float rad;
      sscanf(str,"s %g %g %g %g",&pos.x,&pos.y,&pos.z,&rad);
      groupToAddTo->Add(new Sphere(pos,rad,currentShader));
      continue;
    }

    /* polygon (with vertex normals) */
    
    if (!strcmp(token,"pp")) {
      int vertices;
      sscanf(str,"pp %d",&vertices);
      Vec3f *vertex = new Vec3f[vertices];
      Vec3f *normal = new Vec3f[vertices];

      for (int i=0;i<vertices;i++) {
	fscanf(file,"%g %g %g %g %g %g\n",
	       &vertex[i].x,&vertex[i].y,&vertex[i].z,
	       &normal[i].x,&normal[i].y,&normal[i].z);
      }
      assert(currentShader != NULL);
      for (int i=2;i<vertices;i++) {
	groupToAddTo->Add
	  (new SmoothTriangle(vertex[0],
			      vertex[i-1],
			      vertex[i],
			      normal[0],
			      normal[i-1],
			      normal[i],
			      currentShader));
      }
      delete[] vertex;
      delete[] normal;
      continue;
    }

    
    /* polygon (with vertex normals and texture coordinates) */
    
    if (!strcmp(token,"tpp")) {
      int vertices;
      sscanf(str,"tpp %d",&vertices);
      Vec3f *vertex = new Vec3f[vertices];
      Vec3f *normal = new Vec3f[vertices];
      Vec3f *texcoord = new Vec3f[vertices];

      for (int i=0;i<vertices;i++) {
	fscanf(file,"%g %g %g %g %g %g %g %g %g\n",
	       &vertex[i].x,&vertex[i].y,&vertex[i].z,
	       &normal[i].x,&normal[i].y,&normal[i].z,
	       &texcoord[i].x,&texcoord[i].y,&texcoord[i].z);
      }
      assert(currentShader != NULL);
      for (int i=2;i<vertices;i++) {
	groupToAddTo->Add
	    (new TexturedSmoothTriangle(vertex[0],
					vertex[i-1],
					vertex[i],
					normal[0],
					normal[i-1],
					normal[i],
					texcoord[0],
					texcoord[i-1],
					texcoord[i],
					currentShader));
      }
      delete[] vertex;
      delete[] normal;
      delete[] texcoord;
      continue;
    }

    /* background color */

    if (!strcmp(token,"b")) {
      sscanf(line,"b %g %g %g",&bgColor.x,&bgColor.y,&bgColor.z);
      continue;
    }
    
    /* polygon */
    if (!strcmp(token,"p")) {
      int vertices;
      sscanf(str,"p %d",&vertices);
      Vec3f *vertex = new Vec3f[vertices];
      for (int i=0;i<vertices;i++) {
	fscanf(file,"%g %g %g\n",&vertex[i].x,&vertex[i].y,&vertex[i].z);
      }
      assert(currentShader != NULL);
      for (int i=2;i<vertices;i++) {
	groupToAddTo->Add
	  (new Triangle(vertex[0],
			vertex[i-1],
			vertex[i],
			currentShader));
      }
      delete[] vertex;
      continue;
    }

    
    /* polygon with a given normal (used for Bump Mapping) */
    if (!strcmp(token,"pn")) {
      int vertices;
      sscanf(str,"pn %d",&vertices);
      Vec3f normal;
      fscanf(file,"%g %g %g\n",&normal.x,&normal.y,&normal.z);
      Vec3f *vertex = new Vec3f[vertices];
      for (int i=0;i<vertices;i++) {
	fscanf(file,"%g %g %g\n",&vertex[i].x,&vertex[i].y,&vertex[i].z);
      }
      assert(currentShader != NULL);
      for (int i=2;i<vertices;i++) {
	groupToAddTo->Add
	  (new BumpedTriangle(vertex[0],
			      vertex[i-1],
			      vertex[i],
			      normal,
			      currentShader));
      }
      delete[] vertex;
      continue;
    }
    
    
    /* include new NFF file */
    if (!strcmp(token,"include")) {
      if (!fgets(line,LINESIZE,file)) {
	std::cerr << " error in include, cannot read filename to include" << std::endl;
	exit(0);
      }
      line[strlen(line)-1] = 0; // remove trailing eol indicator '\n'
      std::cout << "including file " << line << std::endl;
      
      Group *subGroup = new Group;
      groupToAddTo->Add(subGroup);
      ParseNFF(NULL,line,subGroup);
      continue;
    }
    
    /* shader parameters */
    if (!strcmp(token,"f")) {
      float r,g,b,kd,ks,shine,t,ior;
      char texture[LINESIZE];

      sscanf(str,"f %g %g %g %g %g %g %g %g %s \n",
	     &r,&g,&b,&kd,&ks,&shine,&t,&ior,texture);
      Vec3f color(r,g,b);
      
      EYELIGHT_SHADER(currentShader = new EyeLightShader(kd*color);)
      REFLECTIVEEYELIGHT_SHADER(currentShader = new ReflectiveEyeLightShader(kd*color, ks);)
      PHONG_SHADER(currentShader = new PhongShader(color,color,WHITE,0.1,kd,ks,shine,Vec3f(1,1,1));)
      TEXTUREDEYELIGHT_SHADER(currentShader = new TexturedEyeLightShader(kd*color,texture);)
      
      continue;
    }

    /* phong shader (ignore default shader) */
    if (!strcmp(token,"fph")) {
      float r,g,b,kd,ks,shine,t,ior;

      sscanf(str,"fph %g %g %g %g %g %g %g %g \n",
	     &r,&g,&b,&kd,&ks,&shine,&t,&ior);
      Vec3f color(r,g,b);
      
      currentShader = new PhongShader(color,color,WHITE,0.1,kd,ks,shine,Vec3f(1,1,1));
      
      continue;
    }
    
    /* lightsource */

    // directional light (exercise 4.1)
    if (!strcmp(token,"dl")) {
      Vec3f dir, col;
      int num = sscanf(line,"dl %g %g %g %g %g %g",
		       &dir.x,&dir.y,&dir.z,
		       &col.x,&col.y,&col.z);

      if ((num == 3) || (num == 6)) {
	  // light source with direction (and color)
	  if (num == 3) {
	      // light source with direction only
	      col = Vec3f(1,1,1);
	  }

	  lights.push_back( new DirectionalLight(dir, col) );	  
      }
      else {
	  std::cout << "error in " << fileName << " : " << line << std::endl;
      }
      continue;
    }
    
    // point light (exercise 4.2)
    if (!strcmp(token,"l")) {
      Vec3f pos, col;
      int num = sscanf(line,"l %g %g %g %g %g %g",
		       &pos.x,&pos.y,&pos.z,
		       &col.x,&col.y,&col.z);

      if ((num == 3) || (num == 6)) {
	  // light source with position (and color)
	  if (num == 3) {
	      // light source with position only
	      col = Vec3f(1,1,1);
	  }

	  lights.push_back( new PointLight(pos, col) );	  
      }
      else {
	std::cout << "error in " << fileName << " : " << line << std::endl;
      }
      continue;
    }

    // spot light (exercise 4.3)
    if (!strcmp(token,"sl")) {
      Vec3f pos, dir, col;
      float alphaMin, alphaMax;
      int num = sscanf(line,"sl %g %g %g %g %g %g %g %g %g %g %g",
		       &pos.x,&pos.y,&pos.z,
		       &dir.x,&dir.y,&dir.z,
		       &col.x,&col.y,&col.z,
		       &alphaMin,&alphaMax);

      if (num == 11) {
	  lights.push_back( new SpotLight(pos, dir, col, alphaMin, alphaMax) );	  
      }
      else {
	std::cout << "error in " << fileName << " : " << line << std::endl;
      }
      continue;
    }
    
    /* unknown command */
    
    std::cout << "error in " << fileName << " : " << line << std::endl;
    exit(0);
  }
  
  if (fileToUse)
    fclose(file);
};
