/* readvrml.C: reads VRML files */

#ifndef NO_VRML

#include <stdio.h>
#include <string.h>

#include "error.h"
#include "readvrml.H"

#include "xrml.H"
#include "renderer.H"
#include "Stack.H"
#include "nodeCatalog.H"
#include "Material.H"
#include "TextureTransform.H"
#include "Geometry.H"
#include "PointSet.H"
#include "Viewpoint.H"
#include "PhBAppearance.H"
#include "Texture.H"

extern "C" {
#include "vector.h"
#include "vertex.h"
#include "surface.h"
#include "compound.h"
#include "geom.h"
#include "scene.h"
#include "phong.h"
#include "splitbsdf.h"
#include "patch.h"
#include "pools.h"
#include "error.h"
#include "camera.h"
#include "options.h"
#include "defaults.h"
#include "cie.h"
#include "fileopts.h"
}

#include "PhBRML.H"
#include "PhBRMLBackground.H"

static xrml::stack<GEOMLIST *> geomStack;
static GEOMLIST *currentGeomList;

static xrml::stack<MATERIAL *> matStack;
static MATERIAL *currentMaterial;
static TEXTURE *currentTexture;

static VECTORLIST *currentCoordList, *currentNormalList, *currentTexCoordList;
static VERTEXLIST *currentVertexList;
static PATCHLIST *currentPatchList;

static xrml::array<VERTEXLIST *> vert_hash;
static xrml::array<VECTOR *>coords;
static xrml::array<VECTOR *>normals;  // normals explicitly specified in input
static xrml::array<VECTOR *>backnormals;  // reverse of normals explicitly specified in input
static xrml::array<VECTOR *>texCoords;// tex.co's explictly specified in input

static VECTOR auto_normal, back_normal;	// automatically generated normal and reverse
static VECTOR *currentNormal, *currentBackNormal;
static VECTOR auto_texCoord;	// automaticaly generated tex.co.
static VECTOR *currentTexCoord;
static RGB currentColor;
static xrml::array<VERTEX *> vert(10), backvert(10);
static int nrfaces, skipface;

static VERTEX *InstallVertex(int id, VECTOR *coord, VECTOR *normal, VECTOR *texCoord, const RGB& col)
{
  VERTEX *v = (VERTEX *)NULL;

  if (normal && id > 0)
    v = VertexListFind(vert_hash[id], coord, normal, texCoord);
  if (!v) {
    if (normal == &auto_normal)	{ // automatically generated normal
      normal = VectorCreate(auto_normal.x, auto_normal.y, auto_normal.z);
      currentNormalList = VectorListAdd(currentNormalList, normal);
    }

    if (texCoord == &auto_texCoord) { // automatically generated tex.co
      texCoord = VectorCreate(auto_texCoord.x, auto_texCoord.y, auto_texCoord.z);
      currentTexCoordList = VectorListAdd(currentTexCoordList, texCoord);
    }

    v = VertexCreate(coord, normal, texCoord, PatchListCreate());
    v->color = col;
    if (id > 0)
      vert_hash[id] = VertexListAdd(vert_hash[id], v);
    currentVertexList = VertexListAdd(currentVertexList, v);
  } else {
    // Add color to vertex color. Vertex colors are normalised when connecting the
    // patches to a surface (SurfaceConnectFace() in surface.c)
    v->color.r += col.r;
    v->color.g += col.g;
    v->color.b += col.b;
  }
  return v;
}

static void SetDefaultVRMLCamera(void)
{
  VECTOR eyep(0., 0., 10.), lookp(0., 0., 0.), updir(0., 1., 0.);
  CameraSet(&Camera, &eyep, &lookp, &updir, 
	    Camera.fov, Camera.hres, Camera.vres, &Camera.background);
}

class vrml_importer: public xrml::renderer {
//////////////////////////////////////////////////////
// routines for importing VRML'97 geometry and other
// standard stuff.
//////////////////////////////////////////////////////

  xrml::Mat3 xfn;	// current transform matrix for normals.
  xrml::Mat4 txf;	// current texture transform
  int viewpoint_count;
  bool twosided;        // creates a pair of twin patches for every input polygon
  bool ccw;             // counter clock wise vertex order

  void begin_frame(xrml::world*)
  {
    currentTexture = NULL;
    begin_Material(dynamic_cast<xrml::Material*>(xrml::builtin_nodes->lookup("Material")));
    SetDefaultVRMLCamera();
    viewpoint_count = 0;
  }

