// Filename: dcField.cxx
// Created by:  drose (11Oct00)
//
////////////////////////////////////////////////////////////////////
//
// 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 "dcField.h"
#include "dcFile.h"
#include "dcPacker.h"
#include "dcClass.h"
#include "hashGenerator.h"
#include "dcmsgtypes.h"

#ifdef WITHIN_PANDA
#include "pStatTimer.h"
#endif

////////////////////////////////////////////////////////////////////
//     Function: DCField::Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
DCField::
DCField() : 
  _dclass(NULL)
#ifdef WITHIN_PANDA
  ,
  _field_update_pcollector("DCField")
#endif
{
  _number = -1;
  _default_value_stale = true;
  _has_default_value = false;

  _bogus_field = false;

  _has_nested_fields = true;
  _num_nested_fields = 0;
  _pack_type = PT_field;

  _has_fixed_byte_size = true;
  _fixed_byte_size = 0;
  _has_fixed_structure = true;
}

////////////////////////////////////////////////////////////////////
//     Function: DCField::Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
DCField::
DCField(const string &name, DCClass *dclass) : 
  DCPackerInterface(name),
  _dclass(dclass)
#ifdef WITHIN_PANDA
  ,
  _field_update_pcollector(dclass->_class_update_pcollector, name)
#endif
{
  _number = -1;
  _has_default_value = false;
  _default_value_stale = true;

  _has_nested_fields = true;
  _num_nested_fields = 0;
  _pack_type = PT_field;

  _has_fixed_byte_size = true;
  _fixed_byte_size = 0;
  _has_fixed_structure = true;
}

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

////////////////////////////////////////////////////////////////////
//     Function: DCField::as_field
//       Access: Published, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
DCField *DCField::
as_field() {
  return this;
}

////////////////////////////////////////////////////////////////////
//     Function: DCField::as_field
//       Access: Published, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
const DCField *DCField::
as_field() const {
  return this;
}

