// Filename: xFile.cxx
// Created by:  drose (03Oct04)
//
////////////////////////////////////////////////////////////////////
//
// 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 "xFile.h"
#include "xParserDefs.h"
#include "xLexerDefs.h"
#include "xFileTemplate.h"
#include "xFileDataNodeTemplate.h"
#include "config_xfile.h"
#include "standard_templates.h"
#include "zStream.h"
#include "virtualFileSystem.h"
#include "dcast.h"

TypeHandle XFile::_type_handle;
PT(XFile) XFile::_standard_templates;

////////////////////////////////////////////////////////////////////
//     Function: XFile::Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
XFile::
XFile(bool keep_names) : XFileNode(this, "") {
  _major_version = 3;
  _minor_version = 2;
  _format_type = FT_text;
  _float_size = FS_64;
  _keep_names = keep_names;
}

////////////////////////////////////////////////////////////////////
//     Function: XFile::Destructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
XFile::
~XFile() {
  clear();
}

////////////////////////////////////////////////////////////////////
//     Function: XFile::clear
//       Access: Public, Virtual
//  Description: Removes all of the classes defined within the XFile
//               and prepares it for reading a new file.
////////////////////////////////////////////////////////////////////
void XFile::
clear() {
  XFileNode::clear();

  _nodes_by_guid.clear();
}

////////////////////////////////////////////////////////////////////
//     Function: XFile::read
//       Access: Public
//  Description: Opens and reads the indicated .x file by name.  The
//               nodes and templates defined in the file will be
//               appended to the set of nodes already recorded, if
//               any.
//
//               Returns true if the file is successfully read, false
//               if there was an error (in which case the file might
//               have been partially read).
////////////////////////////////////////////////////////////////////
bool XFile::
read(Filename filename) {
  filename.set_text();
  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
  istream *in = vfs->open_read_file(filename, true);
  if (in == (istream *)NULL) {
    xfile_cat.error()
      << "Cannot open " << filename << " for reading.\n";
    return false;
  }
  bool okflag = read(*in, filename);
  vfs->close_read_file(in);
  return okflag;
}

////////////////////////////////////////////////////////////////////
//     Function: XFile::read
//       Access: Public
//  Description: Parses the already-opened input stream for
//               distributed class descriptions.  The filename
//               parameter is optional and is only used when reporting
//               errors.
//
//               The distributed classes defined in the file will be
//               appended to the set of distributed classes already
//               recorded, if any.
//
//               Returns true if the file is successfully read, false
//               if there was an error (in which case the file might
//               have been partially read).
////////////////////////////////////////////////////////////////////
bool XFile::
read(istream &in, const string &filename) {
  if (!read_header(in)) {
    return false;
  }

  if (_format_type != FT_text) {
    // Does anyone actually use the binary format?  It wouldn't be too
    // hard to support it if there were any reason at all to do so.
    xfile_cat.error()
      << "Cannot read binary .x files at this time.\n";
    return false;
  }

  // We must call this first so the standard templates file will be
  // parsed and available by the time we need it--it's tricky to
  // invoke the parser from within another parser instance.
  get_standard_templates();

  x_init_parser(in, filename, *this);
  xyyparse();
  x_cleanup_parser();

  return (x_error_count() == 0);
}

////////////////////////////////////////////////////////////////////
//     Function: XFile::write
//       Access: Public
//  Description: Opens the indicated filename for output and writes a
//               parseable description of all the known distributed
//               classes to the file.
//
//               Returns true if the description is successfully
//               written, false otherwise.
////////////////////////////////////////////////////////////////////
bool XFile::
write(Filename filename) const {
  ofstream out;

  // We actually open the file to write in binary mode, to avoid the
  // MS-DOS newline characters (since Windows seems to do this too).
  filename.set_binary();
  filename.open_write(out);

  if (!out) {
    xfile_cat.error()
      << "Can't open " << filename << " for output.\n";
    return false;
  }

#ifdef HAVE_ZLIB
  if (filename.get_extension() == "pz") {
    // The filename ends in .pz, which means to automatically compress
    // the X file that we write.
    OCompressStream compressor(&out, false);
    return write(compressor);
  }
#endif  // HAVE_ZLIB

  return write(out);
}