  void viewpoint(xrml::Viewpoint *vp)
  {
    VECTOR eyep(0., 0., 0.), lookp(0., 0., -10.), updir(0., 1., 0.), view_rotaxis;
    TRANSFORM view_rot;
    float fov = vp->fieldOfView;

    VECTORSET(view_rotaxis, vp->orientation.x, vp->orientation.y, vp->orientation.z);
    view_rot = Rotate(vp->orientation.radians, view_rotaxis);
    TRANSFORM_POINT_3D(view_rot, lookp, lookp);
    VECTORSET(eyep, vp->position.x, vp->position.y, vp->position.z);
    VECTORADD(lookp, eyep, lookp);
    /* TRANSFORM_VECTOR_3D(view_rot, updir, updir); */

    if (viewpoint_count > 0)
      CameraPush(&Camera);

    CameraSet(&Camera, &eyep, &lookp, &updir, 
	      fov, Camera.hres, Camera.vres, &Camera.background);
    viewpoint_count++;
  }

  // grouping and transforms
  void begin_Grouping(xrml::Grouping *)
  {
    geomStack.push(currentGeomList);
    currentGeomList = GeomListCreate();
  }

  void end_Grouping(xrml::Grouping *)
  {
    GEOM *newgeom = (GEOM *)NULL;
    if (currentGeomList)
      newgeom = GeomCreate(CompoundCreate(currentGeomList), CompoundMethods());
    currentGeomList = geomStack.pop();
    if (newgeom)
      currentGeomList = GeomListAdd(currentGeomList, newgeom);
  }

  // texture transform
  void begin_TextureTransform(xrml::TextureTransform *t)
  {
    txf = t->xf;
  }

  void end_TextureTransform(xrml::TextureTransform *)
  {
    txf = xrml::Mat4();	// identity transform
  }

  // materials
  void begin_Material(xrml::Material *matnode)
  {
    char *name = strdup(matnode->name());
    COLOR Rd, Rs, Td, Ts;
    double Ns;

    matStack.push(currentMaterial);

#ifdef RGBCOLORS
    COLORSET(Rd, matnode->diffuseColor.r, matnode->diffuseColor.g, matnode->diffuseColor.b);
    COLORSET(Rs, matnode->specularColor.r, matnode->specularColor.g, matnode->specularColor.b);
#else
    rgb_xyz((float *)&matnode->diffuseColor, Rd.spec);
    rgb_xyz((float *)&matnode->specularColor, Rs.spec);
#endif
    Ns = matnode->shininess * 128.;
    BRDF *brdf = BrdfCreate(PhongBrdfCreate(&Rd, &Rs, Ns), &PhongBrdfMethods);

    BTDF *btdf = (BTDF*)NULL;
    if (matnode->transparency > EPSILON) {
      COLORSETMONOCHROME(Ts, matnode->transparency);
      COLORCLEAR(Td);
      btdf = BtdfCreate(PhongBtdfCreate(&Td, &Ts, 1000., 1., 0.), &PhongBtdfMethods);
    }

    SPLIT_BSDF *splitbsdf = SplitBSDFCreate(brdf, btdf, disable_textures ? (TEXTURE*)NULL : currentTexture);
    BSDF *bsdf = BsdfCreate(splitbsdf, &SplitBsdfMethods);
    
    EDF *edf = (EDF*)NULL;

    currentMaterial = MaterialCreate(name, edf, bsdf, 1);
    MaterialLib = MaterialListAdd(MaterialLib, currentMaterial);
  }

  void end_Material(xrml::Material *)
  {
    currentMaterial = matStack.pop();
  }

  // TODO: standard VRML textures
  void begin_Texture(xrml::Texture* t)
  {
    if (!t->data.p) {
      if (!t->Map) t->load();
      t->data.p = (void*)CreateTexture(t->Width, t->Height, t->Channels, t->Map);
    }
    currentTexture = (TEXTURE*)(t->data.p);
  }

  void end_Texture(xrml::Texture* t)
  {
    currentTexture = (TEXTURE*)NULL;
  }

