// cso_exporter.C: exports a scene graph in the form of a
// C++ program, with main, C-callable, routine 
//
// void import(void* world, char **strpool)
//
// that will append the scene graph to the xrml::world*
// passed.
//
// When compiled as shared libraries, these programs can
// be loaded dynamically again later using the cso::importer
// class.
//
// Philippe Bekaert, February 28, 2001
//
// TODO:
//
// - save strings in the string pool passed to import()
// - save [S|M]FImage values
// - save version information

#include <stdlib.h>
#include "exporter.H"
#include "nodeInterface.H"
#include "route.H"
#include "List.H"

namespace cso {

bool exporter::saved_already(const SFNode* node)
{
  return (node->export_data.i != 0);
}

int exporter::save_id(const SFNode* node)
{
  return (node->export_data.i);
}

void exporter::set_save_id(class SFNode* node)
{
  id++;
  node->export_data.i = id;
}

char* exporter::unique_nameId(class SFNode* node)
{
  static char* buf = 0; static int bufsize = 0;
  int reqsize = (int)strlen(node->nameId) + 12;
  if (bufsize < reqsize) {
    if (buf) delete buf;
    buf = new char[reqsize];
    bufsize = reqsize;
  }
  sprintf(buf, "(%d)%s", node->nameIndex+1, node->nameId);
  return buf;
}

void exporter::save_value(const SFBool& val)
{
  fh->printf("%s", (bool)val ? "true" : "false");
}

void exporter::save_value(const SFString& val)
{
  fh->printf("strsave(\"%s\")", (char*)val);
}

void exporter::save_value(const SFInt32& val)
{
  fh->printf("%d", (int)val);
}

void exporter::save_value(const SFFloat& val)
{
  fh->printf("%g", (float)val);
}

void exporter::save_value(const SFTime& val)
{
  fh->printf("%lg", (double)val);
}

void exporter::save_value(const SFVec2f& val)
{
  fh->printf("%g, %g", val.s, val.t);
}

void exporter::save_value(const SFVec3f& val)
{
  fh->printf("%g, %g, %g", val.x, val.y, val.z);
}

void exporter::save_value(const SFColor& val)
{
  fh->printf("%g, %g, %g", val.r, val.g, val.b);
}

void exporter::save_value(const SFRotation& val)
{
  fh->printf("%g, %g, %g, %g", val.x, val.y, val.z, val.radians);
}

void exporter::save_value(const SFImage& val)
{
  fh->printf("SFImage(%d, %d, %d)", val.width, val.height, val.num_components);
  // TODO
  Warning("cso::exporter::save_value(SFImage)", "not yet implemented");
}

void exporter::save_value(const SFNode *val)
{
  if (val)
    fh->printf("node_%d()", save_id(val));
  else
    fh->printf("(SFNode*)0");
}

#define save_single_value(stype, component) { \
    fh->printf("  %s %s(", stype, Id); \
    save_value(*fvalue.value.component); \
    fh->printf(");\n"); \
}

#define save_array_value(eltype, stype, mtype, component) {              \
  fh->printf("  %s the_%s[] = {", eltype, Id);                     \
  for (int i=0; i<(*fvalue.value.component).size; i++) {   \
    fh->printf(i>0 ? ", " : " ");      \
    save_value((*fvalue.value.component)[i]);                    \
  }                                    \
  fh->printf(" };\n");                    \
  fh->printf("  %s %s; %s.set(%d, (%s*)the_%s);\n", mtype, Id, Id, (*fvalue.value.component).size, stype, Id); \
}

