// Filename: softNodeTree.cxx
// Created by:  masad (26Sep03)
// 
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2003, 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 .
//
////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////
// Includes
////////////////////////////////////////////////////////////////////

#include "softNodeTree.h"
#include "softEggGroupUserData.h"
#include "config_softegg.h"
#include "eggGroup.h"
#include "eggTable.h"
#include "eggXfmSAnim.h"
#include "eggData.h"
#include "softToEggConverter.h"
#include "dcast.h"

#include <SAA.h>

////////////////////////////////////////////////////////////////////
//     Function: SoftNodeTree::Constructor
//       Access: Public
//  Description: 
////////////////////////////////////////////////////////////////////
SoftNodeTree::
SoftNodeTree() {
  _root = new SoftNodeDesc(NULL, "----root");
  _root->fullname = "----root";
  _fps = 0.0;
  _use_prefix = 0;
  _search_prefix = NULL;
  _egg_data = (EggData *)NULL;
  _egg_root = (EggGroupNode *)NULL;
  _skeleton_node = (EggGroupNode *)NULL;
}
////////////////////////////////////////////////////////////////////
//     Function: GetName
//       Access: Public
//  Description: Given an element, return a copy of the element's 
//                 name WITHOUT prefix. 
////////////////////////////////////////////////////////////////////
char *SoftNodeTree::
GetName( SAA_Scene *scene, SAA_Elem *element ) {
  int nameLen;
  char *name;
  
  // get the name
  SAA_elementGetNameLength( scene, element, &nameLen ); 
  name = new char[++nameLen];
  SAA_elementGetName( scene, element, nameLen, name );

  return name;
}

////////////////////////////////////////////////////////////////////
//     Function: GetFullName
//       Access: Public
//  Description: Given an element, return a copy of the element's 
//                 name complete with prefix. 
////////////////////////////////////////////////////////////////////
char *SoftNodeTree::
GetFullName( SAA_Scene *scene, SAA_Elem *element )
{
  int nameLen, prefixLen;
  char *name, *prefix;

  // get the name length
  SAA_elementGetNameLength( scene, element, &nameLen );
  // get the prefix length
  SAA_elementGetPrefixLength( scene, element, &prefixLen );
  // allocate the array to hold name
  name = new char[++nameLen];
  // allocate the array to hold prefix and length + hyphen
  prefix = new char[++prefixLen + nameLen + 4];
  // get the name
  SAA_elementGetName( scene, element, nameLen, name );
  // get the prefix
  SAA_elementGetPrefix( scene, element, prefixLen, prefix );
  // add 'em together
  strcat(prefix, "-");
  strcat(prefix, name);

  // return string
  return prefix;
}

////////////////////////////////////////////////////////////////////
//     Function: GetModelNoteInfo
//       Access: Public
//  Description: Given an element, return a string containing the
//               contents of its MODEL NOTE entry 
////////////////////////////////////////////////////////////////////
char *SoftNodeTree::
GetModelNoteInfo( SAA_Scene *scene, SAA_Elem *model ) {
  int size;
  char *modelNote = NULL;
  SAA_Boolean bigEndian;

  SAA_elementGetUserDataSize( scene, model, "MNOT", &size );

  if ( size != 0 ) {
    // allocate modelNote string
    modelNote = new char[size + 1];
    
    // get ModelNote data from this model
    SAA_elementGetUserData( scene, model, "MNOT", size,
                            &bigEndian, (void *)modelNote );
    
    //strip off newline, if present
    char *eol = (char *)memchr( modelNote, '\n', size );
    if ( eol != NULL)
      *eol = '\0';
    else
      modelNote[size] = '\0';

    softegg_cat.spam() << "\nmodelNote = " << modelNote << endl;
  }
  
  return modelNote;
}

////////////////////////////////////////////////////////////////////
//     Function: GetRootName
//       Access: Public
//  Description: Given a string, return a copy of the string up to
//                 the first occurence of '-'. 
////////////////////////////////////////////////////////////////////
char *SoftNodeTree::
GetRootName( const char *name ) {
  const char *hyphen;
  char *root;
  int len;
  
  hyphen = strchr( name, '-' );
  len = hyphen-name;
  
  if ( (hyphen != NULL) && len ) {
    root = new char[len+1];
    strncpy( root, name, len );
    root[len] = '\0';
  }
  else {
    root = new char[strlen(name)+1];
    strcpy( root, name );
  }
  return( root );
}

