// Filename: xFileDataDef.cxx
// Created by:  drose (03Oct04)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University.  All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license.  You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////

#include "xFileDataDef.h"
#include "indent.h"
#include "xLexerDefs.h"
#include "xFileParseData.h"
#include "xFileDataObjectInteger.h"
#include "xFileDataObjectDouble.h"
#include "xFileDataObjectString.h"
#include "xFileDataNodeTemplate.h"
#include "xFileDataObjectArray.h"
#include "string_utils.h"

TypeHandle XFileDataDef::_type_handle;

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

////////////////////////////////////////////////////////////////////
//     Function: XFileDataDef::clear
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
void XFileDataDef::
clear() {
  XFileNode::clear();
  _array_def.clear();
}

////////////////////////////////////////////////////////////////////
//     Function: XFileDataDef::add_array_def
//       Access: Public
//  Description: Adds an additional array dimension to the data
//               description.
////////////////////////////////////////////////////////////////////
void XFileDataDef::
add_array_def(const XFileArrayDef &array_def) {
  _array_def.push_back(array_def);
}

////////////////////////////////////////////////////////////////////
//     Function: XFileDataDef::write_text
//       Access: Public, Virtual
//  Description: Writes a suitable representation of this node to an
//               .x file in text mode.
////////////////////////////////////////////////////////////////////
void XFileDataDef::
write_text(ostream &out, int indent_level) const {
  indent(out, indent_level);

  if (!_array_def.empty()) {
    out << "array ";
  }

  switch (_type) {
  case T_word:
    out << "WORD";
    break;

  case T_dword:
    out << "DWORD";
    break;

  case T_float:
    out << "FLOAT";
    break;

  case T_double:
    out << "DOUBLE";
    break;

  case T_char:
    out << "CHAR";
    break;

  case T_uchar:
    out << "UCHAR";
    break;

  case T_sword:
    out << "SWORD";
    break;

  case T_sdword:
    out << "SDWORD";
    break;

  case T_string:
    out << "STRING";
    break;

  case T_cstring:
    out << "CSTRING";
    break;

  case T_unicode:
    out << "UNICODE";
    break;

  case T_template:
    out << _template->get_name();
    break;
  }

  if (has_name()) {
    out << " " << get_name();
  }

  ArrayDef::const_iterator ai;
  for (ai = _array_def.begin(); ai != _array_def.end(); ++ai) {
    (*ai).output(out);
  }

  out << ";\n";
}

////////////////////////////////////////////////////////////////////
//     Function: XFileDataDef::repack_data
//       Access: Public, Virtual
//  Description: This is called on the template that defines an
//               object, once the data for the object has been parsed.
//               It is responsible for identifying which component of
//               the template owns each data element, and packing the
//               data elements appropriately back into the object.
//
//               It returns true on success, or false on an error
//               (e.g. not enough data elements, mismatched data
//               type).
////////////////////////////////////////////////////////////////////
bool XFileDataDef::
repack_data(XFileDataObject *object, 
            const XFileParseDataList &parse_data_list,
            XFileDataDef::PrevData &prev_data,
            size_t &index, size_t &sub_index) const {
  // We'll fill this in with the data value we pack, if any.
  PT(XFileDataObject) data_value;

  // What kind of data element are we expecting?
  switch (_type) {
  case T_word:
  case T_dword:
  case T_char:
  case T_uchar:
  case T_sword:
  case T_sdword:
    // Expected integer data.
    data_value = unpack_value(parse_data_list, 0,
                              prev_data, index, sub_index,
                              &XFileDataDef::unpack_integer_value);
    break;

  case T_float:
  case T_double:
    data_value = unpack_value(parse_data_list, 0,
                              prev_data, index, sub_index,
                              &XFileDataDef::unpack_double_value);
    break;

  case T_string:
  case T_cstring:
  case T_unicode:
    data_value = unpack_value(parse_data_list, 0,
                              prev_data, index, sub_index,
                              &XFileDataDef::unpack_string_value);
    break;

  case T_template:
    data_value = unpack_value(parse_data_list, 0,
                              prev_data, index, sub_index,
                              &XFileDataDef::unpack_template_value);
    break;
  }

  if (data_value != (XFileDataObject *)NULL) {
    object->add_element(data_value);
    prev_data[this] = data_value;
  }

  return XFileNode::repack_data(object, parse_data_list, 
                                prev_data, index, sub_index);
}