void exporter::save_value(const char* Id, const fieldValue& fvalue, int index)
{
  assert(fvalue.value.any);
  switch (fvalue.type) {
  case tSFBool:     save_single_value("SFBool", sfbool); break;
  case tSFString:   save_single_value("SFString", sfstring); break; 
  case tSFInt32:    save_single_value("SFInt32", sfint32); break; 
  case tSFFloat:    save_single_value("SFFloat", sffloat); break; 
  case tSFTime:	    save_single_value("SFTime", sftime); break; 
  case tSFVec2f:    save_single_value("SFVec2f", sfvec2f); break; 
  case tSFVec3f:    save_single_value("SFVec3f", sfvec3f); break; 
  case tSFColor:    save_single_value("SFColor", sfcolor); break; 
  case tSFRotation: save_single_value("SFRotation", sfrotation); break; 
  case tSFImage:    save_single_value("SFImage", sfimage); break; 
  case tSFNode:     save_single_value("SFNode*", sfnode); break;
  case tMFBool:	    save_array_value("bool", "SFBool", "MFBool", mfbool); break;
  case tMFString:   save_array_value("char*", "SFString", "MFString", mfstring); break;
  case tMFInt32:    save_array_value("int", "SFInt32", "MFInt32", mfint32); break;
  case tMFFloat:    save_array_value("float", "SFFloat", "MFFloat", mffloat); break;
  case tMFTime:	    save_array_value("double", "SFTime", "MFTime", mftime); break;
  case tMFVec2f:    save_array_value("float", "SFVec2f", "MFVec2f", mfvec2f); break;
  case tMFVec3f:    save_array_value("float", "SFVec3f", "MFVec3f", mfvec3f); break;
  case tMFColor:    save_array_value("float", "SFColor", "MFColor", mfcolor); break;
  case tMFRotation: save_array_value("float", "SFRotation", "MFRotation", mfrotation); break;
  case tMFImage:    save_array_value("SFImage", "SFImage", "MFImage", mfimage); break;
  case tMFNode:	    save_array_value("SFNode*", "SFNode*", "MFNode", mfnode); break;
  default:	    Fatal(-1, "cso::exporter(fieldValue)", "invalid type '%d'", fvalue.type);
  }
  fh->printf("  node->interface[%d]->assign(node, fieldValue(&%s));\n", index, Id);
}

void exporter::save_field(class field* field, int index)
{
  save_value(field->Id, field->value, index);
}

void exporter::save_link(class link* link, int index)
{
  fh->printf("  node->interface[%d] = node->interface[%d]->link(node, master, master->interface[%d]);\n",
	     index, index, link->refidx);
}

void exporter::save_field_or_link(class interfaceMember* ifm, int index)
{
  if (is_link(ifm))
    save_link(dynamic_cast<link*>(ifm), index);
  else if (is_field(ifm))
    save_field(dynamic_cast<field*>(ifm), index);
}

void exporter::save_members(class SFNode* node)
{
  for (int i=0; i<node->nrInterfaceMembers; i++) {
    if (node->interface[i]->timestamp != CONSTRUCT_TIME || is_link(node->interface[i]))
      save_field_or_link(node->interface[i], i);
  }
}

void exporter::save_children_nodes(class field* field)
{
  switch (field->value.type) {
  case tSFNode:
    {
      SFNode* child = *field->value.value.sfnode;
      if (child) save(child);
    }
    break;
  case tMFNode:
    {
      MFNode children = *field->value.value.mfnode;
      for (int i=0; i<children.size; i++)
	if (children[i]) save(children[i]);
    }
    break;
  default: /* skip */ break;
  }
}

void exporter::save_children_nodes(class SFNode* node)
{
  for (int i=0; i<node->nrInterfaceMembers; i++) {
    if (is_field(node->interface[i]))
      save_children_nodes(dynamic_cast<field*>(node->interface[i]));
  }
}

void exporter::save(class SFNode* node)
{
  if (!node || saved_already(node))
    return;

  // save children node definitions first
  save_children_nodes(node);
  set_save_id(node);

  Proto* proto = dynamic_cast<Proto*>(node);
  if (!proto)
    fh->printf("#include \"%s.H\"\n\n", node->typeId);

  fh->printf("static SFNode* node_%d(void)\n{\n", save_id(node));
  fh->printf("  static %s* node = 0;\n", proto ? "SFNode" : node->typeId);
  fh->printf("  if (node) return node;\n\n");

  if (!proto)
    fh->printf("  node = new %s;\n", node->typeId);
  else
    fh->printf("  node = proto_%d()->instantiate();\n", save_id(proto->parent));

  // save field values and links
  save_members(node);

  // named nodes save actions
  if (node->nameId) {
    fh->printf("  node->nameId = strsave(\"%s\");\n", node->nameId);
    fh->printf("  node->nameIndex = namedNodes->add((char*)(node->nameId), node);\n", node->nameId);
  }
  fh->printf("  return node;\n");
  fh->printf("}\n\n");
}

const char* exporter::memberkindname(class interfaceMember* ifm)
{
  if      (is_exposedField(ifm)) return "exposedField";
  else if (is_field(ifm))        return "field";
  else if (is_eventIn(ifm))      return "eventIn";
  else if (is_eventOut(ifm))     return "eventOut";
  else Fatal(-1, "cso::exporter::kindname", "Unknown interface member kind!!");
  return 0;
}