  // called by the default geometry handler renderer::geometry()
  void begin_faces(xrml::Geometry *geomnode)
  {
    twosided = !geomnode->solid && !force_onesided_surfaces;
    ccw = geomnode->ccw;

    currentPatchList = PatchListCreate();

    currentCoordList = VectorListCreate();
    currentNormalList = VectorListCreate();
    currentTexCoordList = VectorListCreate();
    currentVertexList = VertexListCreate();

    vert_hash.size = 0;
    vert_hash.grow(geomnode->co->size);

    coords.size = 0;
    coords.grow(geomnode->co->size);
    for (int i=0; i<vert_hash.size; i++) {
      vert_hash[i] = VertexListCreate();
      // transform points as row vectors
      xrml::Vec3 v = xrml::Vec3((*geomnode->co)[i]) * xf;
      coords[i] = VectorCreate(v.x, v.y, v.z);
      currentCoordList = VectorListAdd(currentCoordList, coords[i]);
    }

    // inverse of the upper 3x3 part of xf (equals translational part of xf
    // times inverse of xf = upper 3x3 part of inverse xf).
    xfn = xrml::Mat3(inverse_xf);
    normals.size = 0; backnormals.size = 0;
    if (geomnode->norm) {
      normals.grow(geomnode->norm->size);
      if (twosided) backnormals.grow(geomnode->norm->size);
      for (int i=0; i<geomnode->norm->size; i++) {
	// transform normals as column vectors (= use transpose of transform)
	xrml::Vec3 n = xfn * xrml::Vec3((*geomnode->norm)[i]); n.normalize();
	normals[i] = VectorCreate(n.x, n.y, n.z);
	currentNormalList = VectorListAdd(currentNormalList, normals[i]);
	if (twosided) {
	  backnormals[i] = VectorCreate(-n.x, -n.y, -n.z);
	  currentNormalList = VectorListAdd(currentNormalList, backnormals[i]);
	}
      }
    }

    // texture coordinates
    texCoords.size = 0;
    if (geomnode->texco) {
      texCoords.grow(geomnode->texco->size);
      for (int i=0; i<geomnode->texco->size; i++) {
	xrml::SFVec2f t = (*geomnode->texco)[i];
	xrml::Vec3 T = xrml::Vec3(t.s, t.t, 0.) * txf;
	texCoords[i] = VectorCreate(T.x, T.y, T.z);
	currentTexCoordList = VectorListAdd(currentTexCoordList, texCoords[i]);
      }
    }
  }

  void begin_face(int, int)
  {
    vert.size = backvert.size = 0;
    currentNormal = (VECTOR *)NULL;
    currentBackNormal = (VECTOR *)NULL;
    currentTexCoord = (VECTOR *)NULL;
    //    POINT p(0.,0.,0.);
    //    COLOR rho = BsdfDiffuseReflectance(currentMaterial->bsdf, &p);
    //ColorToRGB(rho, &currentColor);
    currentColor = White;	// TODO: fix this
    skipface = 0;
  }

  void face_normal(int id, const xrml::SFVec3f& norm)
  {
    if (id >= 0) {
      currentNormal = normals[id];
      if (twosided) currentBackNormal = backnormals[id];
    } else {
      xrml::Vec3 n = xfn * xrml::Vec3(norm); n.normalize();
      VECTORSET(auto_normal, n.x, n.y, n.z);
      currentNormal = &auto_normal;
      VECTORSET(back_normal, -n.x, -n.y, -n.z);
      currentBackNormal = &back_normal;
      if (n == xrml::Vec3(0,0,0))
	skipface = 1;	// degenerate face: skip
    }
  }

  void face_color(int, const xrml::SFColor& col)
  {
    RGBSET(currentColor, col.r, col.g, col.b);
  }

  void vertex_normal(int id, const xrml::SFVec3f& norm)
  {
    if (id >= 0) {
      currentNormal = normals[id];
      if (twosided) currentBackNormal = backnormals[id];
    } else {
      xrml::Vec3 n = xfn * xrml::Vec3(norm); n.normalize();
      VECTORSET(auto_normal, n.x, n.y, n.z);
      currentNormal = &auto_normal;
      VECTORSET(back_normal, -n.x, -n.y, -n.z);
      currentBackNormal = &back_normal;
      if (n == xrml::Vec3(0,0,0))
	skipface = 1;	// degenerate face: skip
    }
  }

  void vertex_color(int, const xrml::SFColor& col)
  {
    RGBSET(currentColor, col.r, col.g, col.b);
  }

