// Filename: dcFile.cxx
// Created by:  drose (05Oct00)
//
////////////////////////////////////////////////////////////////////
//
// 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 "dcFile.h"
#include "dcClass.h"
#include "dcSwitch.h"
#include "dcParserDefs.h"
#include "dcLexerDefs.h"
#include "dcTypedef.h"
#include "dcKeyword.h"
#include "hashGenerator.h"

#ifdef WITHIN_PANDA
#include "filename.h"
#include "config_express.h"
#include "virtualFileSystem.h"
#include "executionEnvironment.h"
#include "configVariableList.h"
#endif


////////////////////////////////////////////////////////////////////
//     Function: DCFile::Constructor
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
DCFile::
DCFile() {
  _all_objects_valid = true;
  _inherited_fields_stale = false;

  setup_default_keywords();
}

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

////////////////////////////////////////////////////////////////////
//     Function: DCFile::clear
//       Access: Published
//  Description: Removes all of the classes defined within the DCFile
//               and prepares it for reading a new file.
////////////////////////////////////////////////////////////////////
void DCFile::
clear() {
  Declarations::iterator di;
  for (di = _declarations.begin(); di != _declarations.end(); ++di) {
    delete (*di);
  }
  for (di = _things_to_delete.begin(); di != _things_to_delete.end(); ++di) {
    delete (*di);
  }
  
  _classes.clear();
  _imports.clear();
  _things_by_name.clear();
  _typedefs.clear();
  _typedefs_by_name.clear();
  _keywords.clear_keywords();
  _declarations.clear();
  _things_to_delete.clear();
  setup_default_keywords();

  _all_objects_valid = true;
  _inherited_fields_stale = false;
}

#ifdef WITHIN_PANDA

////////////////////////////////////////////////////////////////////
//     Function: DCFile::read_all
//       Access: Published
//  Description: This special method reads all of the .dc files named
//               by the "dc-file" config.prc variable, and loads them
//               into the DCFile namespace.
////////////////////////////////////////////////////////////////////
bool DCFile::
read_all() {
  static ConfigVariableList dc_files
    ("dc-file", PRC_DESC("The list of dc files to load."));

  if (dc_files.size() == 0) {
    cerr << "No files specified via dc-file Config.prc variable!\n";
    return false;
  }

  int size = dc_files.size();

  // Load the DC files in opposite order, because we want to load the
  // least-important (most fundamental) files first.
  for (int i = size - 1; i >= 0; --i) {
    string dc_file = ExecutionEnvironment::expand_string(dc_files[i]);
    Filename filename = Filename::from_os_specific(dc_file);
    if (!read(filename)) {
      return false;
    }
  }

  return true;
}

#endif  // WITHIN_PANDA