const char* exporter::membertypename(class interfaceMember* ifm)
{
  switch (fieldType(ifm->value)) {
  case tSFNode:
    return "SFNode*";
  default:
    return fieldTypeName(fieldType(ifm->value));
  }
}

void exporter::save_proto_interfaceMember(class interfaceMember* ifm)
{
  fh->printf("    new %s(strsave(\"%s\"), ", memberkindname(ifm), ifm->Id);
  if (is_field(ifm))
    fh->printf("fieldValue(new %s), ", membertypename(ifm));
  else
    fh->printf("%s, ", membertypename(ifm));
  fh->printf("0),\n");
}

void exporter::save_proto_interface(class Proto* proto)
{
  int i;
  save_children_nodes(proto);

  fh->printf("static SFNode* proto_%d_master(void)\n{\n", save_id(proto));
  fh->printf("  interfaceMember* interface[%d] = {\n", proto->nrInterfaceMembers);
  for (i=0; i<proto->nrInterfaceMembers; i++) {
    save_proto_interfaceMember(proto->interface[i]);
  }
  fh->printf("  };\n");
  fh->printf("  SFNode* node = new SFNode(strsave(\"%s\"), %d, interface);\n\n", proto->typeId, proto->nrInterfaceMembers);

  for (i=0; i<proto->nrInterfaceMembers; i++)
    save_field_or_link(proto->interface[i], i);
  fh->printf("  return node;\n");
  fh->printf("}\n\n");
}

void exporter::save_proto_implementation(class Proto* proto)
{
  char new_prefix[100];
  sprintf(new_prefix, "proto_%d_", save_id(proto));
  save_context(proto->sceneGraph, proto->protos, proto->namedNodes, new_prefix);
}

void exporter::save_protodef(class Proto* proto)
{
  set_save_id(proto);

  save_proto_interface(proto);
  save_proto_implementation(proto);

  fh->printf("static Proto* proto_%d(void)\n{\n", save_id(proto));
  fh->printf("  static Proto* proto = 0;\n");
  fh->printf("  if (proto) return proto;\n\n");
  fh->printf("  SFNode* prev_master = master; master = proto_%d_master();\n", save_id(proto));
  fh->printf("  MFNode* prev_sg = sceneGraph; sceneGraph = new MFNode;\n");
  fh->printf("  namedNodeTable* prev_nn = namedNodes; namedNodes = new namedNodeTable;\n");
  fh->printf("  protoTable* prev_protos = protos; protos = new protoTable;\n");
  fh->printf("  \n");
  fh->printf("  proto_%d_protodecls();\n", save_id(proto));
  fh->printf("  proto_%d_scenegraph();\n", save_id(proto));
  fh->printf("  proto_%d_routes();\n", save_id(proto));
  fh->printf("  proto = new Proto(master, sceneGraph, *namedNodes, *protos);\n");
  fh->printf("  \n");
  fh->printf("  master = prev_master;\n");
  fh->printf("  sceneGraph = prev_sg;\n");
  fh->printf("  namedNodes = prev_nn;\n");
  fh->printf("  protos = prev_protos;\n\n");
  fh->printf("  protos->add((char*)(proto->typeId), proto);\n", proto->typeId);
  fh->printf("  return proto;\n");
  fh->printf("}\n\n");
}

void exporter::save_route(class SFNode* srcNode, int srcIndex, class route* route)
{
  fh->printf("  node_%d()->interface[%d]->add_route(node_%d(), node_%d()->interface[%d]);\n",
	     save_id(srcNode), srcIndex,
	     save_id(route->refnode), save_id(route->refnode), route->refidx);
}

void exporter::save_routes(class SFNode* srcNode, int srcIndex, class routing_table* rtable)
{
  if (!rtable->routes)
    return;

  for (class list<route> *r=rtable->routes; r; r=r->next) {
    class route* route = (class route*)(*r);
    save_route(srcNode, srcIndex, route);
  }
}

void exporter::save_children_routes(class  field* field)
{
  switch (field->value.type) {
  case tSFNode: save_routes(*field->value.value.sfnode); break;
  case tMFNode: save_routes(*field->value.value.mfnode); break;
  default: /* skip */ break;
  }
}

void exporter::save_routes(class SFNode* node)
{
  if (!node || !node->nameId)
    return;   // not a named node -> no routes, or already saved

  for (int i=0; i<node->nrInterfaceMembers; i++) {
    if (is_routable(node->interface[i]))
      save_routes(node, i, dynamic_cast<routing_table*>(node->interface[i]));
  }
}