////////////////////////////////////////////////////////////////////
//     Function: XFileDataDef::fill_zero_data
//       Access: Public, Virtual
//  Description: This is similar to repack_data(), except it is used
//               to fill the initial values for a newly-created
//               template object to zero.
////////////////////////////////////////////////////////////////////
bool XFileDataDef::
fill_zero_data(XFileDataObject *object) const {
  PT(XFileDataObject) data_value;

  // What kind of data element are we expecting?
  switch (_type) {
  case T_word:
  case T_dword:
  case T_char:
  case T_uchar:
  case T_sword:
  case T_sdword:
    data_value = zero_fill_value(0, &XFileDataDef::zero_fill_integer_value);
    break;

  case T_float:
  case T_double:
    data_value = zero_fill_value(0, &XFileDataDef::zero_fill_double_value);
    break;

  case T_string:
  case T_cstring:
  case T_unicode:
    data_value = zero_fill_value(0, &XFileDataDef::zero_fill_string_value);
    break;

  case T_template:
    data_value = zero_fill_value(0, &XFileDataDef::zero_fill_template_value);
    break;
  }

  if (data_value != (XFileDataObject *)NULL) {
    object->add_element(data_value);
  }

  return XFileNode::fill_zero_data(object);
}

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

  const XFileDataDef *data_def = DCAST(XFileDataDef, other);
  if (data_def->get_data_type() != get_data_type()) {
    return false;
  }

  if (get_data_type() == T_template &&
      !get_template()->matches(data_def->get_template())) {
    return false;
  }

  if (data_def->get_num_array_defs() != get_num_array_defs()) {
    return false;
  }

  for (int i = 0; i < get_num_array_defs(); i++) {
    if (!get_array_def(i).matches(data_def->get_array_def(i),
                                  this, data_def)) {
      return false;
    }
  }

  return true;
}