////////////////////////////////////////////////////////////////////
//     Function: SoftNodeTree::build_complete_hierarchy
//       Access: Public
//  Description: Walks through the complete Soft hierarchy and builds
//               up the corresponding tree.
////////////////////////////////////////////////////////////////////
bool SoftNodeTree::
build_complete_hierarchy(SAA_Scene &scene, SAA_Database &database) {
  SI_Error status;
  SoftNodeDesc *node;

  // Get the entire Soft scene.
  int numModels;
  SAA_Elem *models;

  SAA_sceneGetNbModels( &scene, &numModels ); 
  softegg_cat.spam() << "Scene has " << numModels << " model(s)...\n";
  
  // This while loop walks through the entire Soft hierarchy, one
  // node at a time. 
  bool all_ok = true;
  if ( numModels ) {
    // allocate array of models
    models = (SAA_Elem *) new SAA_Elem[numModels];
    if ( models != NULL ) {
      if ((status = SAA_sceneGetModels( &scene, numModels, models )) != SI_SUCCESS) {
        return false;
      }
      for ( int i = 0; i < numModels; i++ ) {
        int level;
        status = SAA_elementGetHierarchyLevel( &scene, &models[i], &level );
        softegg_cat.spam() << "model[" << i << "]" << endl;
        softegg_cat.spam() << " level " << level << endl;
        softegg_cat.spam() << " status is " << status << "\n";

        node = build_node(&scene, &models[i]);
        if (!level && node)
          node->set_parent(_root);
      }
    }
  }

  softegg_cat.spam() << "jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj\n";

  // check the nodes that are junk for animation/artist control purposes
  _root->check_junk(false);

  softegg_cat.spam() << "jpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjp\n";

  // check the nodes that are parent of ancestors of a joint
  _root->check_joint_parent();

  softegg_cat.spam() << "pppppppppppppppppppppppppppppppppppppppppppppppppppppppp\n";

  // check the nodes that are pseudo joints
  _root->check_pseudo_joints(false);

  softegg_cat.spam() << "========================================================\n";

  // find _parentJoint for each node
  _root->set_parentJoint(&scene, NULL);

  return all_ok;
}
#if 0
////////////////////////////////////////////////////////////////////
//     Function: SoftNodeTree::build_selected_hierarchy
//       Access: Public
//  Description: Walks through the selected subset of the Soft
//               hierarchy (or the complete hierarchy, if nothing is
//               selected) and builds up the corresponding tree.
////////////////////////////////////////////////////////////////////
bool SoftNodeTree::
build_selected_hierarchy(char *scene_name) {
  MStatus status;

  MItDag dag_iterator(MItDag::kDepthFirst, MFn::kTransform, &status);
  if (!status) {
    status.perror("MItDag constructor");
    return false;
  }

  // Get only the selected geometry.
  MSelectionList selection;
  status = MGlobal::getActiveSelectionList(selection);
  if (!status) {
    status.perror("MGlobal::getActiveSelectionList");
    return false;
  }
  
  // Get the selected geometry only if the selection is nonempty;
  // otherwise, get the whole scene anyway.
  if (selection.isEmpty()) {
    softegg_cat.info()
      << "Selection list is empty.\n";
    return build_complete_hierarchy();
  }

  bool all_ok = true;
  unsigned int length = selection.length();
  for (unsigned int i = 0; i < length; i++) {
    MDagPath root_path;
    status = selection.getDagPath(i, root_path);
    if (!status) {
      status.perror("MSelectionList::getDagPath");
    } else {
      // Now traverse through the selected dag path and all nested
      // dag paths.
      dag_iterator.reset(root_path);
      while (!dag_iterator.isDone()) {
        MDagPath dag_path;
        status = dag_iterator.getPath(dag_path);
        if (!status) {
          status.perror("MItDag::getPath");
        } else {
          build_node(dag_path);
        }
        
        dag_iterator.next();
      }
    }
  }

  if (all_ok) {
    _root->check_pseudo_joints(false);
  }

  return all_ok;
}
#endif
////////////////////////////////////////////////////////////////////
//     Function: SoftNodeTree::get_num_nodes
//       Access: Public
//  Description: Returns the total number of nodes in the hierarchy,
//               not counting the root node.
////////////////////////////////////////////////////////////////////
int SoftNodeTree::
get_num_nodes() const {
  return _nodes.size();
}