void exporter::save_routes(const MFNode& nodes)
{
  for (int i=0; i<nodes.size; i++)
    save_routes(nodes[i]);
}

void exporter::save_context(const MFNode* sceneGraph, const protoTable& protos, const namedNodeTable& namedNodes,  char* new_prefix)
{
  int i;
  char *prev_prefix = prefix;
  prefix = new_prefix;

  for (i=0; i<protos.size; i++) {
    save_protodef(protos[i]);
  }

  fh->printf("static void %sprotodecls(void)\n{\n", prefix);
  for (i=0; i<protos.size; i++) {
    fh->printf("  proto_%d();\n", save_id(protos[i]));
  }
  fh->printf("}\n\n");

  if (sceneGraph) {
    for (i=0; i<sceneGraph->size; i++) {
      save((*sceneGraph)[i]);
    }

    fh->printf("static void %sscenegraph(void)\n{\n", prefix);
    fh->printf("  SFNode* the_nodes[] = {");
    for (i=0; i<sceneGraph->size; i++) {
      fh->printf("%snode_%d()",
		 i==0 ? " " : ", ",
		 save_id((*sceneGraph)[i]));
    }
    fh->printf(" };\n");
    fh->printf("  sceneGraph->append(%d, the_nodes);\n", sceneGraph->size);
    fh->printf("}\n\n");
    
    fh->printf("static void %sroutes(void)\n{\n", prefix);
    for (i=0; i<namedNodes.size; i++) {
      save_routes(namedNodes[i]);
    }
    fh->printf("}\n\n");
  }
  
  prefix = prev_prefix;
}

void exporter::clear_data(class field* field)
{
  switch (field->value.type) {
  case tSFNode: clear_data(*field->value.value.sfnode); break;
  case tMFNode: clear_data(*field->value.value.mfnode); break;
  default: /* skip */ break;
  }  
}

void exporter::clear_data(class SFNode* node)
{
  if (!node || node->export_data.i==0) return;

  node->export_data.i = 0;
  for (int i=0; i<node->nrInterfaceMembers; i++)
    if (is_field(node->interface[i]))
      clear_data(dynamic_cast<field*>(node->interface[i]));
}

void exporter::clear_data(const MFNode& nodes)
{
  for (int i=0; i<nodes.size; i++)
    clear_data(nodes[i]);
}

void exporter::clear_data(class Proto* proto)
{
  int i;
  for (i=0; i<proto->protos.size; i++)
    clear_data(proto->protos[i]);
  clear_data(*(proto->sceneGraph));
}

void exporter::clear_data(class world* world)
{
  int i;
  for (i=0; i<world->protos.size; i++)
    clear_data(world->protos[i]);
  clear_data((*world->sceneGraph));  
}

void exporter::save(class world* world)
{
  // TODO: save version information.

  id = 0;  // unique ID number for all nodes in the model.
  prefix = 0;

  fh->printf("// 3D model (%s) saved using XRML C/so exporter\n", world->filename);

  fh->printf("\n#include <string.h>\n#define strsave strdup\n\n");

  fh->printf("#include \"xrmlP.H\"\n\n");
  fh->printf("using namespace xrml;\n\n");

  fh->printf("static MFNode* sceneGraph;\n");
  fh->printf("static namedNodeTable* namedNodes;\n");
  fh->printf("static protoTable* protos;\n");
  fh->printf("static SFNode* master;  // contains enclosing PROTO interface\n\n");

  save_context(world->sceneGraph, world->protos, world->namedNodes, "");

  fh->printf("static void do_import(class world* world)\n{\n");
  fh->printf("  sceneGraph = world->sceneGraph;\n");
  fh->printf("  namedNodes = &world->namedNodes;\n");
  fh->printf("  protos = &world->protos;\n");
  fh->printf("  master = (SFNode*)0;\n\n");
  fh->printf("  protodecls();\n");
  fh->printf("  bool save_bind_bindables = init_bind_bindables;\n");
  fh->printf("  init_bind_bindables = true;\n");
  fh->printf("  scenegraph();\n");
  fh->printf("  init_bind_bindables = save_bind_bindables;\n");
  fh->printf("  routes();\n");
  fh->printf("}\n\n");

  fh->printf("extern \"C\" {\n\nvoid import(void *w, char **strpool)\n{\n  do_import((xrml::world*)w);\n}\n\n}\n\n");

  clear_data(world);
}

}  // namespace cso