  void vertex_texCoord(int id, const xrml::SFVec2f& texco)
  {
    if (id >= 0)
      currentTexCoord = texCoords[id];
    else {
      xrml::SFVec2f t = texco;
      xrml::Vec3 T = xrml::Vec3(t.s, t.t, 0.) * txf;
      VECTORSET(auto_texCoord, T.x, T.y, T.z);
      currentTexCoord = &auto_texCoord;
    }
  }

  void vertex_coord(int id, const xrml::SFVec3f& /*vertex*/)
  {
    if (!skipface) {
      vert.append(InstallVertex(id, coords[id], currentNormal, currentTexCoord, currentColor));
      if (twosided)
	backvert.append(InstallVertex(id, coords[id], currentBackNormal , currentTexCoord, currentColor));
      currentTexCoord = (VECTOR*)NULL;
    }
  }

  void end_face(int, int)
  {
    if (skipface || vert.size < 3) return;

    if (vert.size > 4) {
      Warning("vrml_importer::end_face", "can't yet handle faces with more than 4 vertices");
      return;
    }

    PATCH *patch = NULL;
    if (ccw)
      patch = PatchCreate(vert.size, vert[0], vert[1], vert[2], vert.size==4 ? vert[3] : (VERTEX *)NULL);
    else
      patch = PatchCreate(vert.size, vert[vert.size-1], vert[vert.size-2], vert[vert.size-3], vert.size==4 ? vert[0] : (VERTEX *)NULL);
    if (!patch) return;  // degenerate patches!!
    currentPatchList = PatchListAdd(currentPatchList, patch);

    nrfaces++;
    if (nrfaces%1000 == 0) putc('.', stderr);

    if (twosided) {
      PATCH *back = NULL;
      if (ccw)
	back = PatchCreate(vert.size, backvert[vert.size-1], backvert[vert.size-2], backvert[vert.size-3], vert.size==4 ? backvert[0] : (VERTEX *)NULL);
      else 
	back = PatchCreate(vert.size, backvert[0], backvert[1], backvert[2], vert.size==4 ? backvert[3] : (VERTEX *)NULL);
      if (!back) return;  // degenerate patches
      currentPatchList = PatchListAdd(currentPatchList, back);
      patch->twin = back;
      back->twin = patch;
      nrfaces++;
      if (nrfaces%1000 == 0) putc('.', stderr);
    }
  }

  void close_geom(void)
  {
    GEOM *newgeom =
      GeomCreate(SurfaceCreate(currentMaterial, 
			       currentCoordList, currentNormalList, currentTexCoordList,
			       currentVertexList, currentPatchList,
			       NO_COLORS),
		 SurfaceMethods());
    currentGeomList = GeomListAdd(currentGeomList, newgeom);
  }

  void end_faces(xrml::Geometry *)
  {
    if (currentPatchList) close_geom();

    for (int i=0; i<vert_hash.size; i++)
      if (vert_hash[i]) VertexListDestroy(vert_hash[i]);
  }

  ////////////// shaded point sets ////////////////
  float currentPointSize;

  void begin_points(class xrml::PointSet* ps)
  {
    currentPatchList = PatchListCreate();

    currentCoordList = VectorListCreate();
    currentNormalList = VectorListCreate();
    currentTexCoordList = VectorListCreate();
    currentVertexList = VertexListCreate();

    // transpose of current normal transform
    xfn = xrml::Mat3(inverse_xf);

    currentColor = White;   // TODO: find better color

    currentNormal = NULL;
    currentTexCoord = NULL;

    currentPointSize = ps->pointsize;
  }

  void point_color(const xrml::SFColor& col)
  {
    RGBSET(currentColor, col.r, col.g, col.b);
  }

  void point_normal(const xrml::SFVec3f& norm)
  {
    xrml::Vec3 n = xfn * xrml::Vec3(norm); n.normalize();
    if (n != xrml::Vec3(0,0,0)) {
      VECTOR *norm = VectorCreate(n.x, n.y, n.z);
      currentNormalList = VectorListAdd(currentNormalList, norm);
      currentNormal = norm;
    }
  }

  void point_texCoord(const xrml::SFVec2f& texco)
  {
    xrml::SFVec2f t = texco;
    xrml::Vec3 T = xrml::Vec3(t.s, t.t, 0.) * txf;
    VECTOR *texCoord = VectorCreate(T.x, T.y, T.z);
    currentTexCoordList = VectorListAdd(currentTexCoordList, texCoord);
    currentTexCoord = texCoord;
  }