////////////////////////////////////////////////////////////////////
//     Function: DCField::as_atomic_field
//       Access: Published, Virtual
//  Description: Returns the same field pointer converted to an atomic
//               field pointer, if this is in fact an atomic field;
//               otherwise, returns NULL.
////////////////////////////////////////////////////////////////////
DCAtomicField *DCField::
as_atomic_field() {
  return (DCAtomicField *)NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: DCField::as_atomic_field
//       Access: Published, Virtual
//  Description: Returns the same field pointer converted to an atomic
//               field pointer, if this is in fact an atomic field;
//               otherwise, returns NULL.
////////////////////////////////////////////////////////////////////
const DCAtomicField *DCField::
as_atomic_field() const {
  return (DCAtomicField *)NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: DCField::as_molecular_field
//       Access: Published, Virtual
//  Description: Returns the same field pointer converted to a
//               molecular field pointer, if this is in fact a
//               molecular field; otherwise, returns NULL.
////////////////////////////////////////////////////////////////////
DCMolecularField *DCField::
as_molecular_field() {
  return (DCMolecularField *)NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: DCField::as_molecular_field
//       Access: Published, Virtual
//  Description: Returns the same field pointer converted to a
//               molecular field pointer, if this is in fact a
//               molecular field; otherwise, returns NULL.
////////////////////////////////////////////////////////////////////
const DCMolecularField *DCField::
as_molecular_field() const {
  return (DCMolecularField *)NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: DCField::as_parameter
//       Access: Published, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
DCParameter *DCField::
as_parameter() {
  return (DCParameter *)NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: DCField::as_parameter
//       Access: Published, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
const DCParameter *DCField::
as_parameter() const {
  return (DCParameter *)NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: DCField::format_data
//       Access: Published
//  Description: Given a blob that represents the packed data for this
//               field, returns a string formatting it for human
//               consumption.  Returns empty string if there is an error.
////////////////////////////////////////////////////////////////////
string DCField::
format_data(const string &packed_data, bool show_field_names) {
  DCPacker packer;
  packer.set_unpack_data(packed_data);
  packer.begin_unpack(this);
  string result = packer.unpack_and_format(show_field_names);
  if (!packer.end_unpack()) {
    return string();
  }
  return result;
}

////////////////////////////////////////////////////////////////////
//     Function: DCField::parse_string
//       Access: Published
//  Description: Given a human-formatted string (for instance, as
//               returned by format_data(), above) that represents the
//               value of this field, parse the string and return the
//               corresponding packed data.  Returns empty string if
//               there is an error.
////////////////////////////////////////////////////////////////////
string DCField::
parse_string(const string &formatted_string) {
  DCPacker packer;
  packer.begin_pack(this);
  if (!packer.parse_and_pack(formatted_string)) {
    // Parse error.
    return string();
  }
  if (!packer.end_pack()) {
    // Data type mismatch.
    return string();
  }

  return packer.get_string();
}

////////////////////////////////////////////////////////////////////
//     Function: DCField::validate_ranges
//       Access: Published
//  Description: Verifies that all of the packed values in the field
//               data are within the specified ranges and that there
//               are no extra bytes on the end of the record.  Returns
//               true if all fields are valid, false otherwise.
////////////////////////////////////////////////////////////////////
bool DCField::
validate_ranges(const string &packed_data) const {
  DCPacker packer;
  packer.set_unpack_data(packed_data);
  packer.begin_unpack(this);
  packer.unpack_validate();
  if (!packer.end_unpack()) {
    return false;
  }

  return (packer.get_num_unpacked_bytes() == packed_data.length());
}

#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
//     Function: DCField::pack_args
//       Access: Published
//  Description: Packs the Python arguments from the indicated tuple
//               into the packer.  Returns true on success, false on
//               failure.
//
//               It is assumed that the packer is currently positioned
//               on this field.
////////////////////////////////////////////////////////////////////
bool DCField::
pack_args(DCPacker &packer, PyObject *sequence) const {
  nassertr(!packer.had_error(), false);
  nassertr(packer.get_current_field() == this, false);

  packer.pack_object(sequence);
  if (!packer.had_error()) {
    /*
    PyObject *str = PyObject_Str(sequence);
    cerr << "pack " << get_name() << PyString_AsString(str) << "\n";
    Py_DECREF(str);
    */

    return true;
  }

  if (!Notify::ptr()->has_assert_failed()) {
    ostringstream strm;

    if (as_parameter() != (DCParameter *)NULL) {
      // If it's a parameter-type field, the value may or may not be a
      // sequence.
      PyObject *str = PyObject_Str(sequence);
      nassertr(str != (PyObject *)NULL, false);
      
      if (packer.had_pack_error()) {
        strm << "Incorrect arguments to field: " << get_name()
             << " = " << PyString_AsString(str);
      } else {
        strm << "Value out of range on field: " << get_name()
             << " = " << PyString_AsString(str);
      }
      Py_DECREF(str);

    } else {
      // If it's a molecular or atomic field, the value should be a
      // sequence.
      PyObject *tuple = PySequence_Tuple(sequence);
      if (tuple == (PyObject *)NULL) {
        PyObject *str = PyObject_Str(sequence);
        nassertr(str != (PyObject *)NULL, false);

        strm << "Value for " << get_name() << " not a sequence: " \
             << PyString_AsString(str);
        Py_DECREF(str);

      } else {
        PyObject *str = PyObject_Str(tuple);
        
        if (packer.had_pack_error()) {
          strm << "Incorrect arguments to field: " << get_name()
               << PyString_AsString(str);
        } else {
          strm << "Value out of range on field: " << get_name()
               << PyString_AsString(str);
        }
        
        Py_DECREF(str);
        Py_DECREF(tuple);
      }
    }

    nassert_raise(strm.str());
  }
  return false;
}
#endif  // HAVE_PYTHON

#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
//     Function: DCField::unpack_args
//       Access: Published
//  Description: Unpacks the values from the packer, beginning at
//               the current point in the unpack_buffer, into a Python
//               tuple and returns the tuple.
//
//               It is assumed that the packer is currently positioned
//               on this field.
////////////////////////////////////////////////////////////////////
PyObject *DCField::
unpack_args(DCPacker &packer) const {
  nassertr(!packer.had_error(), NULL);
  nassertr(packer.get_current_field() == this, NULL);

  PyObject *object = packer.unpack_object();

  if (!packer.had_error()) {
    // Successfully unpacked.
    /*
    PyObject *str = PyObject_Str(object);
    cerr << "recv " << get_name() << PyString_AsString(str) << "\n";
    Py_DECREF(str);
    */

    return object;
  }
  
  ostringstream strm;
  if (packer.had_pack_error()) {
    strm << "Error unpacking to field " << get_name();
  } else {
    PyObject *str = PyObject_Str(object);
    strm << "Found value outside specified range when unpacking field " 
         << get_name() << ": " << PyString_AsString(str);
    Py_DECREF(str);
  }

  /*
  Datagram dg(data.data(), packer.get_num_unpacked_bytes());
  dg.dump_hex(cerr);
  */
    
  nassert_raise(strm.str());
  Py_XDECREF(object);
  return NULL;
}
#endif  // HAVE_PYTHON

#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
//     Function: DCField::receive_update
//       Access: Published
//  Description: Extracts the update message out of the datagram and
//               applies it to the indicated object by calling the
//               appropriate method.
////////////////////////////////////////////////////////////////////
void DCField::
receive_update(DCPacker &packer, PyObject *distobj) const {
  if (as_parameter() != (DCParameter *)NULL) {
    // If it's a parameter-type field, just store a new value on the
    // object.
    PyObject *value = unpack_args(packer);
    if (value != (PyObject *)NULL) {
      PyObject_SetAttrString(distobj, (char *)_name.c_str(), value);
    }
    Py_DECREF(value);

  } else {
    // Otherwise, it must be an atomic or molecular field, so call the
    // corresponding method.

    if (!PyObject_HasAttrString(distobj, (char *)_name.c_str())) {
      // If there's no Python method to receive this message, don't
      // bother unpacking it to a Python tuple--just skip past the
      // message.
      packer.unpack_skip();

    } else {
      // Otherwise, get a Python tuple from the args and call the Python
      // method.
      PyObject *args = unpack_args(packer);

      if (args != (PyObject *)NULL) {
        PyObject *func = PyObject_GetAttrString(distobj, (char *)_name.c_str());
        nassertv(func != (PyObject *)NULL);
      
        PyObject *result;
        {
#ifdef WITHIN_PANDA
          PStatTimer timer(((DCField *)this)->_field_update_pcollector);
#endif
          result = PyObject_CallObject(func, args);
        }
        Py_XDECREF(result);
        Py_DECREF(func);
        Py_DECREF(args);
      }
    }
  }
}
#endif  // HAVE_PYTHON

#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
//     Function: DCField::client_format_update
//       Access: Published
//  Description: Generates a datagram containing the message necessary
//               to send an update for the indicated distributed
//               object from the client.
////////////////////////////////////////////////////////////////////
Datagram DCField::
client_format_update(int do_id, PyObject *args) const {
  DCPacker packer;

  packer.raw_pack_uint16(CLIENT_OBJECT_UPDATE_FIELD);
  packer.raw_pack_uint32(do_id);
  packer.raw_pack_uint16(_number);

  packer.begin_pack(this);
  pack_args(packer, args);
  if (!packer.end_pack()) {
    return Datagram();
  }

  return Datagram(packer.get_data(), packer.get_length());
}
#endif  // HAVE_PYTHON

#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
//     Function: DCField::ai_format_update
//       Access: Published
//  Description: Generates a datagram containing the message necessary
//               to send an update for the indicated distributed
//               object from the AI.
////////////////////////////////////////////////////////////////////
Datagram DCField::
ai_format_update(int do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args) const {
  DCPacker packer;

  packer.raw_pack_uint8(1);
  packer.RAW_PACK_CHANNEL(to_id);
  packer.RAW_PACK_CHANNEL(from_id);
  packer.raw_pack_uint16(STATESERVER_OBJECT_UPDATE_FIELD);
  packer.raw_pack_uint32(do_id);
  packer.raw_pack_uint16(_number);

  packer.begin_pack(this);
  pack_args(packer, args);
  if (!packer.end_pack()) {
    return Datagram();
  }

  return Datagram(packer.get_data(), packer.get_length());
}
#endif  // HAVE_PYTHON


////////////////////////////////////////////////////////////////////
//     Function: DCField::generate_hash
//       Access: Public, Virtual
//  Description: Accumulates the properties of this field into the
//               hash.
////////////////////////////////////////////////////////////////////
void DCField::
generate_hash(HashGenerator &hashgen) const {
  // It shouldn't be necessary to explicitly add _number to the
  // hash--this is computed based on the relative position of this
  // field with the other fields, so adding it explicitly will be
  // redundant.  However, the field name is significant.
  hashgen.add_string(_name);

  // Actually, we add _number anyway, since we need to ensure the hash
  // code comes out different in the dc_multiple_inheritance case.
  if (dc_multiple_inheritance) {
    hashgen.add_int(_number);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: DCField::pack_default_value
//       Access: Public, Virtual
//  Description: Packs the field's specified default value (or a
//               sensible default if no value is specified) into the
//               stream.  Returns true if the default value is packed,
//               false if the field doesn't know how to pack its
//               default value.
////////////////////////////////////////////////////////////////////
bool DCField::
pack_default_value(DCPackData &pack_data, bool &) const {
  // The default behavior is to pack the default value if we got it;
  // otherwise, to return false and let the packer visit our nested
  // elements.
  if (!_default_value_stale) {
    pack_data.append_data(_default_value.data(), _default_value.length());
    return true;
  }

  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: DCField::set_name
//       Access: Public, Virtual
//  Description: Sets the name of this field.
////////////////////////////////////////////////////////////////////
void DCField::
set_name(const string &name) {
  DCPackerInterface::set_name(name);
  if (_dclass != (DCClass *)NULL) {
    _dclass->_dc_file->mark_inherited_fields_stale();
  }
}

////////////////////////////////////////////////////////////////////
//     Function: DCField::refresh_default_value
//       Access: Protected
//  Description: Recomputes the default value of the field by
//               repacking it.
////////////////////////////////////////////////////////////////////
void DCField::
refresh_default_value() {
  DCPacker packer;
  packer.begin_pack(this);
  packer.pack_default_value();
  if (!packer.end_pack()) {
    cerr << "Error while packing default value for " << get_name() << "\n";
  } else {
    _default_value.assign(packer.get_data(), packer.get_length());
  }
  _default_value_stale = false;
}
