// Filename: xFileNode.cxx
// Created by:  drose (03Oct04)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license.  You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////

#include "xFileNode.h"
#include "windowsGuid.h"
#include "xFile.h"
#include "xLexerDefs.h"
#include "xFileParseData.h"
#include "xFile.h"
#include "xFileDataNodeTemplate.h"
#include "filename.h"
#include "string_utils.h"

TypeHandle XFileNode::_type_handle;

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
XFileNode::
XFileNode(XFile *x_file, const string &name) :
  Namable(),
  _x_file(x_file)
{
  if (x_file && x_file->_keep_names) {
    set_name(name);
  } else {
    set_name(make_nice_name(name));
  }
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::Destructor
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
XFileNode::
~XFileNode() {
  clear();
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::find_child
//       Access: Public
//  Description: Returns the child with the indicated name, if any, or
//               NULL if none.
////////////////////////////////////////////////////////////////////
XFileNode *XFileNode::
find_child(const string &name) const {
  ChildrenByName::const_iterator ni;
  ni = _children_by_name.find(downcase(name));
  if (ni != _children_by_name.end()) {
    return get_child((*ni).second);
  }

  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::find_child_index
//       Access: Public
//  Description: Returns the index number of the child with the
//               indicated name, if any, or -1 if none.
////////////////////////////////////////////////////////////////////
int XFileNode::
find_child_index(const string &name) const {
  ChildrenByName::const_iterator ni;
  ni = _children_by_name.find(downcase(name));
  if (ni != _children_by_name.end()) {
    return (*ni).second;
  }

  return -1;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::find_child_index
//       Access: Public
//  Description: Returns the index number of the indicated child,
//               or -1 if none.
////////////////////////////////////////////////////////////////////
int XFileNode::
find_child_index(const XFileNode *child) const {
  for (int i = 0; i < (int)_children.size(); i++) {
    if (_children[i] == child) {
      return i;
    }
  }

  return -1;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::find_descendent
//       Access: Public
//  Description: Returns the first child or descendent found with the
//               indicated name after a depth-first search, if any, or
//               NULL if none.
////////////////////////////////////////////////////////////////////
XFileNode *XFileNode::
find_descendent(const string &name) const {
  XFileNode *child = find_child(name);
  if (child != (XFileNode *)NULL) {
    return child;
  }

  Children::const_iterator ci;
  for (ci = _children.begin(); ci != _children.end(); ++ci) {
    XFileNode *child = (*ci)->find_descendent(name);
    if (child != (XFileNode *)NULL){ 
      return child;
    }
  }

  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::has_guid
//       Access: Public, Virtual
//  Description: Returns true if this node has a GUID associated.
////////////////////////////////////////////////////////////////////
bool XFileNode::
has_guid() const {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::get_guid
//       Access: Public, Virtual
//  Description: If has_guid() returned true, returns the particular
//               GUID associated with this node.
////////////////////////////////////////////////////////////////////
const WindowsGuid &XFileNode::
get_guid() const {
  static WindowsGuid empty;
  return empty;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::is_template_def
//       Access: Public, Virtual
//  Description: Returns true if this node represents the definition
//               of some template.  This is the template definition,
//               not an actual data object that represents an instance
//               of the template.  If the file strictly uses standard
//               templates, the presence of template definitions is
//               optional.
//
//               If this returns true, the node must be of type
//               XFileTemplate.
////////////////////////////////////////////////////////////////////
bool XFileNode::
is_template_def() const {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::is_reference
//       Access: Public, Virtual
//  Description: Returns true if this node represents an indirect
//               reference to an object defined previously in the
//               file.  References are generally transparent, so in
//               most cases you never need to call this, unless you
//               actually need to differentiate between references and
//               instances; you can simply use the reference node as
//               if it were itself the object it references.
//
//               If this returns true, the node must be of type
//               XFileDataNodeReference.
////////////////////////////////////////////////////////////////////
bool XFileNode::
is_reference() const {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::is_object
//       Access: Public, Virtual
//  Description: Returns true if this node represents a data object
//               that is the instance of some template, or false
//               otherwise.  This also returns true for references to
//               objects (which are generally treated just like the
//               objects themselves).
//
//               If this returns true, the node must be of type
//               XFileDataNode (it is either an XFileDataNodeTemplate
//               or an XFileDataNodeReference).
////////////////////////////////////////////////////////////////////
bool XFileNode::
is_object() const {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::is_standard_object
//       Access: Public, Virtual
//  Description: Returns true if this node represents an instance of
//               the standard template with the indicated name, or
//               false otherwise.  This returns also returns true for
//               references to standard objects.
//
//               If this returns true, the node must be of type
//               XFileDataNode (it is either an XFileDataNodeTemplate
//               or an XFileDataNodeReference).
////////////////////////////////////////////////////////////////////
bool XFileNode::
is_standard_object(const string &template_name) const {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::add_child
//       Access: Public
//  Description: Adds the indicated node as a child of this node.
////////////////////////////////////////////////////////////////////
void XFileNode::
add_child(XFileNode *node) {
  if (node->has_name()) {
    _children_by_name[downcase(node->get_name())] = (int)_children.size();
  }
  if (node->has_guid()) {
    _x_file->_nodes_by_guid[node->get_guid()] = node;
  }
  if (node->is_of_type(XFileDataNode::get_class_type())) {
    _objects.push_back(DCAST(XFileDataNode, node));
  }
  _children.push_back(node);
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::clear
//       Access: Public, Virtual
//  Description: Removes all children from the node, and otherwise
//               resets it to its initial state.
////////////////////////////////////////////////////////////////////
void XFileNode::
clear() {
  _children.clear();
  _objects.clear();
  _children_by_name.clear();
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::write_text
//       Access: Public, Virtual
//  Description: Writes a suitable representation of this node to an
//               .x file in text mode.
////////////////////////////////////////////////////////////////////
void XFileNode::
write_text(ostream &out, int indent_level) const {
  Children::const_iterator ci;
  for (ci = _children.begin(); ci != _children.end(); ++ci) {
    (*ci)->write_text(out, indent_level);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::repack_data
//       Access: Public, Virtual
//  Description: This is called on the template that defines an
//               object, once the data for the object has been parsed.
//               It is responsible for identifying which component of
//               the template owns each data element, and packing the
//               data elements appropriately back into the object.
//
//               It returns true on success, or false on an error
//               (e.g. not enough data elements, mismatched data
//               type).
////////////////////////////////////////////////////////////////////
bool XFileNode::
repack_data(XFileDataObject *object, 
            const XFileParseDataList &parse_data_list,
            XFileNode::PrevData &prev_data,
            size_t &index, size_t &sub_index) const {
  // This method should be specialized for data types that actually
  // consume a data element.  Here in the base class, it just walks
  // through its children, asking each one to pull off the appropriate
  // number of data elements.

  Children::const_iterator ci;
  for (ci = _children.begin(); ci != _children.end(); ++ci) {
    if (!(*ci)->repack_data(object, parse_data_list, 
                            prev_data, index, sub_index)) {
      return false;
    }
  }

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::fill_zero_data
//       Access: Public, Virtual
//  Description: This is similar to repack_data(), except it is used
//               to fill the initial values for a newly-created
//               template object to zero.
////////////////////////////////////////////////////////////////////
bool XFileNode::
fill_zero_data(XFileDataObject *object) const {
  Children::const_iterator ci;
  for (ci = _children.begin(); ci != _children.end(); ++ci) {
    if (!(*ci)->fill_zero_data(object)) {
      return false;
    }
  }

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::matches
//       Access: Public, Virtual
//  Description: Returns true if the node, particularly a template
//               node, is structurally equivalent to the other node
//               (which must be of the same type).  This checks data
//               element types, but does not compare data element
//               names.
////////////////////////////////////////////////////////////////////
bool XFileNode::
matches(const XFileNode *other) const {
  if (other->get_type() != get_type()) {
    return false;
  }

  if (other->get_num_children() != get_num_children()) {
    return false;
  }

  for (int i = 0; i < get_num_children(); i++) {
    if (!get_child(i)->matches(other->get_child(i))) {
      return false;
    }
  }

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::add_Mesh
//       Access: Public
//  Description: Creates a new Mesh instance, as a child of this node.
////////////////////////////////////////////////////////////////////
XFileDataNode *XFileNode::
add_Mesh(const string &name) {
  XFileTemplate *xtemplate = XFile::find_standard_template("Mesh");
  nassertr(xtemplate != (XFileTemplate *)NULL, NULL);
  XFileDataNodeTemplate *node =
    new XFileDataNodeTemplate(get_x_file(), name, xtemplate);
  add_child(node);
  node->zero_fill();

  return node;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::add_MeshNormals
//       Access: Public
//  Description: Creates a new MeshNormals instance, as a child of
//               this node.
////////////////////////////////////////////////////////////////////
XFileDataNode *XFileNode::
add_MeshNormals(const string &name) {
  XFileTemplate *xtemplate = XFile::find_standard_template("MeshNormals");
  nassertr(xtemplate != (XFileTemplate *)NULL, NULL);
  XFileDataNodeTemplate *node =
    new XFileDataNodeTemplate(get_x_file(), name, xtemplate);
  add_child(node);
  node->zero_fill();

  return node;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::add_MeshVertexColors
//       Access: Public
//  Description: Creates a new MeshVertexColors instance, as a child of
//               this node.
////////////////////////////////////////////////////////////////////
XFileDataNode *XFileNode::
add_MeshVertexColors(const string &name) {
  XFileTemplate *xtemplate = XFile::find_standard_template("MeshVertexColors");
  nassertr(xtemplate != (XFileTemplate *)NULL, NULL);
  XFileDataNodeTemplate *node =
    new XFileDataNodeTemplate(get_x_file(), name, xtemplate);
  add_child(node);
  node->zero_fill();

  return node;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::add_MeshTextureCoords
//       Access: Public
//  Description: Creates a new MeshTextureCoords instance, as a child of
//               this node.
////////////////////////////////////////////////////////////////////
XFileDataNode *XFileNode::
add_MeshTextureCoords(const string &name) {
  XFileTemplate *xtemplate = XFile::find_standard_template("MeshTextureCoords");
  nassertr(xtemplate != (XFileTemplate *)NULL, NULL);
  XFileDataNodeTemplate *node =
    new XFileDataNodeTemplate(get_x_file(), name, xtemplate);
  add_child(node);
  node->zero_fill();

  return node;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::add_MeshMaterialList
//       Access: Public
//  Description: Creates a new MeshMaterialList instance, as a child of
//               this node.
////////////////////////////////////////////////////////////////////
XFileDataNode *XFileNode::
add_MeshMaterialList(const string &name) {
  XFileTemplate *xtemplate = XFile::find_standard_template("MeshMaterialList");
  nassertr(xtemplate != (XFileTemplate *)NULL, NULL);
  XFileDataNodeTemplate *node =
    new XFileDataNodeTemplate(get_x_file(), name, xtemplate);
  add_child(node);
  node->zero_fill();

  return node;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::add_Material
//       Access: Public
//  Description: Creates a new Material instance, as a child of
//               this node.
////////////////////////////////////////////////////////////////////
XFileDataNode *XFileNode::
add_Material(const string &name, const Colorf &face_color,
             double power, const RGBColorf &specular_color,
             const RGBColorf &emissive_color) {
  XFileTemplate *xtemplate = XFile::find_standard_template("Material");
  nassertr(xtemplate != (XFileTemplate *)NULL, NULL);
  XFileDataNodeTemplate *node =
    new XFileDataNodeTemplate(get_x_file(), name, xtemplate);
  add_child(node);
  node->zero_fill();

  (*node)["faceColor"]["red"] = face_color[0];
  (*node)["faceColor"]["green"] = face_color[1];
  (*node)["faceColor"]["blue"] = face_color[2];
  (*node)["faceColor"]["alpha"] = face_color[3];
  (*node)["power"] = power;
  (*node)["specularColor"]["red"] = specular_color[0];
  (*node)["specularColor"]["green"] = specular_color[1];
  (*node)["specularColor"]["blue"] = specular_color[2];
  (*node)["emissiveColor"]["red"] = emissive_color[0];
  (*node)["emissiveColor"]["green"] = emissive_color[1];
  (*node)["emissiveColor"]["blue"] = emissive_color[2];

  return node;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::add_TextureFilename
//       Access: Public
//  Description: Creates a new TextureFilename instance, as a child of
//               this node.
////////////////////////////////////////////////////////////////////
XFileDataNode *XFileNode::
add_TextureFilename(const string &name, const Filename &filename) {
  XFileTemplate *xtemplate = XFile::find_standard_template("TextureFilename");
  nassertr(xtemplate != (XFileTemplate *)NULL, NULL);
  XFileDataNodeTemplate *node =
    new XFileDataNodeTemplate(get_x_file(), name, xtemplate);
  add_child(node);
  node->zero_fill();

  (*node)["filename"] = filename.to_os_specific();

  return node;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::add_Frame
//       Access: Public
//  Description: Creates a new Frame instance, as a child of this
//               node.
////////////////////////////////////////////////////////////////////
XFileDataNode *XFileNode::
add_Frame(const string &name) {
  XFileTemplate *xtemplate = XFile::find_standard_template("Frame");
  nassertr(xtemplate != (XFileTemplate *)NULL, NULL);
  XFileDataNodeTemplate *node =
    new XFileDataNodeTemplate(get_x_file(), name, xtemplate);
  add_child(node);
  node->zero_fill();

  return node;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::add_FrameTransformMatrix
//       Access: Public
//  Description: Creates a new FrameTransformMatrix instance, as a
//               child of this node.
////////////////////////////////////////////////////////////////////
XFileDataNode *XFileNode::
add_FrameTransformMatrix(const LMatrix4d &mat) {
  XFileTemplate *xtemplate = 
    XFile::find_standard_template("FrameTransformMatrix");
  nassertr(xtemplate != (XFileTemplate *)NULL, NULL);
  XFileDataNodeTemplate *node = 
    new XFileDataNodeTemplate(get_x_file(), "", xtemplate);
  add_child(node);
  node->zero_fill();

  XFileDataObject &xmat = (*node)["frameMatrix"]["matrix"];
  xmat[0] = mat(0, 0);
  xmat[1] = mat(0, 1);
  xmat[2] = mat(0, 2);
  xmat[3] = mat(0, 3);

  xmat[4] = mat(1, 0);
  xmat[5] = mat(1, 1);
  xmat[6] = mat(1, 2);
  xmat[7] = mat(1, 3);

  xmat[8] = mat(2, 0);
  xmat[9] = mat(2, 1);
  xmat[10] = mat(2, 2);
  xmat[11] = mat(2, 3);

  xmat[12] = mat(3, 0);
  xmat[13] = mat(3, 1);
  xmat[14] = mat(3, 2);
  xmat[15] = mat(3, 3);

  return node;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileNode::make_nice_name
//       Access: Protected, Static
//  Description: Transforms the indicated egg name to a name that is
//               acceptable for a node in the X File format.
////////////////////////////////////////////////////////////////////
string XFileNode::
make_nice_name(const string &str) {
  string result;

  string::const_iterator si;
  for (si = str.begin(); si != str.end(); ++si) {
    if (isalnum(*si)) {
      result += (*si);
    } else {
      switch (*si) {
      case '-':
        result += (*si);
        break;
      default:
        result += "_";
      }
    }
  }

  if (str.empty() || isdigit(str[0])) {
    // If the name begins with a digit, or if it
    // is empty, then we must make it begin with
    // something else, like for instance an underscore.
    result = '_' + result;
  }

  return result;
}