  void tangentdirs(const xrml::Vec3& n, xrml::Vec3& X, xrml::Vec3& Y)
  {
    xrml::Vec3 Z = n;
    double zz = sqrt(1 - Z.z*Z.z);
    X = (zz < EPSILON)
      ? xrml::Vec3(1.,0.,0.)
      : xrml::Vec3(Z.y/zz, -Z.x/zz, 0.);
    Y = Z ^ X;
  }

  VECTOR* install_point_coord(const xrml::Vec3& c)
  {
    VECTOR *coord = VectorCreate(c.x, c.y, c.z);
    currentCoordList = VectorListAdd(currentCoordList, coord);
    return coord;
  }

  void point_coord(const xrml::SFVec3f& coord)
  {
    if (!currentNormal)
      return;  // no normal, no shading

    xrml::Vec3 C = xrml::Vec3(coord.x, coord.y, coord.z) * xf;

    // generate a quadrilateral patch
    xrml::Vec3 n(currentNormal->x, currentNormal->y, currentNormal->z);
    xrml::Vec3 X, Y;
    tangentdirs(n, X, Y);
    X *= currentPointSize*0.5;
    Y *= currentPointSize*0.5;

    VERTEX *v[4];
    v[0] = InstallVertex(-1, install_point_coord(C+X+Y), currentNormal, currentTexCoord, currentColor);
    v[1] = InstallVertex(-1, install_point_coord(C-X+Y), currentNormal, currentTexCoord, currentColor);
    v[2] = InstallVertex(-1, install_point_coord(C-X-Y), currentNormal, currentTexCoord, currentColor);
    v[3] = InstallVertex(-1, install_point_coord(C+X-Y), currentNormal, currentTexCoord, currentColor);
    
    PATCH *patch = PatchCreate(4, v[0], v[1], v[2], v[3]);
    currentPatchList = PatchListAdd(currentPatchList, patch);
    
    currentNormal = NULL;
    currentTexCoord = NULL;

    nrfaces++;
    if (nrfaces%100 == 0) putc('.', stderr);
  }

  void end_points(class xrml::PointSet*)
  {
    if (currentPatchList) close_geom();
  }

///////////////////////////////////////////////////////
// Routines for importing PhBRML appearance etc... 
// extensions to VRML'97
///////////////////////////////////////////////////////

  void begin_PhBAppearance(xrml::PhBAppearance *matnode)
  {
    char *name = strdup(matnode->name());

    matStack.push(currentMaterial);
    currentMaterial = PhBRMLMaterialCreate(name, matnode, inverse_xf, xf);
    MaterialLib = MaterialListAdd(MaterialLib, currentMaterial);
  }

  void end_PhBAppearance(xrml::PhBAppearance *)
  {
    currentMaterial = matStack.pop();
  }

  void phbBackground(xrml::PhBBackground* bkgnode)
  {
    if (!Background)
      Background = PhBRMLBackgroundCreate(bkgnode);
  }

  void phbAtmosphere(xrml::PhBAtmosphere*)
  {
    xrml::Warning("vrml_importer::phbAtmosphere", "not yet implemented");
  }
};


void VrmlDefaults(void)
{
}

void ParseVrmlOptions(int *argc, char **argv)
{
  /*  ParseOptions(vrmlOptions, argc, argv); */
}

void PrintVrmlOptions(FILE *fp)
{
  /*
  fprintf(fp, "\nVRML input options:\n");
  PrintOptions(fp, vrmlOptions);
  */
}

void ReadVrml(char *filename)
{
  xrml::world *w = new xrml::world;

  if (!w->parse((filename[0] == '#') ? NULL : filename))
    return;
  w->newframe();

  fprintf(stderr, "Importing ...\n");

  vrml_importer *importer = new vrml_importer;
  importer->nrQuartCircleDivisions = nqcdivs;
  w->set_renderer(importer);

  currentGeomList = GeomListCreate();
  currentMaterial = &defaultMaterial;
  MaterialLib = MaterialListCreate();
  nrfaces = 0;

  w->render();

  World = currentGeomList;
  fprintf(stderr, "\n");
}

#endif /*NO_VRML*/