////////////////////////////////////////////////////////////////////
//     Function: DCFile::read
//       Access: Published
//  Description: Opens and reads the indicated .dc file by name.  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 DCFile::
read(Filename filename) {
#ifdef WITHIN_PANDA
  filename.set_text();
  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
  istream *in = vfs->open_read_file(filename, true);
  if (in == (istream *)NULL) {
    cerr << "Cannot open " << filename << " for reading.\n";
    return false;
  }
  bool okflag = read(*in, filename);
  
  // For some reason--compiler bug in gcc 3.2?--explicitly deleting
  // the in pointer does not call the appropriate global delete
  // function; instead apparently calling the system delete
  // function.  So we call the delete function by hand instead.
  vfs->close_read_file(in);
  
  return okflag;

#else  // WITHIN_PANDA

  pifstream in;
  in.open(filename.c_str());

  if (!in) {
    cerr << "Cannot open " << filename << " for reading.\n";
    return false;
  }

  return read(in, filename);

#endif  // WITHIN_PANDA
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::read
//       Access: Published
//  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 DCFile::
read(istream &in, const string &filename) {
  cerr << "DCFile::read of " << filename << "\n";
  dc_init_parser(in, filename, *this);
  dcyyparse();
  dc_cleanup_parser();

  return (dc_error_count() == 0);
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::write
//       Access: Published
//  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 DCFile::
write(Filename filename, bool brief) const {
  pofstream out;

#ifdef WITHIN_PANDA
  filename.set_text();
  filename.open_write(out);
#else
  out.open(filename.c_str());
#endif

  if (!out) {
    cerr << "Can't open " << filename << " for output.\n";
    return false;
  }
  return write(out, brief);
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::write
//       Access: Published
//  Description: Writes a parseable description of all the known
//               distributed classes to the stream.
//
//               Returns true if the description is successfully
//               written, false otherwise.
////////////////////////////////////////////////////////////////////
bool DCFile::
write(ostream &out, bool brief) const {
  if (!_imports.empty()) {
    Imports::const_iterator ii;
    for (ii = _imports.begin(); ii != _imports.end(); ++ii) {
      const Import &import = (*ii);
      if (import._symbols.empty()) {
        out << "import " << import._module << "\n";
      } else {
        out << "from " << import._module << " import ";
        ImportSymbols::const_iterator si = import._symbols.begin();
        out << *si;
        ++si;
        while (si != import._symbols.end()) {
          out << ", " << *si;
        ++si;
        }
        out << "\n";
      }
    }
    out << "\n";
  }

  Declarations::const_iterator di;
  for (di = _declarations.begin(); di != _declarations.end(); ++di) {
    (*di)->write(out, brief, 0);
    out << "\n";
  }

  return !out.fail();
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::get_num_classes
//       Access: Published
//  Description: Returns the number of classes read from the .dc
//               file(s).
////////////////////////////////////////////////////////////////////
int DCFile::
get_num_classes() const {
  return _classes.size();
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::get_class
//       Access: Published
//  Description: Returns the nth class read from the .dc file(s).
////////////////////////////////////////////////////////////////////
DCClass *DCFile::
get_class(int n) const {
  nassertr(n >= 0 && n < (int)_classes.size(), NULL);
  return _classes[n];
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::get_class_by_name
//       Access: Published
//  Description: Returns the class that has the indicated name, or
//               NULL if there is no such class.
////////////////////////////////////////////////////////////////////
DCClass *DCFile::
get_class_by_name(const string &name) const {
  ThingsByName::const_iterator ni;
  ni = _things_by_name.find(name);
  if (ni != _things_by_name.end()) {
    return (*ni).second->as_class();
  }

  return (DCClass *)NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::get_switch_by_name
//       Access: Published
//  Description: Returns the switch that has the indicated name, or
//               NULL if there is no such switch.
////////////////////////////////////////////////////////////////////
DCSwitch *DCFile::
get_switch_by_name(const string &name) const {
  ThingsByName::const_iterator ni;
  ni = _things_by_name.find(name);
  if (ni != _things_by_name.end()) {
    return (*ni).second->as_switch();
  }

  return (DCSwitch *)NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::get_field_by_index
//       Access: Published, Static
//  Description: Returns a pointer to the one DCField that has the
//               indicated index number, of all the DCFields across
//               all classes in the file.
//
//               This method is only valid if dc-multiple-inheritance
//               is set true in the Config.prc file.  Without this
//               setting, different DCFields may share the same index
//               number, so this global lookup is not possible.
////////////////////////////////////////////////////////////////////
DCField *DCFile::
get_field_by_index(int index_number) const {
  nassertr(dc_multiple_inheritance, NULL);

  if (index_number >= 0 && index_number < (int)_fields_by_index.size()) {
    return _fields_by_index[index_number];
  }
  
  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::get_num_import_modules
//       Access: Published
//  Description: Returns the number of import lines read from the .dc
//               file(s).
////////////////////////////////////////////////////////////////////
int DCFile::
get_num_import_modules() const {
  return _imports.size();
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::get_import_module
//       Access: Published
//  Description: Returns the module named by the nth import line read
//               from the .dc file(s).
////////////////////////////////////////////////////////////////////
string DCFile::
get_import_module(int n) const {
  nassertr(n >= 0 && n < (int)_imports.size(), string());
  return _imports[n]._module;
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::get_num_import_symbols
//       Access: Published
//  Description: Returns the number of symbols explicitly imported by
//               the nth import line.  If this is 0, the line is
//               "import modulename"; if it is more than 0, the line
//               is "from modulename import symbol, symbol ... ".
////////////////////////////////////////////////////////////////////
int DCFile::
get_num_import_symbols(int n) const {
  nassertr(n >= 0 && n < (int)_imports.size(), 0);
  return _imports[n]._symbols.size();
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::get_import_symbol
//       Access: Published
//  Description: Returns the ith symbol named by the nth import line
//               read from the .dc file(s).
////////////////////////////////////////////////////////////////////
string DCFile::
get_import_symbol(int n, int i) const {
  nassertr(n >= 0 && n < (int)_imports.size(), string());
  nassertr(i >= 0 && i < (int)_imports[n]._symbols.size(), string());
  return _imports[n]._symbols[i];
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::get_num_typedefs
//       Access: Published
//  Description: Returns the number of typedefs read from the .dc
//               file(s).
////////////////////////////////////////////////////////////////////
int DCFile::
get_num_typedefs() const {
  return _typedefs.size();
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::get_typedef
//       Access: Published
//  Description: Returns the nth typedef read from the .dc file(s).
////////////////////////////////////////////////////////////////////
DCTypedef *DCFile::
get_typedef(int n) const {
  nassertr(n >= 0 && n < (int)_typedefs.size(), NULL);
  return _typedefs[n];
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::get_typedef_by_name
//       Access: Published
//  Description: Returns the typedef that has the indicated name, or
//               NULL if there is no such typedef name.
////////////////////////////////////////////////////////////////////
DCTypedef *DCFile::
get_typedef_by_name(const string &name) const {
  TypedefsByName::const_iterator ni;
  ni = _typedefs_by_name.find(name);
  if (ni != _typedefs_by_name.end()) {
    return (*ni).second;
  }

  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::get_num_keywords
//       Access: Published
//  Description: Returns the number of keywords read from the .dc
//               file(s).
////////////////////////////////////////////////////////////////////
int DCFile::
get_num_keywords() const {
  return _keywords.get_num_keywords();
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::get_keyword
//       Access: Published
//  Description: Returns the nth keyword read from the .dc file(s).
////////////////////////////////////////////////////////////////////
const DCKeyword *DCFile::
get_keyword(int n) const {
  return _keywords.get_keyword(n);
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::get_keyword_by_name
//       Access: Published
//  Description: Returns the keyword that has the indicated name, or
//               NULL if there is no such keyword name.
////////////////////////////////////////////////////////////////////
const DCKeyword *DCFile::
get_keyword_by_name(const string &name) const {
  const DCKeyword *keyword = _keywords.get_keyword_by_name(name);
  if (keyword == (const DCKeyword *)NULL) {
    keyword = _default_keywords.get_keyword_by_name(name);
    if (keyword != (const DCKeyword *)NULL) {
      // One of the historical default keywords was used, but wasn't
      // defined.  Define it implicitly right now.
      ((DCFile *)this)->_keywords.add_keyword(keyword);
    }
  }

  return keyword;
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::get_hash
//       Access: Published
//  Description: Returns a 32-bit hash index associated with this
//               file.  This number is guaranteed to be consistent if
//               the contents of the file have not changed, and it is
//               very likely to be different if the contents of the
//               file do change.
////////////////////////////////////////////////////////////////////
unsigned long DCFile::
get_hash() const {
  HashGenerator hashgen;
  generate_hash(hashgen);
  return hashgen.get_hash();
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::generate_hash
//       Access: Public, Virtual
//  Description: Accumulates the properties of this file into the
//               hash.
////////////////////////////////////////////////////////////////////
void DCFile::
generate_hash(HashGenerator &hashgen) const {
  if (dc_virtual_inheritance) {
    // Just to make the hash number change in this case.
    if (dc_sort_inheritance_by_file) {
      hashgen.add_int(1);
    } else {
      hashgen.add_int(2);
    }
  }

  hashgen.add_int(_classes.size());
  Classes::const_iterator ci;
  for (ci = _classes.begin(); ci != _classes.end(); ++ci) {
    (*ci)->generate_hash(hashgen);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::add_class
//       Access: Public
//  Description: Adds the newly-allocated distributed class definition
//               to the file.  The DCFile becomes the owner of the
//               pointer and will delete it when it destructs.
//               Returns true if the class is successfully added, or
//               false if there was a name conflict.
////////////////////////////////////////////////////////////////////
bool DCFile::
add_class(DCClass *dclass) {
  if (!dclass->get_name().empty()) {
    bool inserted = _things_by_name.insert
      (ThingsByName::value_type(dclass->get_name(), dclass)).second;
    
    if (!inserted) {
      return false;
    }
  }

  if (!dclass->is_struct()) {
    dclass->set_number(get_num_classes());
  }
  _classes.push_back(dclass);

  if (dclass->is_bogus_class()) {
    _all_objects_valid = false;
  }

  if (!dclass->is_bogus_class()) {
    _declarations.push_back(dclass);
  } else {
    _things_to_delete.push_back(dclass);
  }

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::add_switch
//       Access: Public
//  Description: Adds the newly-allocated switch definition
//               to the file.  The DCFile becomes the owner of the
//               pointer and will delete it when it destructs.
//               Returns true if the switch is successfully added, or
//               false if there was a name conflict.
////////////////////////////////////////////////////////////////////
bool DCFile::
add_switch(DCSwitch *dswitch) {
  if (!dswitch->get_name().empty()) {
    bool inserted = _things_by_name.insert
      (ThingsByName::value_type(dswitch->get_name(), dswitch)).second;
    
    if (!inserted) {
      return false;
    }
  }

  _declarations.push_back(dswitch);

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::add_import_module
//       Access: Public
//  Description: Adds a new name to the list of names of Python
//               modules that are to be imported by the client or AI
//               to define the code that is associated with the class
//               interfaces named within the .dc file.
////////////////////////////////////////////////////////////////////
void DCFile::
add_import_module(const string &import_module) {
  Import import;
  import._module = import_module;
  _imports.push_back(import);
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::add_import_symbol
//       Access: Public
//  Description: Adds a new name to the list of symbols that are to be
//               explicitly imported from the most-recently added
//               module, e.g. "from module_name import symbol".  If
//               the list of symbols is empty, the syntax is taken to
//               be "import module_name".
////////////////////////////////////////////////////////////////////
void DCFile::
add_import_symbol(const string &import_symbol) {
  nassertv(!_imports.empty());
  _imports.back()._symbols.push_back(import_symbol);
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::add_typedef
//       Access: Public
//  Description: Adds the newly-allocated distributed typedef definition
//               to the file.  The DCFile becomes the owner of the
//               pointer and will delete it when it destructs.
//               Returns true if the typedef is successfully added, or
//               false if there was a name conflict.
////////////////////////////////////////////////////////////////////
bool DCFile::
add_typedef(DCTypedef *dtypedef) {
  bool inserted = _typedefs_by_name.insert
    (TypedefsByName::value_type(dtypedef->get_name(), dtypedef)).second;

  if (!inserted) {
    return false;
  }

  dtypedef->set_number(get_num_typedefs());
  _typedefs.push_back(dtypedef);

  if (dtypedef->is_bogus_typedef()) {
    _all_objects_valid = false;
  }

  if (!dtypedef->is_bogus_typedef() && !dtypedef->is_implicit_typedef()) {
    _declarations.push_back(dtypedef);
  } else {
    _things_to_delete.push_back(dtypedef);
  }

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::add_keyword
//       Access: Public
//  Description: Adds the indicated keyword string to the list of
//               keywords known to the DCFile.  These keywords may
//               then be added to DCFields.  It is not an error to add
//               a particular keyword more than once.
////////////////////////////////////////////////////////////////////
bool DCFile::
add_keyword(const string &name) {
  DCKeyword *keyword = new DCKeyword(name);
  bool added = _keywords.add_keyword(keyword);

  if (added) {
    _declarations.push_back(keyword);
  } else {
    delete keyword;
  }

  return added;
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::add_thing_to_delete
//       Access: Public
//  Description: Adds the indicated declaration to the list of
//               declarations that are not reported with the file, but
//               will be deleted when the DCFile object destructs.
//               That is, transfers ownership of the indicated pointer
//               to the DCFile.
////////////////////////////////////////////////////////////////////
void DCFile::
add_thing_to_delete(DCDeclaration *decl) {
  _things_to_delete.push_back(decl);
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::set_new_index_number
//       Access: Public
//  Description: Sets the next sequential available index number on
//               the indicated field.  This is only meant to be called
//               by DCClass::add_field(), while the dc file is being
//               parsed.
////////////////////////////////////////////////////////////////////
void DCFile::
set_new_index_number(DCField *field) {
  field->set_number((int)_fields_by_index.size());
  _fields_by_index.push_back(field);
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::setup_default_keywords
//       Access: Private
//  Description: Adds an entry for each of the default keywords that
//               are defined for every DCFile for legacy reasons.
////////////////////////////////////////////////////////////////////
void DCFile::
setup_default_keywords() {
  struct KeywordDef {
    const char *name;
    int flag;
  };
  static KeywordDef default_keywords[] = {
    { "required", 0x0001 },
    { "broadcast", 0x0002 },
    { "ownrecv", 0x0004 },
    { "ram", 0x0008 },
    { "db", 0x0010 },
    { "clsend", 0x0020 },
    { "clrecv", 0x0040 },
    { "ownsend", 0x0080 },
    { "airecv", 0x0100 },
    { NULL, 0 }
  };

  _default_keywords.clear_keywords();
  for (int i = 0; default_keywords[i].name != NULL; ++i) {
    DCKeyword *keyword = 
      new DCKeyword(default_keywords[i].name, 
                    default_keywords[i].flag);
    
    _default_keywords.add_keyword(keyword);
    _things_to_delete.push_back(keyword);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: DCFile::rebuild_inherited_fields
//       Access: Private
//  Description: Reconstructs the inherited fields table of all
//               classes.
////////////////////////////////////////////////////////////////////
void DCFile::
rebuild_inherited_fields() {
  _inherited_fields_stale = false;

  Classes::iterator ci;
  for (ci = _classes.begin(); ci != _classes.end(); ++ci) {
    (*ci)->clear_inherited_fields();
  }
  for (ci = _classes.begin(); ci != _classes.end(); ++ci) {
    (*ci)->rebuild_inherited_fields();
  }
}