////////////////////////////////////////////////////////////////////
//     Function: SoftNodeTree::get_node
//       Access: Public
//  Description: Returns the nth node in the hierarchy, in an
//               arbitrary ordering.
////////////////////////////////////////////////////////////////////
SoftNodeDesc *SoftNodeTree::
get_node(int n) const {
  nassertr(n >= 0 && n < (int)_nodes.size(), NULL);
  return _nodes[n];
}

////////////////////////////////////////////////////////////////////
//     Function: SoftNodeTree::get_node
//       Access: Public
//  Description: Returns the node named 'name' in the hierarchy, in
//               an arbitrary ordering.
////////////////////////////////////////////////////////////////////
SoftNodeDesc *SoftNodeTree::
get_node(string name) const {
  NodesByName::const_iterator ni = _nodes_by_name.find(name);
  if (ni != _nodes_by_name.end())
    return (*ni).second;
  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: SoftNodeTree::clear_egg
//       Access: Public
//  Description: Removes all of the references to generated egg
//               structures from the tree, and prepares the tree for
//               generating new egg structures.
////////////////////////////////////////////////////////////////////
void SoftNodeTree::
clear_egg(EggData *egg_data, EggGroupNode *egg_root, 
          EggGroupNode *skeleton_node) {
  _root->clear_egg();
  _egg_data = egg_data;
  _egg_root = egg_root;
  _skeleton_node = skeleton_node;
}

////////////////////////////////////////////////////////////////////
//     Function: SoftNodeTree::get_egg_group
//       Access: Public
//  Description: Returns the EggGroupNode corresponding to the group
//               or joint for the indicated node.  Creates the group
//               node if it has not already been created.
////////////////////////////////////////////////////////////////////
EggGroup *SoftNodeTree::
get_egg_group(SoftNodeDesc *node_desc) {
  nassertr(_egg_root != (EggGroupNode *)NULL, NULL);

  // lets print some relationship
  softegg_cat.spam() << " group " << node_desc->get_name() << "(" << node_desc->_egg_group << ")";
  if (node_desc->_parent)
    softegg_cat.spam() << " parent " << node_desc->_parent->get_name() << "(" << node_desc->_parent << ")";
  else
    softegg_cat.spam() << " parent " << node_desc->_parent;
  softegg_cat.spam() << endl;

  if (node_desc->_egg_group == (EggGroup *)NULL) {
    // We need to make a new group node.
    EggGroup *egg_group;
    
    egg_group = new EggGroup(node_desc->get_name());
    if (node_desc->is_joint()) {
      egg_group->set_group_type(EggGroup::GT_joint);
    }

    if (stec.flatten || (!node_desc->_parentJoint || node_desc->_parentJoint == _root)) {
      // The parent is the root.
      softegg_cat.spam() << "came hereeeee\n";
      _egg_root->add_child(egg_group);
    } else {
      // The parent is another node.
      EggGroup *parent_egg_group = get_egg_group(node_desc->_parentJoint);
      parent_egg_group->add_child(egg_group);
    }

    node_desc->_egg_group = egg_group;
  }
  
  return node_desc->_egg_group;
}

////////////////////////////////////////////////////////////////////
//     Function: SoftNodeTree::get_egg_table
//       Access: Public
//  Description: Returns the EggTable corresponding to the joint
//               for the indicated node.  Creates the table node if it
//               has not already been created.
////////////////////////////////////////////////////////////////////
EggTable *SoftNodeTree::
get_egg_table(SoftNodeDesc *node_desc) {
  nassertr(_skeleton_node != (EggGroupNode *)NULL, NULL);
  nassertr(node_desc->is_joint(), NULL);
  
  // lets print some relationship
  softegg_cat.spam() << " group " << node_desc->get_name() << "(" << node_desc->_egg_group << ")";
  if (node_desc->_parent)
    softegg_cat.spam() << " parent " << node_desc->_parent->get_name() << "(" << node_desc->_parent << ")";
  else
    softegg_cat.spam() << " parent " << node_desc->_parent;
  softegg_cat.spam() << endl;

  if (node_desc->_egg_table == (EggTable *)NULL) {
    softegg_cat.spam() << "creating a new table\n";
    // We need to make a new table node.
    //    nassertr(node_desc->_parent != (SoftNodeDesc *)NULL, NULL);
    
    EggTable *egg_table = new EggTable(node_desc->get_name());
    node_desc->_anim = new EggXfmSAnim("xform", _egg_data->get_coordinate_system());
    node_desc->_anim->set_fps(_fps);
    egg_table->add_child(node_desc->_anim);
    
    if (stec.flatten || (!node_desc->_parentJoint || node_desc->_parentJoint == _root)) {
      //    if (!node_desc->_parent->is_joint()) {
      // The parent is not a joint; put it at the top.
      _skeleton_node->add_child(egg_table);
    } else {
      // The parent is another joint.
      EggTable *parent_egg_table = get_egg_table(node_desc->_parentJoint);
      parent_egg_table->add_child(egg_table);
    }

    node_desc->_egg_table = egg_table;
  }
  
  return node_desc->_egg_table;
}

////////////////////////////////////////////////////////////////////
//     Function: SoftNodeTree::get_egg_anim
//       Access: Public
//  Description: Returns the anim table corresponding to the joint
//               for the indicated node.  Creates the table node if it
//               has not already been created.
////////////////////////////////////////////////////////////////////
EggXfmSAnim *SoftNodeTree::
get_egg_anim(SoftNodeDesc *node_desc) {
  get_egg_table(node_desc);
  return node_desc->_anim;
}

////////////////////////////////////////////////////////////////////
//     Function: SoftNodeTree::handle_null
//       Access: Public
//  Description: Sets joint information for MNILL node
////////////////////////////////////////////////////////////////////
void SoftNodeTree::
handle_null(SAA_Scene *scene, SoftNodeDesc *node_desc, const char *node_name) {
  const char *name = node_name;
  SAA_AlgorithmType    algo;
  SAA_Elem *model = node_desc->get_model();
  
  SAA_modelGetAlgorithm( scene, model, &algo );
  softegg_cat.spam() << " null algorithm: " << algo << endl;
  
  if ( algo == SAA_ALG_INV_KIN ) {
    //    MakeJoint( &scene, lastJoint, lastAnim,  model, name );
    node_desc->set_joint();
    softegg_cat.spam() << " encountered IK root: " << name << endl;
  }
  else if ( algo == SAA_ALG_INV_KIN_LEAF ) {
    //    MakeJoint( &scene, lastJoint, lastAnim, model, name );
    node_desc->set_joint();
    softegg_cat.spam() << " encountered IK leaf: " << name << endl;
  }
  else if ( algo == SAA_ALG_STANDARD ) {
    SAA_Boolean isSkeleton = FALSE;
    softegg_cat.spam() << " encountered Standard null: " << name << endl;

    SAA_modelIsSkeleton( scene, model, &isSkeleton );

    // check to see if this NULL is used as a skeleton
    // or is animated via constraint only ( these nodes are
    // tagged by the animator with the keyword "joint"
    // somewhere in the nodes name)
    if ( isSkeleton || (strstr( name, "joint" ) != NULL) ) {
      //      MakeJoint( &scene, lastJoint, lastAnim, model, name );
      node_desc->set_joint();
      softegg_cat.spam() << " animating Standard null!!!\n";
      softegg_cat.spam() << "isSkeleton: " << isSkeleton << endl;
    }
  }
  else
    softegg_cat.spam() << " encountered some other NULL: " << algo << endl;
}

////////////////////////////////////////////////////////////////////
//     Function: SoftNodeTree::build_node
//       Access: Public
//  Description: Returns a pointer to the node corresponding to the
//               indicated dag_path object, creating it first if
//               necessary.
////////////////////////////////////////////////////////////////////
SoftNodeDesc *SoftNodeTree::
build_node(SAA_Scene *scene, SAA_Elem *model) {
  char *name, *fullname;
  string node_name;
  int numChildren;
  int thisChild;
  SAA_Elem *children;
  SAA_ModelType type;
  SAA_Boolean isSkeleton = FALSE;

  fullname = GetFullName(scene, model);
  if (_use_prefix)
    name = fullname;
  else
    name = GetName(scene, model);

  node_name = name;

  SoftNodeDesc *node_desc = r_build_node(NULL, node_name);

  node_desc->fullname = fullname;
  node_desc->set_model(model);
  SAA_modelIsSkeleton( scene, model, &isSkeleton );

  // find out what type of node we're dealing with
  SAA_modelGetType( scene, node_desc->get_model(), &type );
  
  if (type == SAA_MJNT || isSkeleton || (strstr(node_desc->get_name().c_str(), "joint") != NULL))
    node_desc->set_joint();
  
  // treat the MNILL differently, because it needs to detect and set some joints
  if (type == SAA_MNILL)
    handle_null(scene, node_desc, name);

  if (node_desc->is_joint())
    softegg_cat.spam() << "type: " << type << " isSkeleton: " << isSkeleton << endl;

  // get to the children
  SAA_modelGetNbChildren( scene, model, &numChildren );
  softegg_cat.spam() << " Model " << node_name << " children: " << numChildren << endl;
  
  if ( numChildren ) {
    children = new SAA_Elem[numChildren];
    SAA_modelGetChildren( scene, model, numChildren, children );
    if (!children)
      softegg_cat.info() << "Not enough Memory for children...\n";
    
    for ( thisChild = 0; thisChild < numChildren; thisChild++ ) {
      fullname = GetFullName(scene, &children[thisChild]);
      if (_use_prefix)
        node_name = fullname;
      else
        node_name = GetName(scene, &children[thisChild]);
      
      softegg_cat.spam() << " building child " << thisChild << "...";
      
      SoftNodeDesc *node_child = r_build_node(node_desc, node_name);

      node_child->fullname = fullname;
      node_child->set_model(&children[thisChild]);
      SAA_modelIsSkeleton( scene, &children[thisChild], &isSkeleton );

      // find out what type of node we're dealing with
      SAA_modelGetType( scene, node_child->get_model(), &type );
      
      if (type == SAA_MJNT || isSkeleton || (strstr(node_child->get_name().c_str(), "joint") != NULL))
        node_child->set_joint();

      // treat the MNILL differently, because it needs to detect and set some joints
      if (type == SAA_MNILL)
        handle_null(scene, node_child, node_name.c_str());

      if (node_child->is_joint())
        softegg_cat.spam() << "type: " << type << " isSkeleton: " << isSkeleton << endl;
    }
  }
  return node_desc;
}

////////////////////////////////////////////////////////////////////
//     Function: SoftNodeTree::r_build_node
//       Access: Private
//  Description: The recursive implementation of build_node().
////////////////////////////////////////////////////////////////////
SoftNodeDesc *SoftNodeTree::
r_build_node(SoftNodeDesc *parent_node, const string &name) {
  SoftNodeDesc *node_desc;

  // If we have already encountered this pathname, return the
  // corresponding SoftNodeDesc immediately.
  NodesByName::const_iterator ni = _nodes_by_name.find(name);
  if (ni != _nodes_by_name.end()) {
    softegg_cat.spam() << "  already built node " << (*ni).first;
    node_desc = (*ni).second;
    node_desc->set_parent(parent_node);
    return node_desc;
  }

  // Otherwise, we have to create it.  Do this recursively, so we
  // create each node along the path.
  node_desc = new SoftNodeDesc(parent_node, name);

  softegg_cat.spam() << " node name : " << name << endl;
  _nodes.push_back(node_desc);

  _nodes_by_name.insert(NodesByName::value_type(name, node_desc));

  return node_desc;
}

//
//
//
