/* renderer.C: VRML to MGF conversion renderer object */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "mgf/renderer.H"

#include "cie.h"

#include "Assoc.H"
#include "file.H"

#include "Grouping.H"
#include "Transforming.H"
#include "Shape.H"
#include "Appearance.H"
#include "Material.H"
#include "Geometry.H"
#include "Box.H"
#include "Sphere.H"
#include "Cylinder.H"
#include "Cone.H"

#include "nodeCatalog.H"

namespace mgf {
  using namespace xrml;

renderer::renderer(class xrml::file* file_handle,
		   char* progname, bool rgb_vertex_colors)
{
  fh = file_handle;
  renderer::progname = progname;
  renderer::rgb_vertex_colors = rgb_vertex_colors;

  matlevel = ocount = 0;
  xf_stack = stack<bool>(10);
  vidx = array<int>(10);
  current_normal = Vec3(0,0,0);
  current_color = SFColor(0,0,0);
  reverse_vertex_order = color_updated = orig_material = false;
  current_matname = 0;
  current_sidedness = -1;
}

renderer::~renderer()
{
}

#define DEFAULTMATNAME "default"
static void convert_material(char *name, Material *material);

static char demangtab[256];

static void init_demangle_names(void)
{
  int c;
  for (c=0; c<256; c++) demangtab[c] = '_';
  for (c='a'; c<='z'; c++) demangtab[c] = c;
  for (c='A'; c<='Z'; c++) demangtab[c] = c;
  for (c='0'; c<='9'; c++) demangtab[c] = c;
}

// converts special characters to '_'
static char* demangle_name(char *str)
{
  char *s = str;
  while (*s) { *s = demangtab[*s]; s++; }
  return str;
}

static char* mknodename(SFNode *node)
{
  static char *buf=0; static int bufsize=0;
  const char *nodename = node->nameId ? node->nameId : node->typeId;
  int reqbufsz = strlen(nodename) + strlen(node->filename) + 20;
  if (reqbufsz > bufsize) {
    buf = buf ? (char*)realloc(buf, reqbufsz) : (char*)malloc(reqbufsz);
    bufsize = reqbufsz;
  }
  sprintf(buf, "%s@%s:%d", nodename, node->filename, node->line);
  return demangle_name(buf);
}

void renderer::begin_frame(world *wrl)
{
  init_demangle_names();

  fh->printf("# converted from %s with %s\n", wrl->file->url, progname ? progname : "unkown program");

  /* set default material */
  matlevel = 0;
  ocount = 0;
  convert_material(DEFAULTMATNAME, dynamic_cast<Material*>(builtin_nodes->lookup("Material")));
}

void renderer::end_frame(world *)
{
  fh->printf("m\n");	// restore MGF default material
}

void renderer::begin_Grouping(Grouping *node)
{
  fh->printf("o %s\n", mknodename(node));
}

void renderer::end_Grouping(Grouping *)
{
  fh->printf("o\n");
}

void renderer::begin_Transforming(Transforming *xf)
{
  int do_center = Vec3(xf->center.x, xf->center.y, xf->center.z)!=Vec3(0,0,0);
  int do_rotation = Vec4(xf->rotation.x, xf->rotation.y, xf->rotation.z, xf->rotation.radians)!=Vec4(0,0,1,0);
  int do_scale = Vec3(xf->scale.x, xf->scale.y, xf->scale.z)!=Vec3(1,1,1);
  int do_scaleOrientation = Vec4(xf->scaleOrientation.x, xf->scaleOrientation.y, xf->scaleOrientation.z, xf->scaleOrientation.radians)!=Vec4(0,0,1,0);
  int do_translation = Vec3(xf->translation.x, xf->translation.y, xf->translation.z)!=Vec3(0,0,0);

  int do_something = do_center || do_rotation || do_scale || do_scaleOrientation || do_translation;
  xf_stack.push(do_something);

  if (do_something) { 
    fh->printf("xf ");
    if (do_center) 
      fh->printf("-t %g %g %g ", -xf->center.x, -xf->center.y, -xf->center.z);
    if (do_scaleOrientation) 
      fh->printf("-r %g %g %g %g ", xf->scaleOrientation.x, xf->scaleOrientation.y, xf->scaleOrientation.z, -(float)xf->scaleOrientation.radians * 180./M_PI);
    if (do_scale) {
      if (xf->scale.x!=1.) fh->printf("-sx %g ", xf->scale.x);
      if (xf->scale.y!=1.) fh->printf("-sy %g ", xf->scale.y);
      if (xf->scale.z!=1.) fh->printf("-sz %g ", xf->scale.z);
    }
    if (do_scaleOrientation)
      fh->printf("-r %g %g %g %g ", xf->scaleOrientation.x, xf->scaleOrientation.y, xf->scaleOrientation.z, (float)xf->scaleOrientation.radians * 180./M_PI);
    if (do_rotation)
      fh->printf("-r %g %g %g %g ", xf->rotation.x, xf->rotation.y, xf->rotation.z, (float)xf->rotation.radians * 180./M_PI);
    if (do_center) 
      fh->printf("-t %g %g %g ", xf->center.x, xf->center.y, xf->center.z);
    if (do_translation) 
      fh->printf("-t %g %g %g ", xf->translation.x, xf->translation.y, xf->translation.z);
    fh->printf("\n");
  }

  begin_Grouping(xf);
}

void renderer::end_Transforming(Transforming *xf)
{  
  end_Grouping(xf);

  bool did_something = xf_stack.pop();
  if (did_something)
    fh->printf("xf\n");
}

void renderer::begin_Shape(Shape *shape)
{
  bool usedefaultmat = false;

  if (shape->appearance && !IsAppearance(shape->appearance)) {
    cerr << shape->filename << ":" << shape->line << " appearance node of this Shape is not a standard Appearance node. This program cannot convert it yet.\n";
    usedefaultmat = true;
  } else {
    Appearance *app = dynamic_cast<Appearance*>(shape->appearance);
    if (!app  || !app->material)
      usedefaultmat = true;
  }

  if (usedefaultmat)
    fh->printf("m %s\n", DEFAULTMATNAME);
}

// converts material name into Material* of previously converted materials
static assoc<char*,Material*> mattab;

void renderer::begin_Material(Material *mat)
{
  char *matname = "blablabla";
  bool havemat = false;

  if (mat->nameId) {			// named Material node
    int i;
    matname = (char*)mat->nameId;
    havemat = true;
    if ((i=mattab(matname)) < 0 || 	// new name
	mattab[i] != mat) {		// name exists but new material
      mattab.add(matname,mat);
      havemat = false;
    }
  } else {				// unnamed Material node
    matname = mknodename(mat);
    havemat = false;
  }

  if (havemat)		// previously defined named material
    fh->printf("m %s\n", matname);
  else
    convert_material(matname, mat);

  current_color = mat->diffuseColor;	// may change if per-vertex or per-face colors are defined
  orig_material = true;		// mat name needs to be changed if updated with per-face or per-vertex colors
  current_matname = strdup(matname);
  current_sidedness = -1;
  color_updated = false;
}

void renderer::convert_material(char *matname, Material *mat)
{
  float x, y, intensity;

  fh->printf("m %s =\n", matname);

  RGBtoMGF(mat->diffuseColor.r, mat->diffuseColor.g, mat->diffuseColor.b, 
	   &x, &y, &intensity);
  fh->printf("\tc\n\t\tcxy %g %g\n\trd %g\n", x, y, intensity);

  RGBtoMGF(mat->emissiveColor.r, mat->emissiveColor.g, mat->emissiveColor.b, 
	   &x, &y, &intensity);
  if (intensity > 0.)
    fh->printf("\tc\n\t\tcxy %g %g\n\ted %g\n", x, y, intensity);

  RGBtoMGF(mat->specularColor.r, mat->specularColor.g, mat->specularColor.b, 
	   &x, &y, &intensity);
  if (intensity > 0. && mat->shininess > 0.)
    fh->printf("\tc\n\t\tcxy %g %g\n\trs %g %g\n", x, y, intensity, 0.6/sqrt(mat->shininess));
  
  if (mat->transparency > 0.)
    fh->printf("\tc\n\t\tcxy 0.333333 0.333333\n\tts %g 1000\n", (float)mat->transparency);
}

void renderer::end_Material(Material *)
{
  free(current_matname);
  current_matname = DEFAULTMATNAME;
  //  fh->printf("m %s\n", DEFAULTMATNAME);
}

void renderer::geometry(Geometry *geom)
{
  reverse_vertex_order = false;
  if (geom->solid) {
    if (current_sidedness != 1) {
      fh->printf("sides 1\n");	// make current material 1-sided
      current_sidedness = 1;
    }
    if (!geom->ccw)
      reverse_vertex_order = true;
  } else {
    if (current_sidedness != 2) {
      fh->printf("sides 2\n");	// make currrent material 2-sided
      current_sidedness = 2;
    }
  }

  xrml::renderer::geometry(geom);	// default handler
}

void renderer::box(Box *box)
{
  float x=box->size.x/2., y=box->size.y/2., z=box->size.z/2.;

  fh->printf("v cv0 =\n\tp %g %g %g\n", -x, -y, -z);
  fh->printf("v cv1 =\n\tp %g %g %g\n", -x, +y, -z);
  fh->printf("v cv2 =\n\tp %g %g %g\n", +x, +y, -z);
  fh->printf("v cv3 =\n\tp %g %g %g\n", +x, -y, -z);
  fh->printf("prism cv0 cv1 cv2 cv3 %g\n", 2*z);
}

void renderer::cone(Cone *cone)
{
  double r = cone->bottomRadius;
  double h = cone->height/2.;

  fh->printf("v vt1 =\n\tp 0 -%g 0\n\tn 0 -1 0\n", h);
  fh->printf("v vt2 =\n\tp 0 %g 0\n\tn 0 1 0\n", h);
  if (cone->side) fh->printf("cone vt1 %g vt2 0\n", r);
  if (cone->bottom) fh->printf("ring vt1 0 %g\n", r);
}

void renderer::cylinder(Cylinder *cyl)
{
  double h = cyl->height/2.;
  double rad = cyl->radius;

  fh->printf("v vt1 =\n\tp 0 -%g 0\n\tn 0 -1 0\n", h);
  fh->printf("v vt2 =\n\tp 0 %g 0\n\tn 0 1 0\n", h);
  if (cyl->side) fh->printf("cyl vt1 %g vt2\n", rad);
  if (cyl->bottom) fh->printf("ring vt1 0 %g\n", rad);
  if (cyl->top) fh->printf("ring vt2 0 %g\n", rad);
}

void renderer::sphere(Sphere *sph)
{
  float radius = sph->radius;
  fh->printf("v vc =\n\tp 0 0 0\nsph vc %g\n", radius);
}

void renderer::begin_faces(Geometry *geom)
{
  // nothing to do
}

void renderer::begin_face(int, int)
{
  vidx.size = 0;
  current_normal = Vec3(0,0,0);
  if (rgb_vertex_colors)
    color_updated = true;  // force writing the colors
}

void renderer::face_normal(int, const SFVec3f& norm)
{
  current_normal = Vec3(Mat3(inverse_xf) * Vec3(norm)); current_normal.normalize();
}

void renderer::face_color(int, const SFColor& col)
{
  if (rgb_vertex_colors)
    return;  // ignore face colors

  if (col.r != current_color.r || col.g != current_color.g || col.b != current_color.b) {
    current_color = col;
    color_updated = true;
  }
}

void renderer::vertex_normal(int, const SFVec3f& norm)
{
  current_normal = Vec3(Mat3(inverse_xf) * Vec3(norm)); current_normal.normalize();
}

void renderer::vertex_color(int, const SFColor& col)
{
  if (!rgb_vertex_colors) {
    static bool wgiv = false;
    if (!wgiv) {
      cerr << "Per-vertex colors are not supported in MGF. This convertor will turn per-vertex colors into per-face colors\n";
      wgiv = true;
    }
  }

  if (col.r != current_color.r || col.g != current_color.g || col.b != current_color.b) {
    current_color = col;
    color_updated = true;
  }
}

void renderer::vertex_texCoord(int, const SFVec2f& /*t*/)
{
  // texturing is not supported in MGF.
}

void renderer::vertex_coord(int id, const SFVec3f& vert)
{
  Vec3 v = Vec3(vert) * xf;
  fh->printf("v v%d =\n\tp %g %g %g\n", id, v.x, v.y, v.z);
  if (current_normal!=Vec3(0,0,0))
    fh->printf("\tn %g %g %g\n", current_normal.x, current_normal.y, current_normal.z);

  if (rgb_vertex_colors && color_updated) {
    fh->printf("\tvc %g %g %g\n", current_color.r, current_color.g, current_color.b);
    color_updated = false;
  }

  vidx.append(id);
}

void renderer::end_face(int, int)
{
  if (vidx.size >= 3) {
    if (!rgb_vertex_colors && color_updated) {
      if (orig_material) {
	// Change the name of the current material so that future
	// references to the material will use the original,
	// unmodified definition.
	// Note: we don't care about sidedness, since sidedness is
	// explicitly handled for all geometry.
	fh->printf("m %s_updated = %s\n", current_matname, current_matname);
	orig_material = false;
      }

      float x, y, intensity;
      RGBtoMGF(current_color.r, current_color.g, current_color.b, 
	       &x, &y, &intensity);
      fh->printf("\tc\n\t\tcxy %g %g\n\trd %g\n", x, y, intensity);
      color_updated = false;
    }

    fh->printf("f ");
    if (!reverse_vertex_order)
      for (int i=0; i<vidx.size; i++) fh->printf("v%d ", vidx[i]);
    else
      for (int i=vidx.size-1; i>=0; i--) fh->printf("v%d ", vidx[i]);
    fh->printf("\n");
  }
}

void renderer::end_faces(Geometry *geom)
{
  // nothing to do
}

}  // end namespace mgf
