// Filename: dcPacker.cxx
// Created by:  drose (15Jun04)
//
////////////////////////////////////////////////////////////////////
//
// 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 "dcPacker.h"
#include "dcSwitch.h"
#include "dcParserDefs.h"
#include "dcLexerDefs.h"
#include "dcClassParameter.h"
#include "dcSwitchParameter.h"
#include "dcClass.h"

DCPacker::StackElement *DCPacker::StackElement::_deleted_chain = NULL;
int DCPacker::StackElement::_num_ever_allocated = 0;

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::Constructor
//       Access: Published
//  Description: 
////////////////////////////////////////////////////////////////////
DCPacker::
DCPacker() {
  _mode = M_idle;
  _unpack_data = NULL;
  _unpack_length = 0;
  _owns_unpack_data = false;
  _unpack_p = 0;
  _live_catalog = NULL;
  _pack_error = false;
  _range_error = false;
  _stack = NULL;
  
  clear();
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::Destructor
//       Access: Published
//  Description: 
////////////////////////////////////////////////////////////////////
DCPacker::
~DCPacker() {
  clear_data();
  clear();
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::begin_pack
//       Access: Published
//  Description: Begins a packing session.  The parameter is the DC
//               object that describes the packing format; it may be a
//               DCParameter or DCField.
//
//               Unless you call clear_data() between sessions,
//               multiple packing sessions will be concatenated
//               together into the same buffer.  If you wish to add
//               bytes to the buffer between packing sessions, use
//               append_data() or get_write_pointer().
////////////////////////////////////////////////////////////////////
void DCPacker::
begin_pack(const DCPackerInterface *root) {
  nassertv(_mode == M_idle);
  
  _mode = M_pack;
  _pack_error = false;
  _range_error = false;

  _root = root;
  _catalog = NULL;
  _live_catalog = NULL;

  _current_field = root;
  _current_parent = NULL;
  _current_field_index = 0;
  _num_nested_fields = 0;
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::end_pack
//       Access: Published, Virtual
//  Description: Finishes a packing session.
//
//               The return value is true on success, or false if
//               there has been some error during packing.
////////////////////////////////////////////////////////////////////
bool DCPacker::
end_pack() {
  nassertr(_mode == M_pack, false);
  
  _mode = M_idle;

  if (_stack != NULL || _current_field != NULL || _current_parent != NULL) {
    _pack_error = true;
  }

  clear();

  return !had_error();
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::set_unpack_data
//       Access: Public
//  Description: Sets up the unpack_data pointer.  You may call this
//               before calling the version of begin_unpack() that
//               takes only one parameter.
////////////////////////////////////////////////////////////////////
void DCPacker::
set_unpack_data(const string &data) {
  nassertv(_mode == M_idle);

  char *buffer = new char[data.length()];
  memcpy(buffer, data.data(), data.length());
  set_unpack_data(buffer, data.length(), true);
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::set_unpack_data
//       Access: Public
//  Description: Sets up the unpack_data pointer.  You may call this
//               before calling the version of begin_unpack() that
//               takes only one parameter.
////////////////////////////////////////////////////////////////////
void DCPacker::
set_unpack_data(const char *unpack_data, size_t unpack_length, 
                bool owns_unpack_data) {
  nassertv(_mode == M_idle);

  if (_owns_unpack_data) {
    delete[] _unpack_data;
  }
  _unpack_data = unpack_data;
  _unpack_length = unpack_length;
  _owns_unpack_data = owns_unpack_data;
  _unpack_p = 0;
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::begin_unpack
//       Access: Public
//  Description: Begins an unpacking session.  You must have
//               previously called set_unpack_data() to specify a
//               buffer to unpack.
//
//               If there was data left in the buffer after a previous
//               begin_unpack() .. end_unpack() session, the new
//               session will resume from the current point.  This
//               method may be used, therefore, to unpack a sequence
//               of objects from the same buffer.
////////////////////////////////////////////////////////////////////
void DCPacker::
begin_unpack(const DCPackerInterface *root) {
  nassertv(_mode == M_idle);
  nassertv(_unpack_data != NULL);
  
  _mode = M_unpack;
  _pack_error = false;
  _range_error = false;

  _root = root;
  _catalog = NULL;
  _live_catalog = NULL;

  _current_field = root;
  _current_parent = NULL;
  _current_field_index = 0;
  _num_nested_fields = 0;
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::end_unpack
//       Access: Published
//  Description: Finishes the unpacking session.
//
//               The return value is true on success, or false if
//               there has been some error during unpacking (or if all
//               fields have not been unpacked).
////////////////////////////////////////////////////////////////////
bool DCPacker::
end_unpack() {
  nassertr(_mode == M_unpack, false);
  
  _mode = M_idle;

  if (_stack != NULL || _current_field != NULL || _current_parent != NULL) {
    // This happens if we have not unpacked all of the fields.
    // However, this is not an error if we have called seek() during
    // the unpack session (in which case the _catalog will be
    // non-NULL).  On the other hand, if the catalog is still NULL,
    // then we have never called seek() and it is an error not to
    // unpack all values.
    if (_catalog == (DCPackerCatalog *)NULL) {
      _pack_error = true;
    }
  }

  clear();

  return !had_error();
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::begin_repack
//       Access: Public
//  Description: Begins a repacking session.  You must have previously
//               called set_unpack_data() to specify a buffer to
//               unpack.
//
//               Unlike begin_pack() or begin_unpack() you may not
//               concatenate the results of multiple begin_repack()
//               sessions in one buffer.
//
//               Also, unlike in packing or unpacking modes, you may
//               not walk through the fields from beginning to end, or
//               even pack two consecutive fields at once.  Instead,
//               you must call seek() for each field you wish to
//               modify and pack only that one field; then call seek()
//               again to modify another field.
////////////////////////////////////////////////////////////////////
void DCPacker::
begin_repack(const DCPackerInterface *root) {
  nassertv(_mode == M_idle);
  nassertv(_unpack_data != NULL);
  nassertv(_unpack_p == 0);
  
  _mode = M_repack;
  _pack_error = false;
  _range_error = false;
  _pack_data.clear();

  // In repack mode, we immediately get the catalog, since we know
  // we'll need it.
  _root = root;
  _catalog = _root->get_catalog();
  _live_catalog = _catalog->get_live_catalog(_unpack_data, _unpack_length);
  if (_live_catalog == NULL) {
    _pack_error = true;
  }

  // We don't begin at the first field in repack mode.  Instead, you
  // must explicitly call seek().
  _current_field = NULL;
  _current_parent = NULL;
  _current_field_index = 0;
  _num_nested_fields = 0;
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::end_repack
//       Access: Published
//  Description: Finishes the repacking session.
//
//               The return value is true on success, or false if
//               there has been some error during repacking (or if all
//               fields have not been repacked).
////////////////////////////////////////////////////////////////////
bool DCPacker::
end_repack() {
  nassertr(_mode == M_repack, false);

  // Put the rest of the data onto the pack stream.
  _pack_data.append_data(_unpack_data + _unpack_p, _unpack_length - _unpack_p);
  
  _mode = M_idle;
  clear();

  return !had_error();
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::seek
//       Access: Published
//  Description: Sets the current unpack (or repack) position to the
//               named field.  In unpack mode, the next call to
//               unpack_*() or push() will begin to read the named
//               field.  In repack mode, the next call to pack_*() or
//               push() will modify the named field.
//
//               Returns true if successful, false if the field is not
//               known (or if the packer is in an invalid mode).
////////////////////////////////////////////////////////////////////
bool DCPacker::
seek(const string &field_name) {
  if (_catalog == (DCPackerCatalog *)NULL) {
    _catalog = _root->get_catalog();
    _live_catalog = _catalog->get_live_catalog(_unpack_data, _unpack_length);
  }
  nassertr(_catalog != (DCPackerCatalog *)NULL, false);
  if (_live_catalog == NULL) {
    _pack_error = true;
    return false;
  }
  
  int seek_index = _live_catalog->find_entry_by_name(field_name);
  if (seek_index < 0) {
    // The field was not known.
    _pack_error = true;
    return false;
  }

  return seek(seek_index);
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::seek
//       Access: Published
//  Description: Seeks to the field indentified by seek_index, which
//               was returned by an earlier call to
//               DCField::find_seek_index() to get the index of some
//               nested field.  Also see the version of seek() that
//               accepts a field name.
//
//               Returns true if successful, false if the field is not
//               known (or if the packer is in an invalid mode).
////////////////////////////////////////////////////////////////////
bool DCPacker::
seek(int seek_index) {
  if (_catalog == (DCPackerCatalog *)NULL) {
    _catalog = _root->get_catalog();
    _live_catalog = _catalog->get_live_catalog(_unpack_data, _unpack_length);
  }
  nassertr(_catalog != (DCPackerCatalog *)NULL, false);
  if (_live_catalog == NULL) {
    _pack_error = true;
    return false;
  }
  
  if (_mode == M_unpack) {
    const DCPackerCatalog::Entry &entry = _live_catalog->get_entry(seek_index);

    // If we are seeking, we don't need to remember our current stack
    // position.
    clear_stack();
    _current_field = entry._field;
    _current_parent = entry._parent;
    _current_field_index = entry._field_index;
    _num_nested_fields = _current_parent->get_num_nested_fields();
    _unpack_p = _live_catalog->get_begin(seek_index);

    // We don't really need _push_marker and _pop_marker now, except
    // that we should set _push_marker in case we have just seeked to
    // a switch parameter, and we should set _pop_marker to 0 just so
    // it won't get in the way.
    _push_marker = _unpack_p;
    _pop_marker = 0;

    return true;

  } else if (_mode == M_repack) {
    nassertr(_catalog != (DCPackerCatalog *)NULL, false);

    if (_stack != NULL || _current_field != NULL) {
      // It is an error to reseek while the stack is nonempty--that
      // means we haven't finished packing the current field.
      _pack_error = true;
      return false;
    }
    const DCPackerCatalog::Entry &entry = _live_catalog->get_entry(seek_index);

    if (entry._parent->as_switch_parameter() != (DCSwitchParameter *)NULL) {
      // If the parent is a DCSwitch, that can only mean that the
      // seeked field is a switch parameter.  We can't support seeking
      // to a switch parameter and modifying it directly--what would
      // happen to all of the related fields?  Instead, you'll have to
      // seek to the switch itself and repack the whole entity.
      _pack_error = true;
      return false;
    }

    size_t begin = _live_catalog->get_begin(seek_index);
    if (begin < _unpack_p) {
      // Whoops, we are seeking fields out-of-order.  That means we
      // need to write the entire record and start again. 
      _pack_data.append_data(_unpack_data + _unpack_p, _unpack_length - _unpack_p);
      size_t length = _pack_data.get_length();
      char *buffer = _pack_data.take_data();
      set_unpack_data(buffer, length, true);
      _unpack_p = 0;

      _catalog->release_live_catalog(_live_catalog);
      _live_catalog = _catalog->get_live_catalog(_unpack_data, _unpack_length);

      if (_live_catalog == NULL) {
        _pack_error = true;
        return false;
      }

      begin = _live_catalog->get_begin(seek_index);
    }

    // Now copy the bytes from _unpack_p to begin from the
    // _unpack_data to the _pack_data.  These are the bytes we just
    // skipped over with the call to seek().
    _pack_data.append_data(_unpack_data + _unpack_p, begin - _unpack_p);

    // And set the packer up to pack the indicated field (but no
    // subsequent fields).
    _current_field = entry._field;
    _current_parent = entry._parent;
    _current_field_index = entry._field_index;
    _num_nested_fields = 1;
    _unpack_p = _live_catalog->get_end(seek_index);

    // Set up push_marker and pop_marker so we won't try to advance
    // beyond this field.
    _push_marker = begin;
    _pop_marker = _live_catalog->get_end(seek_index);

    return true;
  }

  // Invalid mode.
  _pack_error = true;
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::push
//       Access: Published
//  Description: Marks the beginning of a nested series of fields.
//
//               This must be called before filling the elements of an
//               array or the individual fields in a structure field.
//               It must also be balanced by a matching pop().
//
//               It is necessary to use push() / pop() only if
//               has_nested_fields() returns true.
////////////////////////////////////////////////////////////////////
void DCPacker::
push() {
  if (!has_nested_fields()) {
    _pack_error = true;

  } else {
    StackElement *element = new StackElement;
    element->_current_parent = _current_parent;
    element->_current_field_index = _current_field_index;
    element->_push_marker = _push_marker;
    element->_pop_marker = _pop_marker;
    element->_next = _stack;
    _stack = element;
    _current_parent = _current_field;


    // Now deal with the length prefix that might or might not be
    // before a sequence of nested fields.
    int num_nested_fields = _current_parent->get_num_nested_fields();
    size_t length_bytes = _current_parent->get_num_length_bytes();
    
    if (_mode == M_pack || _mode == M_repack) {
      // Reserve length_bytes for when we figure out what the length
      // is.
      _push_marker = _pack_data.get_length();
      _pop_marker = 0;
      _pack_data.append_junk(length_bytes);

    } else if (_mode == M_unpack) {
      // Read length_bytes to determine the end of this nested
      // sequence.
      _push_marker = _unpack_p;
      _pop_marker = 0;

      if (length_bytes != 0) {
        if (_unpack_p + length_bytes > _unpack_length) {
          _pack_error = true;

        } else {
          size_t length;
          if (length_bytes == 4) {
            length = DCPackerInterface::do_unpack_uint32
              (_unpack_data + _unpack_p);
            _unpack_p += 4;
          } else {
            length = DCPackerInterface::do_unpack_uint16
              (_unpack_data + _unpack_p);
            _unpack_p += 2;
          }
          _pop_marker = _unpack_p + length;
        
          // The explicit length trumps the number of nested fields
          // reported by get_num_nested_fields().
          if (length == 0) {
            num_nested_fields = 0;
          } else {
            num_nested_fields = _current_parent->calc_num_nested_fields(length);
          }
        }
      }
    } else {
      _pack_error = true;
    }


    // Now point to the first field in the nested range.
    _num_nested_fields = num_nested_fields;
    _current_field_index = 0;

    if (_num_nested_fields >= 0 &&
        _current_field_index >= _num_nested_fields) {
      _current_field = NULL;
      
    } else {
      _current_field = _current_parent->get_nested_field(_current_field_index);
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::pop
//       Access: Published
//  Description: Marks the end of a nested series of fields.
//
//               This must be called to match a previous push() only
//               after all the expected number of nested fields have
//               been packed.  It is an error to call it too early, or
//               too late.
////////////////////////////////////////////////////////////////////
void DCPacker::
pop() {
  if (_current_field != NULL && _num_nested_fields >= 0) {
    // Oops, didn't pack or unpack enough values.
    _pack_error = true;

  } else if (_mode == M_unpack && _pop_marker != 0 && 
             _unpack_p != _pop_marker) {
    // Didn't unpack the right number of values.
    _pack_error = true;
  }

  if (_stack == NULL) {
    // Unbalanced pop().
    _pack_error = true;

  } else {
    if (!_current_parent->validate_num_nested_fields(_current_field_index)) {
      // Incorrect number of nested elements.
      _pack_error = true;
    }

    if (_mode == M_pack || _mode == M_repack) {
      size_t length_bytes = _current_parent->get_num_length_bytes();
      if (length_bytes != 0) {
        // Now go back and fill in the length of the array.
        size_t length = _pack_data.get_length() - _push_marker - length_bytes;
        if (length_bytes == 4) {
          DCPackerInterface::do_pack_uint32
            (_pack_data.get_rewrite_pointer(_push_marker, 4), length);
        } else {
          DCPackerInterface::validate_uint_limits(length, 16, _range_error);
          DCPackerInterface::do_pack_uint16
            (_pack_data.get_rewrite_pointer(_push_marker, 2), length);
        }
      }
    }

    _current_field = _current_parent;
    _current_parent = _stack->_current_parent;
    _current_field_index = _stack->_current_field_index;
    _push_marker = _stack->_push_marker;
    _pop_marker = _stack->_pop_marker;
    _num_nested_fields = (_current_parent == NULL) ? 0 : _current_parent->get_num_nested_fields();

    StackElement *next = _stack->_next;
    delete _stack;
    _stack = next;
  }

  advance();
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::pack_default_value
//       Access: Published
//  Description: Adds the default value for the current element into
//               the stream.  If no default has been set for the
//               current element, creates a sensible default.
////////////////////////////////////////////////////////////////////
void DCPacker::
pack_default_value() {
  nassertv(_mode == M_pack || _mode == M_repack);
  if (_current_field == NULL) {
    _pack_error = true;
  } else {
    if (_current_field->pack_default_value(_pack_data, _pack_error)) {
      advance();

    } else {
      // If the single field didn't know how to pack a default value,
      // try packing nested fields.
      push();
      while (more_nested_fields()) {
        pack_default_value();
      }
      pop();
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::unpack_validate
//       Access: Published
//  Description: Internally unpacks the current numeric or string
//               value and validates it against the type range limits,
//               but does not return the value.  If the current field
//               contains nested fields, validates all of them.
////////////////////////////////////////////////////////////////////
void DCPacker::
unpack_validate() {
  nassertv(_mode == M_unpack);
  if (_current_field == NULL) {
    _pack_error = true;

  } else {
    if (_current_field->unpack_validate(_unpack_data, _unpack_length, _unpack_p,
                                        _pack_error, _range_error)) {
      advance();
    } else {
      // If the single field couldn't be validated, try validating
      // nested fields.
      push();
      while (more_nested_fields()) {
        unpack_validate();
      }
      pop();
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::unpack_skip
//       Access: Published
//  Description: Skips the current field without unpacking it and
//               advances to the next field.  If the current field
//               contains nested fields, skips all of them.
////////////////////////////////////////////////////////////////////
void DCPacker::
unpack_skip() {
  nassertv(_mode == M_unpack);
  if (_current_field == NULL) {
    _pack_error = true;

  } else {
    if (_current_field->unpack_skip(_unpack_data, _unpack_length, _unpack_p,
                                    _pack_error)) {
      advance();

    } else {
      // If the single field couldn't be skipped, try skipping nested fields.
      push();
      while (more_nested_fields()) {
        unpack_skip();
      }
      pop();
    }
  }
}

#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
//     Function: DCPacker::pack_object
//       Access: Published
//  Description: Packs the Python object of whatever type into the
//               packer.  Each numeric object and string object maps
//               to the corresponding pack_value() call; a tuple or
//               sequence maps to a push() followed by all of the
//               tuple's contents followed by a pop().
////////////////////////////////////////////////////////////////////
void DCPacker::
pack_object(PyObject *object) {
  nassertv(_mode == M_pack || _mode == M_repack);

  if (PyInt_Check(object)) {
    pack_int(PyInt_AS_LONG(object));

  } else if (PyFloat_Check(object)) {
    pack_double(PyFloat_AS_DOUBLE(object));

  } else if (PyLong_Check(object)) {
    pack_int64(PyLong_AsLongLong(object));

  } else if (PyString_Check(object) || PyUnicode_Check(object)) {
    char *buffer;
    int length;
    PyString_AsStringAndSize(object, &buffer, &length);
    if (buffer) {
      pack_string(string(buffer, length));
    }

  } else {
    int size = PySequence_Size(object);
    bool is_instance = false;

    const DCClass *dclass = NULL;
    const DCPackerInterface *current_field = get_current_field();
    if (current_field != (DCPackerInterface *)NULL) {
      const DCClassParameter *class_param = get_current_field()->as_class_parameter();
      if (class_param != (DCClassParameter *)NULL) {
        dclass = class_param->get_class();
        
        if (dclass->has_class_def()) {
          PyObject *class_def = dclass->get_class_def();
          is_instance = (PyObject_IsInstance(object, dclass->get_class_def()) != 0);
          Py_DECREF(class_def);
        }
      }
    }

    // If dclass is not NULL, the packer is expecting a class object.
    // There are then two cases: (1) the user has supplied a matching
    // class object, or (2) the user has supplied a sequence object.
    // Unfortunately, it may be difficult to differentiate these two
    // cases, since a class object may also be a sequence object.

    // The rule to differentiate them is:

    // (1) If the supplied class object is an instance of the expected
    // class object, it is considered to be a class object.

    // (2) Otherwise, if the supplied class object has a __len__()
    // method (i.e. PySequence_Size() returns a number >= 0), then it
    // is considered to be a sequence.

    // (3) Otherwise, it is considered to be a class object.

    if (dclass != (DCClass *)NULL && (is_instance || size < 0)) {
      // The supplied object is either an instance of the expected
      // class object, or the size is less than 0--this is case (1) or
      // (3).
      pack_class_object(dclass, object);

    } else if (size >= 0) {
      // The supplied object is not an instance of the expected class
      // object, but it does have a size.  This is case (2).
      push();
      int size = PySequence_Size(object);
      for (int i = 0; i < size; i++) {
        PyObject *element = PySequence_GetItem(object, i);
        pack_object(element);
        Py_DECREF(element);
      }
      pop();

    } else {
      // The supplied object does not return a valid size, and we
      // weren't expecting a class parameter.  This is none of the
      // above, an error.
      ostringstream strm;
      PyObject *str = PyObject_Str(object);
      strm << "Don't know how to pack object: "
           << PyString_AsString(str) << "\n";
      Py_DECREF(str);
      nassert_raise(strm.str());
      _pack_error = true;
    }
  }
}
#endif  // HAVE_PYTHON

#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
//     Function: DCPacker::unpack_object
//       Access: Published
//  Description: Unpacks a Python object of the appropriate type from
//               the stream for the current field.  This may be an
//               integer or a string for a simple field object; if the
//               current field represents a list of fields it will be
//               a tuple.
////////////////////////////////////////////////////////////////////
PyObject *DCPacker::
unpack_object() {
  PyObject *object = NULL;

  DCPackType pack_type = get_pack_type();

  switch (pack_type) {
  case PT_invalid:
    object = Py_None;
    unpack_skip();
    break;

  case PT_double:
    {
      double value = unpack_double();
      object = PyFloat_FromDouble(value);
    }
    break;
      
  case PT_int:
    {
      int value = unpack_int();
      object = PyInt_FromLong(value);
    }
    break;
      
  case PT_uint:
    {
      unsigned int value = unpack_uint();
      if (value & 0x80000000) {
        object = PyLong_FromUnsignedLong(value);
      } else {
        object = PyInt_FromLong(value);
      }
    }
    break;
      
  case PT_int64:
    {
      PN_int64 value = unpack_int64();
      object = PyLong_FromLongLong(value);
    }
    break;
      
  case PT_uint64:
    {
      PN_uint64 value = unpack_uint64();
      object = PyLong_FromUnsignedLongLong(value);
    }
    break;

  case PT_string:
  case PT_blob:
    {
      string str;
      unpack_string(str);
      object = PyString_FromStringAndSize(str.data(), str.size());
    }
    break;

  case PT_class:
    {
      const DCClassParameter *class_param = get_current_field()->as_class_parameter();
      if (class_param != (DCClassParameter *)NULL) {
        const DCClass *dclass = class_param->get_class();
        if (dclass->has_class_def()) {
          // If we know what kind of class object this is and it has a
          // valid constructor, create the class object instead of
          // just a tuple.
          object = unpack_class_object(dclass);
          break;
        }
      }
    }
    // If we don't know what kind of class object it is, or it doesn't
    // have a constructor, fall through and make a tuple.
  default:
    {
      // First, build up a list from the nested objects.
      object = PyList_New(0);

      push();
      while (more_nested_fields()) {
        PyObject *element = unpack_object();
        PyList_Append(object, element);
        Py_DECREF(element);
      }
      pop();

      if (pack_type != PT_array) {
        // For these other kinds of objects, we'll convert the list
        // into a tuple.
        PyObject *tuple = PyList_AsTuple(object);
        Py_DECREF(object);
        object = tuple;
      }
    }
    break;
  }

  return object;
}
#endif  // HAVE_PYTHON


////////////////////////////////////////////////////////////////////
//     Function: DCPacker::parse_and_pack
//       Access: Published
//  Description: Parses an object's value according to the DC file
//               syntax (e.g. as a default value string) and packs it.
//               Returns true on success, false on a parse error.
////////////////////////////////////////////////////////////////////
bool DCPacker::
parse_and_pack(const string &formatted_object) {
  istringstream strm(formatted_object);
  return parse_and_pack(strm);
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::parse_and_pack
//       Access: Published
//  Description: Parses an object's value according to the DC file
//               syntax (e.g. as a default value string) and packs it.
//               Returns true on success, false on a parse error.
////////////////////////////////////////////////////////////////////
bool DCPacker::
parse_and_pack(istream &in) {
  dc_init_parser_parameter_value(in, "parse_and_pack", *this);
  dcyyparse();
  dc_cleanup_parser();

  return (dc_error_count() == 0);
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::unpack_and_format
//       Access: Published
//  Description: Unpacks an object and formats its value into a syntax
//               suitable for parsing in the dc file (e.g. as a
//               default value), or as an input to parse_object.
////////////////////////////////////////////////////////////////////
string DCPacker::
unpack_and_format() {
  ostringstream strm;
  unpack_and_format(strm);
  return strm.str();
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::unpack_and_format
//       Access: Published
//  Description: Unpacks an object and formats its value into a syntax
//               suitable for parsing in the dc file (e.g. as a
//               default value), or as an input to parse_object.
////////////////////////////////////////////////////////////////////
void DCPacker::
unpack_and_format(ostream &out) {
  DCPackType pack_type = get_pack_type();

  switch (pack_type) {
  case PT_invalid:
    out << "<invalid>";
    break;

  case PT_double:
    out << unpack_double();
    break;
      
  case PT_int:
    out << unpack_int();
    break;
      
  case PT_uint:
    out << unpack_uint();
    break;
      
  case PT_int64:
    out << unpack_int64();
    break;
      
  case PT_uint64:
    out << unpack_uint64();
    break;

  case PT_string:
    enquote_string(out, '"', unpack_string());
    break;

  case PT_blob:
    output_hex_string(out, unpack_literal_value());
    break;

  default:
    {
      switch (pack_type) {
      case PT_array:
        out << '[';
        break;

      case PT_field:
      case PT_switch:
        out << '(';
        break;

      case PT_class:
      default:
        out << '{';
        break;
      }

      push();
      while (more_nested_fields() && !had_pack_error()) {
        unpack_and_format(out);

        if (more_nested_fields()) {
          out << ", ";
        }
      }
      pop();

      switch (pack_type) {
      case PT_array:
        out << ']';
        break;

      case PT_field:
      case PT_switch:
        out << ')';
        break;

      case PT_class:
      default:
        out << '}';
        break;
      }
    }
    break;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::enquote_string
//       Access: Public, Static
//  Description: Outputs the indicated string within quotation marks.
////////////////////////////////////////////////////////////////////
void DCPacker::
enquote_string(ostream &out, char quote_mark, const string &str) {
  out << quote_mark;
  for (string::const_iterator pi = str.begin();
       pi != str.end();
       ++pi) {
    if ((*pi) == quote_mark || (*pi) == '\\') {
      out << '\\' << (*pi);

    } else if (!isprint(*pi)) {
      char buffer[10];
      sprintf(buffer, "%02x", (unsigned char)(*pi));
      out << "\\x" << buffer;

    } else {
      out << (*pi);
    }
  }
  out << quote_mark;
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::output_hex_string
//       Access: Public, Static
//  Description: Outputs the indicated string as a hex constant.
////////////////////////////////////////////////////////////////////
void DCPacker::
output_hex_string(ostream &out, const string &str) {
  out << '<';
  for (string::const_iterator pi = str.begin();
       pi != str.end();
       ++pi) {
    char buffer[10];
    sprintf(buffer, "%02x", (unsigned char)(*pi));
    out << buffer;
  }
  out << '>';
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::handle_switch
//       Access: Private
//  Description: When we advance past the key field on a switch
//               record, we suddenly have more fields available--all
//               the appropriate alternate fields in the switch.
//
//               This function is called when we detect this
//               condition; it switches the _current_parent to the
//               appropriate case of the switch record.
////////////////////////////////////////////////////////////////////
void DCPacker::
handle_switch(const DCSwitchParameter *switch_parameter) {
  // First, get the value from the key.  This is either found in the
  // unpack or the pack data, depending on what mode we're in.
  const DCPackerInterface *new_parent = NULL;

  if (_mode == M_pack || _mode == M_repack) {
    const char *data = _pack_data.get_data();
    new_parent = switch_parameter->apply_switch
      (data + _push_marker, _pack_data.get_length() - _push_marker);

  } else if (_mode == M_unpack) {
    new_parent = switch_parameter->apply_switch
      (_unpack_data + _push_marker, _unpack_p - _push_marker);
  }

  if (new_parent == (DCPackerInterface *)NULL) {
    // This means an invalid value was packed for the key.
    _pack_error = true;
    return;
  }

  _last_switch = switch_parameter;

  // Now substitute in the switch case for the previous parent (which
  // replaces the switch node itself).  This will suddenly make a slew
  // of new fields appear.
  _current_parent = new_parent;
  _num_nested_fields = _current_parent->get_num_nested_fields();

  if (_num_nested_fields < 0 ||
      _current_field_index < _num_nested_fields) {
    _current_field = _current_parent->get_nested_field(_current_field_index);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::clear
//       Access: Private
//  Description: Resets the data structures after a pack or unpack
//               sequence.
////////////////////////////////////////////////////////////////////
void DCPacker::
clear() {
  clear_stack();
  _current_field = NULL;
  _current_parent = NULL;
  _current_field_index = 0;
  _num_nested_fields = 0;
  _push_marker = 0;
  _pop_marker = 0;
  _last_switch = NULL;

  if (_live_catalog != (DCPackerCatalog::LiveCatalog *)NULL) {
    _catalog->release_live_catalog(_live_catalog);
    _live_catalog = NULL;
  }
  _catalog = NULL;
  _root = NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: DCPacker::clear_stack
//       Access: Private
//  Description: Empties the stack.
////////////////////////////////////////////////////////////////////
void DCPacker::
clear_stack() {
  while (_stack != (StackElement *)NULL) {
    StackElement *next = _stack->_next;
    delete _stack;
    _stack = next;
  }
}

#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
//     Function: DCPacker::pack_class_object
//       Access: Private
//  Description: Given that the current element is a ClassParameter
//               for a Python class object, try to extract the
//               appropriate values from the class object and pack in.
////////////////////////////////////////////////////////////////////
void DCPacker::
pack_class_object(const DCClass *dclass, PyObject *object) {
  PyObject *str = PyObject_Str(object);
  Py_DECREF(str);
  push();
  while (more_nested_fields() && !_pack_error) {
    const DCField *field = get_current_field()->as_field();
    nassertv(field != (DCField *)NULL);
    get_class_element(dclass, object, field);
  }
  pop();
}
#endif  // HAVE_PYTHON

#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
//     Function: DCPacker::unpack_class_object
//       Access: Private
//  Description: Given that the current element is a ClassParameter
//               for a Python class for which we have a valid
//               constructor, unpack it and fill in its values.
////////////////////////////////////////////////////////////////////
PyObject *DCPacker::
unpack_class_object(const DCClass *dclass) {
  PyObject *class_def = dclass->get_class_def();
  nassertr(class_def != (PyObject *)NULL, NULL);

  PyObject *object = NULL;

  if (!dclass->has_constructor()) {
    // If the class uses a default constructor, go ahead and create
    // the Python object for it now.
    object = PyObject_CallObject(class_def, NULL);
    if (object == (PyObject *)NULL) {
      return NULL;
    }
  }

  push();
  if (object == (PyObject *)NULL && more_nested_fields()) {
    // The first nested field will be the constructor.
    const DCField *field = get_current_field()->as_field();
    nassertr(field != (DCField *)NULL, object);
    nassertr(field == dclass->get_constructor(), object);

    set_class_element(class_def, object, field);

    // By now, the object should have been constructed.
    if (object == (PyObject *)NULL) {
      return NULL;
    }
  }
  while (more_nested_fields()) {
    const DCField *field = get_current_field()->as_field();
    nassertr(field != (DCField *)NULL, object);

    set_class_element(class_def, object, field);
  }
  pop();

  return object;
}
#endif  // HAVE_PYTHON


#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
//     Function: DCPacker::set_class_element
//       Access: Private
//  Description: Unpacks the current element and stuffs it on the
//               Python class object in whatever way is appropriate.
////////////////////////////////////////////////////////////////////
void DCPacker::
set_class_element(PyObject *class_def, PyObject *&object, 
                  const DCField *field) {
  string field_name = field->get_name();
  DCPackType pack_type = get_pack_type();

  if (field_name.empty()) {
    switch (pack_type) {
    case PT_class:
    case PT_switch:
      // If the field has no name, but it is one of these container
      // objects, we want to unpack its nested objects directly into
      // the class.
      push();
      while (more_nested_fields()) {
        const DCField *field = get_current_field()->as_field();
        nassertv(field != (DCField *)NULL);
        nassertv(object != (PyObject *)NULL);
        set_class_element(class_def, object, field);
      }
      pop();
      break;

    default:
      // Otherwise, we just skip over the field.
      unpack_skip();
    }

  } else {
    // If the field does have a name, we will want to store it on the
    // class, either by calling a method (for a PT_field pack_type) or
    // by setting a value (for any other kind of pack_type).

    PyObject *element = unpack_object();

    if (pack_type == PT_field) {
      if (object == (PyObject *)NULL) {
        // If the object hasn't been constructed yet, assume this is
        // the constructor.
        object = PyObject_CallObject(class_def, element);

      } else {
        if (PyObject_HasAttrString(object, (char *)field_name.c_str())) {
          PyObject *func = PyObject_GetAttrString(object, (char *)field_name.c_str());
          if (func != (PyObject *)NULL) {
            PyObject *result = PyObject_CallObject(func, element);
            Py_XDECREF(result);
            Py_DECREF(func);
          }
        }
      }
      
    } else {
      nassertv(object != (PyObject *)NULL);
      PyObject_SetAttrString(object, (char *)field_name.c_str(), element);
    }

    Py_DECREF(element);
  }
}
#endif  // HAVE_PYTHON


#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
//     Function: DCPacker::get_class_element
//       Access: Private
//  Description: Gets the current element from the Python object and
//               packs it.
////////////////////////////////////////////////////////////////////
void DCPacker::
get_class_element(const DCClass *dclass, PyObject *object, 
                  const DCField *field) {
  string field_name = field->get_name();
  DCPackType pack_type = get_pack_type();

  if (field_name.empty()) {
    switch (pack_type) {
    case PT_class:
    case PT_switch:
      // If the field has no name, but it is one of these container
      // objects, we want to get its nested objects directly from
      // the class.
      push();
      while (more_nested_fields() && !_pack_error) {
        const DCField *field = get_current_field()->as_field();
        nassertv(field != (DCField *)NULL);
        get_class_element(dclass, object, field);
      }
      pop();
      break;

    default:
      // Otherwise, we just pack the default value.
      pack_default_value();
    }

  } else {
    // If the field does have a name, we will want to get it from the
    // class and pack it.  It just so happens that there's already a
    // method that does this on DCClass.

    if (!dclass->pack_required_field(*this, object, field)) {
      _pack_error = true;
    }
  }
}
#endif  // HAVE_PYTHON
