// Filename: eggReader.cxx
// Created by:  drose (14Feb00)
//
////////////////////////////////////////////////////////////////////
//
// 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 "eggReader.h"

#include "pnmImage.h"
#include "config_util.h"
#include "eggTextureCollection.h"
#include "eggGroup.h"
#include "eggGroupNode.h"
#include "eggSwitchCondition.h"
#include "string_utils.h"
#include "dcast.h"

////////////////////////////////////////////////////////////////////
//     Function: EggReader::Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
EggReader::
EggReader() {
  clear_runlines();
  add_runline("[opts] input.egg");

  redescribe_option
    ("cs",
     "Specify the coordinate system to operate in.  This may be "
     " one of 'y-up', 'z-up', 'y-up-left', or 'z-up-left'.  The default "
     "is the coordinate system of the input egg file.");

  add_option
    ("f", "", 80,
     "Force complete loading: load up the egg file along with all of its "
     "external references.",
     &EggReader::dispatch_none, &_force_complete);

  add_option
    ("noabs", "", 0,
     "Don't allow the input egg file to have absolute pathnames.  "
     "If it does, abort with an error.  This option is designed to help "
     "detect errors when populating or building a standalone model tree, "
     "which should be self-contained and include only relative pathnames.",
     &EggReader::dispatch_none, &_noabs);

  _tex_type = (PNMFileType *)NULL;
  _delod = -1.0;

  _got_tex_dirname = false;
  _got_tex_extension = false;
}

////////////////////////////////////////////////////////////////////
//     Function: EggRead::add_texture_options
//       Access: Public
//  Description: Adds -td, -te, etc. as valid options for this
//               program.  If the user specifies one of the options on
//               the command line, the textures will be copied and
//               converted as each egg file is read.
//
//               Note that if you call this function to add these
//               options, you must call do_reader_options() at the
//               appropriate point before or during processing to
//               execute the options if the user specified them.
////////////////////////////////////////////////////////////////////
void EggReader::
add_texture_options() {
  add_option
    ("td", "dirname", 40,
     "Copy textures to the indicated directory.  The copy is performed "
     "only if the destination file does not exist or is older than the "
     "source file.",
     &EggReader::dispatch_filename, &_got_tex_dirname, &_tex_dirname);

  add_option
    ("te", "ext", 40,
     "Rename textures to have the indicated extension.  This also "
     "automatically copies them to the new filename (possibly in a "
     "different directory if -td is also specified), and may implicitly "
     "convert to a different image format according to the extension.",
     &EggReader::dispatch_string, &_got_tex_extension, &_tex_extension);

  add_option
    ("tt", "type", 40,
     "Explicitly specifies the image format to convert textures to "
     "when copying them via -td or -te.  Normally, this is unnecessary as "
     "the image format can be determined by the extension, but sometimes "
     "the extension is insufficient to unambiguously specify an image "
     "type.",
     &EggReader::dispatch_image_type, NULL, &_tex_type);
}

