// Filename: fltRecord.cxx
// Created by:  drose (24Aug00)
//
////////////////////////////////////////////////////////////////////
//
// 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 "fltRecord.h"
#include "fltRecordReader.h"
#include "fltRecordWriter.h"
#include "fltHeader.h"
#include "fltGroup.h"
#include "fltObject.h"
#include "fltFace.h"
#include "fltCurve.h"
#include "fltMesh.h"
#include "fltLocalVertexPool.h"
#include "fltMeshPrimitive.h"
#include "fltVertexList.h"
#include "fltLOD.h"
#include "fltInstanceDefinition.h"
#include "fltInstanceRef.h"
#include "fltUnsupportedRecord.h"
#include "fltExternalReference.h"
#include "config_flt.h"

#include "dcast.h"
#include "indent.h"
#include "datagramIterator.h"

#include <assert.h>

TypeHandle FltRecord::_type_handle;

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
FltRecord::
FltRecord(FltHeader *header) :
  _header(header)
{
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::Destructor
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
FltRecord::
~FltRecord() {
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::get_num_children
//       Access: Public
//  Description: Returns the number of child records of this record.
//               This reflects the normal scene graph hierarchy.
////////////////////////////////////////////////////////////////////
int FltRecord::
get_num_children() const {
  return _children.size();
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::get_child
//       Access: Public
//  Description: Returns the nth child of this record.
////////////////////////////////////////////////////////////////////
FltRecord *FltRecord::
get_child(int n) const {
  nassertr(n >= 0 && n < (int)_children.size(), (FltRecord *)NULL);
  return _children[n];
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::clear_children
//       Access: Public
//  Description: Removes all children from this record.
////////////////////////////////////////////////////////////////////
void FltRecord::
clear_children() {
  _children.clear();
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::add_child
//       Access: Public
//  Description: Adds a new child to the end of the list of children
//               for this record.
////////////////////////////////////////////////////////////////////
void FltRecord::
add_child(FltRecord *child) {
  _children.push_back(child);
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::get_num_subfaces
//       Access: Public
//  Description: Returns the number of subface records of this record.
//               Normally, subfaces will only be present on object
//               records, although it is logically possible for them to
//               appear anywhere.
////////////////////////////////////////////////////////////////////
int FltRecord::
get_num_subfaces() const {
  return _subfaces.size();
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::get_subface
//       Access: Public
//  Description: Returns the nth subface of this record.
////////////////////////////////////////////////////////////////////
FltRecord *FltRecord::
get_subface(int n) const {
  nassertr(n >= 0 && n < (int)_subfaces.size(), (FltRecord *)NULL);
  return _subfaces[n];
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::clear_subfaces
//       Access: Public
//  Description: Removes all subfaces from this record.
////////////////////////////////////////////////////////////////////
void FltRecord::
clear_subfaces() {
  _subfaces.clear();
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::add_subface
//       Access: Public
//  Description: Adds a new subface to the end of the list of subfaces
//               for this record.
////////////////////////////////////////////////////////////////////
void FltRecord::
add_subface(FltRecord *subface) {
  _subfaces.push_back(subface);
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::get_num_extensions
//       Access: Public
//  Description: Returns the number of extension attribute records for
//               this object.  These are auxiliary nodes, presumably
//               of type FO_extension, that have some local meaning to
//               the object.
////////////////////////////////////////////////////////////////////
int FltRecord::
get_num_extensions() const {
  return _extensions.size();
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::get_extension
//       Access: Public
//  Description: Returns the nth extension of this record.
////////////////////////////////////////////////////////////////////
FltRecord *FltRecord::
get_extension(int n) const {
  nassertr(n >= 0 && n < (int)_extensions.size(), (FltRecord *)NULL);
  return _extensions[n];
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::clear_extensions
//       Access: Public
//  Description: Removes all extensions from this record.
////////////////////////////////////////////////////////////////////
void FltRecord::
clear_extensions() {
  _extensions.clear();
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::add_extension
//       Access: Public
//  Description: Adds a new extension to the end of the list of
//               extensions for this record.  This should be a record
//               of type FO_extension.
////////////////////////////////////////////////////////////////////
void FltRecord::
add_extension(FltRecord *extension) {
  _extensions.push_back(extension);
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::get_num_ancillary
//       Access: Public
//  Description: Returns the number of unsupported ancillary records
//               of this record.  These are ancillary records that
//               appeared following this record in the flt file but that
//               aren't directly understood by the flt
//               loader--normally, an ancillary record is examined and
//               decoded on the spot, and no pointer to it is kept.
////////////////////////////////////////////////////////////////////
int FltRecord::
get_num_ancillary() const {
  return _ancillary.size();
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::get_ancillary
//       Access: Public
//  Description: Returns the nth unsupported ancillary record of this
//               record.  See get_num_ancillary().
////////////////////////////////////////////////////////////////////
FltRecord *FltRecord::
get_ancillary(int n) const {
  nassertr(n >= 0 && n < (int)_ancillary.size(), (FltRecord *)NULL);
  return _ancillary[n];
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::clear_ancillary
//       Access: Public
//  Description: Removes all unsupported ancillary records from this
//               record.  See get_num_ancillary().
////////////////////////////////////////////////////////////////////
void FltRecord::
clear_ancillary() {
  _ancillary.clear();
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::add_ancillary
//       Access: Public
//  Description: Adds a new unsupported ancillary record to the end of
//               the list of ancillary records for this record.  This
//               record will be written to the flt file following this
//               record, without attempting to understand what is in it.
//
//               Normally, there is no reason to use this function; if
//               the data stored in the FltRecord requires one or more
//               ancillary record, the appropriate records will
//               automatically be generated when the record is written.
//               This function is only required to output a record
//               whose type is not supported by the flt loader.  But
//               it would be better to extend the flt loader to know
//               about this new kind of data record.
////////////////////////////////////////////////////////////////////
void FltRecord::
add_ancillary(FltRecord *ancillary) {
  _ancillary.push_back(ancillary);
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::has_comment
//       Access: Public
//  Description: Returns true if this record has a nonempty comment,
//               false otherwise.
////////////////////////////////////////////////////////////////////
bool FltRecord::
has_comment() const {
  return !_comment.empty();
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::get_comment
//       Access: Public
//  Description: Retrieves the comment for this record, or empty
//               string if the record has no comment.
////////////////////////////////////////////////////////////////////
const string &FltRecord::
get_comment() const {
  return _comment;
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::clear_comment
//       Access: Public
//  Description: Removes the comment for this record.
////////////////////////////////////////////////////////////////////
void FltRecord::
clear_comment() {
  _comment = "";
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::set_comment
//       Access: Public
//  Description: Changes the comment for this record.
////////////////////////////////////////////////////////////////////
void FltRecord::
set_comment(const string &comment) {
  _comment = comment;
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::check_remaining_size
//       Access: Public
//  Description: Checks that the iterator has no bytes left, as it
//               should at the end of a successfully read record.  If
//               there *are* remaining bytes, print a warning message
//               but otherwise don't worry about it.
//
//               If we are attempting to read a flt file whose version
//               is newer than the newest this program understands,
//               don't even print a warning message, since this is
//               exactly the sort of thing we expect.
////////////////////////////////////////////////////////////////////
void FltRecord::
check_remaining_size(const DatagramIterator &di, const string &name) const {
  if (di.get_remaining_size() == 0) {
    return;
  }

  if (_header->get_flt_version() <= _header->max_flt_version()) {
    nout << "Warning!  Ignoring extra " << di.get_remaining_size()
         << " bytes at the end of a ";
    if (name.empty()) {
      nout << get_type();
    } else {
      nout << name;
    }
    nout << " record.\n";
  }
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::apply_converted_filenames
//       Access: Public, Virtual
//  Description: Walks the hierarchy at this record and below and
//               copies the _converted_filename record into the
//               _orig_filename record, so the flt file will be
//               written out with the converted filename instead of
//               what was originally read in.
////////////////////////////////////////////////////////////////////
void FltRecord::
apply_converted_filenames() {
  Records::const_iterator ci;
  for (ci = _subfaces.begin(); ci != _subfaces.end(); ++ci) {
    (*ci)->apply_converted_filenames();
  }
  for (ci = _children.begin(); ci != _children.end(); ++ci) {
    (*ci)->apply_converted_filenames();
  }
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::output
//       Access: Public
//  Description: Writes a quick one-line description of the record, but
//               not its children.  This is a human-readable
//               description, primarily for debugging; to write a flt
//               file, use FltHeader::write_flt().
////////////////////////////////////////////////////////////////////
void FltRecord::
output(ostream &out) const {
  out << get_type();
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::write
//       Access: Public
//  Description: Writes a multiple-line description of the record and
//               all of its children.  This is a human-readable
//               description, primarily for debugging; to write a flt
//               file, use FltHeader::write_flt().
////////////////////////////////////////////////////////////////////
void FltRecord::
write(ostream &out, int indent_level) const {
  indent(out, indent_level) << *this;
  write_children(out, indent_level);
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::write_children
//       Access: Protected
//  Description: Assuming the current write position has been left at
//               the end of the last line of the record description,
//               writes out the list of children.
////////////////////////////////////////////////////////////////////
void FltRecord::
write_children(ostream &out, int indent_level) const {
  if (!_ancillary.empty()) {
    out << " + " << _ancillary.size() << " ancillary";
  }
  if (!_extensions.empty()) {
    out << " + " << _extensions.size() << " extensions";
  }
  if (!_subfaces.empty()) {
    out << " [";
    Records::const_iterator ci;
    for (ci = _subfaces.begin(); ci != _subfaces.end(); ++ci) {
      out << " " << *(*ci);
    }
    out << " ]";
  }
  if (!_children.empty()) {
    out << " {\n";
    Records::const_iterator ci;
    for (ci = _children.begin(); ci != _children.end(); ++ci) {
      (*ci)->write(out, indent_level + 2);
    }
    indent(out, indent_level) << "}\n";
  } else {
    out << "\n";
  }
}

  /*
  virtual void write(ostream &out) const;
  virtual void build_record(Datagram &datagram) const;
  */

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::is_ancillary
//       Access: Protected, Static
//  Description: Returns true if the indicated opcode corresponds to
//               an ancillary record type, false otherwise.  In
//               general, this function is used to identify ancillary
//               records that are not presently supported by the
//               FltReader; these will be ignored.  Normally,
//               ancillary records will be detected and processed by
//               extract_ancillary().
////////////////////////////////////////////////////////////////////
bool FltRecord::
is_ancillary(FltOpcode opcode) {
  switch (opcode) {
  case FO_comment:
  case FO_long_id:
  case FO_multitexture:
  case FO_uv_list:
  case FO_replicate:
  case FO_road_zone:
  case FO_transform_matrix:
  case FO_rotate_about_edge:
  case FO_translate:
  case FO_scale:
  case FO_rotate_about_point:
  case FO_rotate_and_scale:
  case FO_put:
  case FO_general_matrix:
  case FO_vector:
  case FO_bounding_box:
  case FO_bounding_sphere:
  case FO_bounding_cylinder:
  case FO_bv_center:
  case FO_bv_orientation:
  case FO_local_vertex_pool:
  case FO_cat_data:

  case FO_14_material_palette:
  case FO_vertex_palette:
  case FO_vertex_c:
  case FO_vertex_cn:
  case FO_vertex_cnu:
  case FO_vertex_cu:
  case FO_color_palette:
  case FO_name_table:
  case FO_15_material:
  case FO_texture:
  case FO_eyepoint_palette:
  case FO_light_definition:
  case FO_texture_map_palette:
    return true;

  case FO_header:
  case FO_mesh:
  case FO_mesh_primitive:
  case FO_group:
  case FO_object:
  case FO_face:
  case FO_light_point:
  case FO_dof:
  case FO_vertex_list:
  case FO_morph_list:
  case FO_bsp:
  case FO_external_ref:
  case FO_lod:
  case FO_sound:
  case FO_light_source:
  case FO_road_segment:
  case FO_road_construction:
  case FO_road_path:
  case FO_clip_region:
  case FO_text:
  case FO_switch:
  case FO_cat:
  case FO_extension:
  case FO_curve:
    return false;

  case FO_push:
  case FO_pop:
  case FO_push_face:
  case FO_pop_face:
  case FO_push_attribute:
  case FO_pop_attribute:
  case FO_push_extension:
  case FO_pop_extension:
  case FO_instance:
  case FO_instance_ref:
    return false;

  default:
    nout << "Don't know whether " << opcode << " is ancillary.\n";
    return false;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::create_new_record
//       Access: Protected
//  Description: Creates a new FltRecord corresponding to the opcode.
//               If the opcode is unknown, creates a
//               FltUnsupportedRecord.
////////////////////////////////////////////////////////////////////
FltRecord *FltRecord::
create_new_record(FltOpcode opcode) const {
  switch (opcode) {
  case FO_group:
    return new FltGroup(_header);

  case FO_object:
    return new FltObject(_header);

  case FO_face:
    return new FltFace(_header);

  case FO_curve:
    return new FltCurve(_header);

  case FO_mesh:
    return new FltMesh(_header);

  case FO_local_vertex_pool:
    return new FltLocalVertexPool(_header);

  case FO_mesh_primitive:
    return new FltMeshPrimitive(_header);

  case FO_vertex_list:
    return new FltVertexList(_header);

  case FO_lod:
    return new FltLOD(_header);

  case FO_instance:
    return new FltInstanceDefinition(_header);

  case FO_instance_ref:
    return new FltInstanceRef(_header);

  case FO_external_ref:
    return new FltExternalReference(_header);

  case FO_vector:
    return new FltVectorRecord(_header);

  default:
    nout << "Ignoring unsupported record " << opcode << "\n";
    return new FltUnsupportedRecord(_header);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::read_record_and_children
//       Access: Protected
//  Description: Extracts this record information from the current
//               record presented in the reader, then advances the
//               reader and continues to read any children, if
//               present.  On return, the reader is position on the
//               next sibling record to this record.
//
//               Returns FE_ok if successful, otherwise on error.
////////////////////////////////////////////////////////////////////
FltError FltRecord::
read_record_and_children(FltRecordReader &reader) {
  if (!extract_record(reader)) {
    nout << "Could not extract record for " << *this << "\n";
    assert(!flt_error_abort);
    return FE_invalid_record;
  }
  FltError result = reader.advance();
  if (result == FE_end_of_file) {
    return FE_ok;
  } else if (result != FE_ok) {
    return result;
  }

  while (true) {
    if (extract_ancillary(reader)) {
      // Ok, a known ancillary record.  Fine.

    } else if (reader.get_opcode() == FO_push) {
      // A push begins a new list of children.
      result = reader.advance();
      if (result != FE_ok) {
        return result;
      }

      while (reader.get_opcode() != FO_pop) {
        PT(FltRecord) child = create_new_record(reader.get_opcode());
        FltError result = child->read_record_and_children(reader);
        if (result != FE_ok) {
          return result;
        }

        if (child->is_of_type(FltInstanceDefinition::get_class_type())) {
          // A special case for an instance definition.  These
          // shouldn't appear in the hierarchy, but should instead be
          // added directly to the header.
          _header->add_instance(DCAST(FltInstanceDefinition, child));

        } else {
          add_child(child);
        }

        if (reader.eof() || reader.error()) {
          assert(!flt_error_abort);
          return FE_end_of_file;
        }
      }

    } else if (reader.get_opcode() == FO_push_face) {
      // A push subface begins a new list of subfaces.
      result = reader.advance();
      if (result != FE_ok) {
        return result;
      }

      while (reader.get_opcode() != FO_pop_face) {
        PT(FltRecord) subface = create_new_record(reader.get_opcode());
        FltError result = subface->read_record_and_children(reader);
        if (result != FE_ok) {
          return result;
        }
        add_subface(subface);
        if (reader.eof() || reader.error()) {
          assert(!flt_error_abort);
          return FE_end_of_file;
        }
      }

    } else if (reader.get_opcode() == FO_push_extension) {
      // A push extension begins a new list of extensions.
      result = reader.advance();
      if (result != FE_ok) {
        return result;
      }

      while (reader.get_opcode() != FO_pop_extension) {
        PT(FltRecord) extension = create_new_record(reader.get_opcode());
        FltError result = extension->read_record_and_children(reader);
        if (result != FE_ok) {
          return result;
        }
        add_extension(extension);
        if (reader.eof() || reader.error()) {
          assert(!flt_error_abort);
          return FE_end_of_file;
        }
      }

    } else if (is_ancillary(reader.get_opcode())) {
      // An unsupported ancillary record.  Skip it.
      PT(FltRecord) ancillary = create_new_record(reader.get_opcode());
      ancillary->extract_record(reader);
      _ancillary.push_back(ancillary);

    } else {
      // None of the above: we're done.
      return FE_ok;
    }

    // Skip to the next record.  If that's the end, fine.
    result = reader.advance(true);
    if (reader.eof() || result != FE_ok) {
      return result;
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::extract_record
//       Access: Protected, Virtual
//  Description: Fills in the information in this record based on the
//               information given in the indicated datagram, whose
//               opcode has already been read.  Returns true on
//               success, false if the datagram is invalid.
////////////////////////////////////////////////////////////////////
bool FltRecord::
extract_record(FltRecordReader &) {
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::extract_ancillary
//       Access: Protected, Virtual
//  Description: Checks whether the given record, which follows this
//               record sequentially in the file, is an ancillary record
//               of this record.  If it is, extracts the relevant
//               information and returns true; otherwise, leaves it
//               alone and returns false.
////////////////////////////////////////////////////////////////////
bool FltRecord::
extract_ancillary(FltRecordReader &reader) {
  if (reader.get_opcode() == FO_comment) {
    _comment = reader.get_iterator().get_remaining_bytes();
    return true;
  }

  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::write_record_and_children
//       Access: Protected, Virtual
//  Description: Writes this record out to the flt file, along with all
//               of its ancillary records and children records.  Returns
//               FE_ok on success, or something else on error.
////////////////////////////////////////////////////////////////////
FltError FltRecord::
write_record_and_children(FltRecordWriter &writer) const {
  // First, write the record.
  if (!build_record(writer)) {
    assert(!flt_error_abort);
    return FE_bad_data;
  }

  FltError result = writer.advance();
  if (result != FE_ok) {
    return result;
  }

  // Then the ancillary data.
  result = write_ancillary(writer);
  if (result != FE_ok) {
    return result;
  }
  Records::const_iterator ci;
  for (ci = _ancillary.begin(); ci != _ancillary.end(); ++ci) {
    if (!(*ci)->build_record(writer)) {
      assert(!flt_error_abort);
      return FE_bad_data;
    }
    result = writer.advance();
    if (result != FE_ok) {
      return result;
    }
  }

  // Any extensions?
  if (!_extensions.empty()) {
    result = writer.write_record(FO_push_face);
    if (result != FE_ok) {
      return result;
    }

    for (ci = _extensions.begin(); ci != _extensions.end(); ++ci) {
      (*ci)->write_record_and_children(writer);
    }

    result = writer.write_record(FO_pop_face);
    if (result != FE_ok) {
      return result;
    }
  }

  // Finally, write all the children.
  if (!_children.empty()) {
    result = writer.write_record(FO_push);
    if (result != FE_ok) {
      return result;
    }

    for (ci = _children.begin(); ci != _children.end(); ++ci) {
      (*ci)->write_record_and_children(writer);
    }

    result = writer.write_record(FO_pop);
    if (result != FE_ok) {
      return result;
    }
  }

  // We must write subfaces *after* the list of children, or Creator
  // will crash trying to load the file.
  if (!_subfaces.empty()) {
    result = writer.write_record(FO_push_face);
    if (result != FE_ok) {
      return result;
    }

    for (ci = _subfaces.begin(); ci != _subfaces.end(); ++ci) {
      (*ci)->write_record_and_children(writer);
    }

    result = writer.write_record(FO_pop_face);
    if (result != FE_ok) {
      return result;
    }
  }

  return FE_ok;
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::build_record
//       Access: Protected, Virtual
//  Description: Fills up the current record on the FltRecordWriter with
//               data for this record, but does not advance the
//               writer.  Returns true on success, false if there is
//               some error.
////////////////////////////////////////////////////////////////////
bool FltRecord::
build_record(FltRecordWriter &) const {
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: FltRecord::write_ancillary
//       Access: Protected, Virtual
//  Description: Writes whatever ancillary records are required for
//               this record.  Returns FE_ok on success, or something
//               else if there is some error.
////////////////////////////////////////////////////////////////////
FltError FltRecord::
write_ancillary(FltRecordWriter &writer) const {
  if (!_comment.empty()) {
    Datagram dc(_comment);
    FltError result = writer.write_record(FO_comment, dc);
    if (result != FE_ok) {
      return result;
    }
  }
  return FE_ok;
}