////////////////////////////////////////////////////////////////////
//     Function: XFile::write
//       Access: Public
//  Description: Writes a parseable description of all the known
//               nodes and templates to the stream.
//
//               Returns true if the description is successfully
//               written, false otherwise.
////////////////////////////////////////////////////////////////////
bool XFile::
write(ostream &out) const {
  if (!write_header(out)) {
    return false;
  }

  write_text(out, 0);

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: XFile::find_template
//       Access: Public
//  Description: Returns the template associated with the indicated
//               name, if any, or NULL if none.
////////////////////////////////////////////////////////////////////
XFileTemplate *XFile::
find_template(const string &name) const {
  XFileTemplate *standard = (XFileTemplate *)NULL;
  const XFile *standard_templates = get_standard_templates();
  if (standard_templates != this) {
    standard = standard_templates->find_template(name);
  }

  XFileNode *child = find_child(name);
  if (child != (XFileNode *)NULL &&
      child->is_of_type(XFileTemplate::get_class_type())) {
    XFileTemplate *xtemplate = DCAST(XFileTemplate, child);
    if (standard != (XFileTemplate *)NULL && xtemplate->matches(standard)) {
      // If the template matches a standard template, return the
      // standard instead.  The assumption is that code may expect a
      // certain naming scheme for the data elements of the standard
      // template, so we want to be sure to provide it.
      return standard;
    }
    return xtemplate;
  }

  return standard;
}

////////////////////////////////////////////////////////////////////
//     Function: XFile::find_template
//       Access: Public
//  Description: Returns the template associated with the indicated
//               GUID, if any, or NULL if none.
////////////////////////////////////////////////////////////////////
XFileTemplate *XFile::
find_template(const WindowsGuid &guid) const {
  XFileTemplate *standard = (XFileTemplate *)NULL;
  const XFile *standard_templates = get_standard_templates();
  if (standard_templates != this) {
    standard = standard_templates->find_template(guid);
  }

  NodesByGuid::const_iterator gi;
  gi = _nodes_by_guid.find(guid);
  if (gi != _nodes_by_guid.end() && 
      (*gi).second->is_of_type(XFileTemplate::get_class_type())) {
    XFileTemplate *xtemplate = DCAST(XFileTemplate, (*gi).second);
    if (standard != (XFileTemplate *)NULL && xtemplate->matches(standard)) {
      // If the template matches a standard template, return the
      // standard instead.  The assumption is that code may expect a
      // certain naming scheme for the data elements of the standard
      // template, so we want to be sure to provide it.
      return standard;
    }
    return xtemplate;
  }

  return standard;
}

////////////////////////////////////////////////////////////////////
//     Function: XFile::find_standard_template
//       Access: Public, Static
//  Description: Returns the standard template associated with the
//               indicated name, if any, or NULL if none.
////////////////////////////////////////////////////////////////////
XFileTemplate *XFile::
find_standard_template(const string &name) {
  const XFile *standard_templates = get_standard_templates();
  return standard_templates->find_template(name);
}

////////////////////////////////////////////////////////////////////
//     Function: XFile::find_standard_template
//       Access: Public, Static
//  Description: Returns the template associated with the indicated
//               GUID, if any, or NULL if none.
////////////////////////////////////////////////////////////////////
XFileTemplate *XFile::
find_standard_template(const WindowsGuid &guid) {
  const XFile *standard_templates = get_standard_templates();
  return standard_templates->find_template(guid);
}

////////////////////////////////////////////////////////////////////
//     Function: XFile::find_data_object
//       Access: Public
//  Description: Returns the data object associated with the indicated
//               name, if any, or NULL if none.
////////////////////////////////////////////////////////////////////
XFileDataNodeTemplate *XFile::
find_data_object(const string &name) const {
  XFileNode *child = find_descendent(name);
  if (child != (XFileNode *)NULL &&
      child->is_of_type(XFileDataNodeTemplate::get_class_type())) {
    return DCAST(XFileDataNodeTemplate, child);
  }

  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: XFile::find_data_object
//       Access: Public
//  Description: Returns the data object associated with the indicated
//               GUID, if any, or NULL if none.
////////////////////////////////////////////////////////////////////
XFileDataNodeTemplate *XFile::
find_data_object(const WindowsGuid &guid) const {
  NodesByGuid::const_iterator gi;
  gi = _nodes_by_guid.find(guid);
  if (gi != _nodes_by_guid.end() && 
      (*gi).second->is_of_type(XFileDataNodeTemplate::get_class_type())) {
    return DCAST(XFileDataNodeTemplate, (*gi).second);
  }

  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: XFile::write_text
//       Access: Public, Virtual
//  Description: Writes a suitable representation of this node to an
//               .x file in text mode.
////////////////////////////////////////////////////////////////////
void XFile::
write_text(ostream &out, int indent_level) const {
  Children::const_iterator ci;
  for (ci = _children.begin(); ci != _children.end(); ++ci) {
    (*ci)->write_text(out, indent_level);
    out << "\n";
  }
}

////////////////////////////////////////////////////////////////////
//     Function: XFile::read_header
//       Access: Private
//  Description: Reads the header and magic number associated with the
//               file.  Returns true on success, false otherwise.
////////////////////////////////////////////////////////////////////
bool XFile::
read_header(istream &in) {
  char magic[4];
  if (!in.read(magic, 4)) {
    xfile_cat.error()
      << "Empty file.\n";
    return false;
  }

  if (memcmp(magic, "xof ", 4) != 0) {
    xfile_cat.error()
      << "Not a DirectX file.\n";
    return false;
  }

  char version[4];
  if (!in.read(version, 4)) {
    xfile_cat.error()
      << "Truncated file.\n";
    return false;
  }
  _major_version = (version[0] - '0') * 10 + (version[1] - '0');
  _minor_version = (version[2] - '0') * 10 + (version[3] - '0');

  char format[4];
  if (!in.read(format, 4)) {
    xfile_cat.error()
      << "Truncated file.\n";
    return false;
  }

  if (memcmp(format, "txt ", 4) == 0) {
    _format_type = FT_text;

  } else if (memcmp(format, "bin ", 4) == 0) {
    _format_type = FT_binary;

  } else if (memcmp(format, "com ", 4) == 0) {
    _format_type = FT_compressed;

  } else {
    xfile_cat.error()
      << "Unknown format type: " << string(format, 4) << "\n";
    return false;
  }

  if (_format_type == FT_compressed) {
    // Read and ignore the compression type, since we don't support
    // compression anyway.
    char compression_type[4];
    in.read(compression_type, 4);
  }

  char float_size[4];
  if (!in.read(float_size, 4)) {
    xfile_cat.error()
      << "Truncated file.\n";
    return false;
  }

  if (memcmp(float_size, "0032", 4) == 0) {
    _float_size = FS_32;

  } else if (memcmp(float_size, "0064", 4) == 0) {
    _float_size = FS_64;

  } else {
    xfile_cat.error()
      << "Unknown float size: " << string(float_size, 4) << "\n";
    return false;
  }

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: XFile::write_header
//       Access: Private
//  Description: Writes the header and magic number associated with the
//               file.  Returns true on success, false otherwise.
////////////////////////////////////////////////////////////////////
bool XFile::
write_header(ostream &out) const {
  out.write("xof ", 4);

  char buffer[128];
  sprintf(buffer, "%02d%02d", _major_version, _minor_version);
  if (strlen(buffer) != 4) {
    xfile_cat.error()
      << "Invalid version: " << _major_version << "." << _minor_version
      << "\n";
    return false;
  }

  out.write(buffer, 4);

  switch (_format_type) {
  case FT_text:
    out.write("txt ", 4);
    break;
 
  case FT_binary:
    out.write("bin ", 4);
    break;
  
  case FT_compressed:
    out.write("cmp ", 4);
    break;

  default:
    xfile_cat.error()
      << "Invalid format type: " << _format_type << "\n";
    return false;
  }

  if (_format_type == FT_compressed) {
    // Write a bogus compression type, just so we have a valid header.
    out.write("xxx ", 4);
  }

  switch (_float_size) {
  case FS_32:
    out.write("0032", 4);
    break;
 
  case FS_64:
    out.write("0064", 4);
    break;

  default:
    xfile_cat.error()
      << "Invalid float size: " << _float_size << "\n";
    return false;
  }

  if (_format_type == FT_text) {
    // If it's a text format, we can now write a newline.
    out << "\n";
  }

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: XFile::get_standard_templates
//       Access: Private, Static
//  Description: Returns a global XFile object that contains the
//               standard list of Direct3D template definitions that
//               may be assumed to be at the head of every file.
////////////////////////////////////////////////////////////////////
const XFile *XFile::
get_standard_templates() {
  if (_standard_templates == (XFile *)NULL) {
    // The standardTemplates.x file has been compiled into this
    // binary.  Extract it out.

    string data((const char *)standard_templates_data, standard_templates_data_len);

#ifdef HAVE_ZLIB
    // The data is stored compressed; decompress it on-the-fly.
    istringstream inz(data);
    IDecompressStream in(&inz, false);
    
#else
    // The data is stored uncompressed, so just load it.
    istringstream in(data);
#endif  // HAVE_ZLIB
    
    _standard_templates = new XFile;
    if (!_standard_templates->read(in, "standardTemplates.x")) {
      xfile_cat.error()
        << "Internal error: Unable to parse built-in standardTemplates.x!\n";
    }

    // Now flag all of these templates as "standard".
    for (int i = 0; i < _standard_templates->get_num_children(); i++) {
      XFileNode *child = _standard_templates->get_child(i);
      if (child->is_of_type(XFileTemplate::get_class_type())) {
        XFileTemplate *xtemplate = DCAST(XFileTemplate, child);
        xtemplate->_is_standard = true;
      }
    }
  }
  
  return _standard_templates;
}