////////////////////////////////////////////////////////////////////
//     Function: EggRead::add_delod_options
//       Access: Public
//  Description: Adds -delod as a valid option for this program.
//
//               Note that if you call this function to add these
//               options, you must call do_reader_options() at the
//               appropriate point before or during processing to
//               execute the options if the user specified them.
////////////////////////////////////////////////////////////////////
void EggReader::
add_delod_options(double default_delod) {
  _delod = default_delod;

  if (default_delod < 0) {
    add_option
      ("delod", "dist", 40,
       "Eliminate LOD's by choosing the level that would be appropriate for "
       "a camera at the indicated fixed distance from each LOD.  "
       "Use -delod -1 to keep all the LOD's as they are, which is "
       "the default.\n",
       &EggReader::dispatch_double, NULL, &_delod);

  } else {
    add_option
      ("delod", "dist", 40,
       "Eliminate LOD's by choosing the level that would be appropriate for "
       "a camera at the indicated fixed distance from each LOD.  "
       "Use -delod -1 to keep all the LOD's as they are.  The default value "
       "is " + format_string(default_delod) + ".",
       &EggReader::dispatch_double, NULL, &_delod);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggReader::as_reader
//       Access: Public, Virtual
//  Description: Returns this object as an EggReader pointer, if it is
//               in fact an EggReader, or NULL if it is not.
//
//               This is intended to work around the C++ limitation
//               that prevents downcasts past virtual inheritance.
//               Since both EggReader and EggWriter inherit virtually
//               from EggSingleBase, we need functions like this to downcast
//               to the appropriate pointer.
////////////////////////////////////////////////////////////////////
EggReader *EggReader::
as_reader() {
  return this;
}

////////////////////////////////////////////////////////////////////
//     Function: EggReader::pre_process_egg_file
//       Access: Public, Virtual
//  Description: Performs any processing of the egg file that is
//               appropriate after reading it in.
//
//               Normally, you should not need to call this function
//               directly; it is called automatically at startup.
////////////////////////////////////////////////////////////////////
void EggReader::
pre_process_egg_file() {
}

////////////////////////////////////////////////////////////////////
//     Function: EggReader::handle_args
//       Access: Protected, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
bool EggReader::
handle_args(ProgramBase::Args &args) {
  if (args.empty()) {
    nout << "You must specify the egg file(s) to read on the command line.\n";
    return false;
  }

  // Any separate egg files that are listed on the command line will
  // get implicitly loaded up into one big egg file.

  if (!args.empty()) {
    _data->set_egg_filename(Filename::from_os_specific(args[0]));
  }
  Args::const_iterator ai;
  for (ai = args.begin(); ai != args.end(); ++ai) {
    Filename filename = Filename::from_os_specific(*ai);

    EggData file_data;
    if (!file_data.read(filename)) {
      // Rather than returning false, we simply exit here, so the
      // ProgramBase won't try to tell the user how to run the program
      // just because we got a bad egg file.
      exit(1);
    }

    if (_noabs && file_data.original_had_absolute_pathnames()) {
      nout << filename.get_basename()
           << " includes absolute pathnames!\n";
      exit(1);
    }

    DSearchPath file_path;
    file_path.append_directory(filename.get_dirname());
    
    if (_force_complete) {
      if (!file_data.load_externals()) {
        exit(1);
      }
    }

    // Now resolve the filenames again according to the user's
    // specified _path_replace.
    convert_paths(&file_data, _path_replace, file_path);

    _data->merge(file_data);
  }

  pre_process_egg_file();

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: EggReader::post_command_line
//       Access: Protected, Virtual
//  Description: This is called after the command line has been
//               completely processed, and it gives the program a
//               chance to do some last-minute processing and
//               validation of the options and arguments.  It should
//               return true if everything is fine, false if there is
//               an error.
////////////////////////////////////////////////////////////////////
bool EggReader::
post_command_line() {
  return EggSingleBase::post_command_line();
}

////////////////////////////////////////////////////////////////////
//     Function: EggReader::do_reader_options
//       Access: Protected
//  Description: Postprocesses the egg file as the user requested
//               according to whatever command-line options are in
//               effect.  Returns true if everything is done
//               correctly, false if there was some problem.
////////////////////////////////////////////////////////////////////
bool EggReader::
do_reader_options() {
  bool okflag = true;

  if (_got_tex_dirname || _got_tex_extension) {
    if (!copy_textures()) {
      okflag = false;
    }
  }

  if (_delod >= 0.0) {
    do_delod(_data);
  }

  return okflag;
}

////////////////////////////////////////////////////////////////////
//     Function: EggReader::copy_textures
//       Access: Private
//  Description: Renames and copies the textures referenced in the egg
//               file, if so specified by the -td and -te options.
//               Returns true if all textures are copied successfully,
//               false if any one of them failed.
////////////////////////////////////////////////////////////////////
bool EggReader::
copy_textures() {
  bool success = true;
  EggTextureCollection textures;
  textures.find_used_textures(_data);

  EggTextureCollection::const_iterator ti;
  for (ti = textures.begin(); ti != textures.end(); ++ti) {
    EggTexture *tex = (*ti);
    Filename orig_filename = tex->get_filename();
    if (!orig_filename.exists()) {
      bool found = orig_filename.resolve_filename(get_model_path());
      if (!found) {
        nout << "Cannot find " << orig_filename << "\n";
        success = false;
        continue;
      }
    }

    Filename new_filename = orig_filename;
    if (_got_tex_dirname) {
      new_filename.set_dirname(_tex_dirname);
    }
    if (_got_tex_extension) {
      new_filename.set_extension(_tex_extension);
    }

    if (orig_filename != new_filename) {
      tex->set_filename(new_filename);

      // The new filename is different; does it need copying?
      int compare = 
        orig_filename.compare_timestamps(new_filename, true, true);
      if (compare > 0) {
        // Yes, it does.  Copy it!
        nout << "Reading " << orig_filename << "\n";
        PNMImage image;
        if (!image.read(orig_filename)) {
          nout << "  unable to read!\n";
          success = false;
        } else {
          nout << "Writing " << new_filename << "\n";
          if (!image.write(new_filename, _tex_type)) {
            nout << "  unable to write!\n";
            success = false;
          }
        }
      }
    }
  }

  return success;
}

////////////////////////////////////////////////////////////////////
//     Function: EggReader::do_delod
//       Access: Private
//  Description: Removes all the LOD's in the egg file by treating the
//               camera as being _delod distance from each LOD.
//               Returns true if this particular group should be
//               preserved, false if it should be removed.
////////////////////////////////////////////////////////////////////
bool EggReader::
do_delod(EggNode *node) {
  if (node->is_of_type(EggGroup::get_class_type())) {
    EggGroup *group = DCAST(EggGroup, node);
    if (group->has_lod()) {
      const EggSwitchCondition &cond = group->get_lod();
      if (cond.is_of_type(EggSwitchConditionDistance::get_class_type())) {
        const EggSwitchConditionDistance *dist = 
          DCAST(EggSwitchConditionDistance, &cond);
        if (_delod >= dist->_switch_out && _delod < dist->_switch_in) {
          // Preserve this group node, but not the LOD information
          // itself.
          nout << "Preserving LOD " << node->get_name() 
               << " (" << dist->_switch_out << " to " << dist->_switch_in
               << ")\n";
          group->clear_lod();
        } else {
          // Remove this group node.
          nout << "Eliminating LOD " << node->get_name()
               << " (" << dist->_switch_out << " to " << dist->_switch_in
               << ")\n";
          return false;
        }
      }
    }
  }

  // Now process all the children.
  if (node->is_of_type(EggGroupNode::get_class_type())) {
    EggGroupNode *group = DCAST(EggGroupNode, node);
    EggGroupNode::iterator ci;
    ci = group->begin();
    while (ci != group->end()) {
      EggNode *child = *ci;
      ++ci;

      if (!do_delod(child)) {
        group->remove_child(child);
      }
    }
  }

  return true;
}