////////////////////////////////////////////////////////////////////
//     Function: XFileDataDef::unpack_integer_value
//       Access: Private
//  Description: Unpacks and returns the next sequential integer value
//               from the parse_data_list.
////////////////////////////////////////////////////////////////////
PT(XFileDataObject) XFileDataDef::
unpack_integer_value(const XFileParseDataList &parse_data_list,
                     const XFileDataDef::PrevData &prev_data,
                     size_t &index, size_t &sub_index) const {
  nassertr(index < parse_data_list._list.size(), NULL);
  const XFileParseData &parse_data = parse_data_list._list[index];

  PT(XFileDataObject) data_value;

  if ((parse_data._parse_flags & XFileParseData::PF_int) != 0) {
    nassertr(sub_index < parse_data._int_list.size(), NULL);
    int value = parse_data._int_list[sub_index];
    data_value = new XFileDataObjectInteger(this, value);
    
    sub_index++;
    if (sub_index >= parse_data._int_list.size()) {
      index++;
      sub_index = 0;
    }

  } else {
    parse_data.yyerror("Expected integer data for " + get_name());
  }

  return data_value;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileDataDef::unpack_double_value
//       Access: Private
//  Description: Unpacks and returns the next sequential double value
//               from the parse_data_list.
////////////////////////////////////////////////////////////////////
PT(XFileDataObject) XFileDataDef::
unpack_double_value(const XFileParseDataList &parse_data_list,
                    const XFileDataDef::PrevData &prev_data,
                    size_t &index, size_t &sub_index) const {
  nassertr(index < parse_data_list._list.size(), NULL);
  const XFileParseData &parse_data = parse_data_list._list[index];

  PT(XFileDataObject) data_value;

  if ((parse_data._parse_flags & XFileParseData::PF_double) != 0) {
    nassertr(sub_index < parse_data._double_list.size(), NULL);
    double value = parse_data._double_list[sub_index];
    data_value = new XFileDataObjectDouble(this, value);
    
    sub_index++;
    if (sub_index >= parse_data._double_list.size()) {
      index++;
      sub_index = 0;
    }

  } else if ((parse_data._parse_flags & XFileParseData::PF_int) != 0) {
    nassertr(sub_index < parse_data._int_list.size(), NULL);
    int value = parse_data._int_list[sub_index];
    data_value = new XFileDataObjectDouble(this, value);
    
    sub_index++;
    if (sub_index >= parse_data._int_list.size()) {
      index++;
      sub_index = 0;
    }

  } else {
    parse_data.yyerror("Expected floating-point data for " + get_name());
  }

  return data_value;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileDataDef::unpack_string_value
//       Access: Private
//  Description: Unpacks and returns the next sequential string value
//               from the parse_data_list.
////////////////////////////////////////////////////////////////////
PT(XFileDataObject) XFileDataDef::
unpack_string_value(const XFileParseDataList &parse_data_list,
                    const XFileDataDef::PrevData &prev_data,
                    size_t &index, size_t &sub_index) const {
  nassertr(index < parse_data_list._list.size(), NULL);
  const XFileParseData &parse_data = parse_data_list._list[index];

  PT(XFileDataObject) data_value;

  if ((parse_data._parse_flags & XFileParseData::PF_string) != 0) {
    data_value = new XFileDataObjectString(this, parse_data._string);
    index++;
    sub_index = 0;

  } else {
    parse_data.yyerror("Expected string data for " + get_name());
  }

  return data_value;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileDataDef::unpack_template_value
//       Access: Private
//  Description: Unpacks a nested template object's data.
////////////////////////////////////////////////////////////////////
PT(XFileDataObject) XFileDataDef::
unpack_template_value(const XFileParseDataList &parse_data_list,
                      const XFileDataDef::PrevData &prev_data,
                      size_t &index, size_t &sub_index) const {
  PT(XFileDataNodeTemplate) data_value = 
    new XFileDataNodeTemplate(get_x_file(), get_name(), _template);

  PrevData nested_prev_data(prev_data);
  if (!_template->repack_data(data_value, parse_data_list, 
                              nested_prev_data, index, sub_index)) {
    return NULL;
  }

  return data_value.p();
}

////////////////////////////////////////////////////////////////////
//     Function: XFileDataDef::unpack_value
//       Access: Private
//  Description: Unpacks and returns the next sequential value, of the
//               type supported by the unpack_method.  If the value
//               is an array type, unpacks all the elements of the
//               array.
////////////////////////////////////////////////////////////////////
PT(XFileDataObject) XFileDataDef::
unpack_value(const XFileParseDataList &parse_data_list, int array_index,
             const XFileDataDef::PrevData &prev_data,
             size_t &index, size_t &sub_index, 
             XFileDataDef::UnpackMethod unpack_method) const {
  PT(XFileDataObject) data_value;
  
  if (array_index == (int)_array_def.size()) {
    if (index >= parse_data_list._list.size()) {
      xyyerror("Not enough data elements in structure at " + get_name());
      return NULL;
    }
    data_value = (this->*unpack_method)(parse_data_list, prev_data,
                                        index, sub_index);

  } else {
    data_value = new XFileDataObjectArray(this);
    int array_size = _array_def[array_index].get_size(prev_data);

    for (int i = 0; i < array_size; i++) {
      if (index >= parse_data_list._list.size()) {
        xyyerror(string("Expected ") + format_string(array_size)
                 + " array elements, found " + format_string(i));
        return data_value;
      }

      PT(XFileDataObject) array_element = 
        unpack_value(parse_data_list, array_index + 1,
                     prev_data, index, sub_index,
                     unpack_method);
      if (array_element == (XFileDataObject *)NULL) {
        return data_value;
      }
      data_value->add_element(array_element);
    }
  }

  return data_value;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileDataDef::zero_fill_integer_value
//       Access: Private
//  Description: Returns a newly-allocated zero integer value.
////////////////////////////////////////////////////////////////////
PT(XFileDataObject) XFileDataDef::
zero_fill_integer_value() const {
  return new XFileDataObjectInteger(this, 0);
}

////////////////////////////////////////////////////////////////////
//     Function: XFileDataDef::zero_fill_double_value
//       Access: Private
//  Description: Returns a newly-allocated zero floating-point value.
////////////////////////////////////////////////////////////////////
PT(XFileDataObject) XFileDataDef::
zero_fill_double_value() const {
  return new XFileDataObjectDouble(this, 0.0);
}

////////////////////////////////////////////////////////////////////
//     Function: XFileDataDef::zero_fill_string_value
//       Access: Private
//  Description: Returns a newly-allocated empty string value.
////////////////////////////////////////////////////////////////////
PT(XFileDataObject) XFileDataDef::
zero_fill_string_value() const {
  return new XFileDataObjectString(this, "");
}

////////////////////////////////////////////////////////////////////
//     Function: XFileDataDef::zero_fill_template_value
//       Access: Private
//  Description: Returns a newly-allocated zero-filled nested template
//               value.
////////////////////////////////////////////////////////////////////
PT(XFileDataObject) XFileDataDef::
zero_fill_template_value() const {
  PT(XFileDataObject) data_value = 
    new XFileDataNodeTemplate(get_x_file(), get_name(), _template);
  if (!_template->fill_zero_data(data_value)) {
    return NULL;
  }

  return data_value;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileDataDef::zero_fill_value
//       Access: Private
//  Description: Creates a zero-valued element for the next sequential
//               value, of the type returned by the zero_fill_method.
//               If the value is a fixed-size array type, zero-fills
//               all the elements of the array.
////////////////////////////////////////////////////////////////////
PT(XFileDataObject) XFileDataDef::
zero_fill_value(int array_index, 
                XFileDataDef::ZeroFillMethod zero_fill_method) const {
  PT(XFileDataObject) data_value;
  
  if (array_index == (int)_array_def.size()) {
    data_value = (this->*zero_fill_method)();

  } else {
    data_value = new XFileDataObjectArray(this);
    int array_size = 0;
    if (_array_def[array_index].is_fixed_size()) {
      array_size = _array_def[array_index].get_fixed_size();
    }

    for (int i = 0; i < array_size; i++) {
      PT(XFileDataObject) array_element = 
        zero_fill_value(array_index + 1, zero_fill_method);
      if (array_element == (XFileDataObject *)NULL) {
        return NULL;
      }
      data_value->add_element(array_element);
    }
  }

  return data_value;
}
