// Filename: loader.cxx
// Created by:  mike (09Jan97)
//
////////////////////////////////////////////////////////////////////
//
// 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 "loader.h"
#include "loaderFileType.h"
#include "loaderFileTypeRegistry.h"
#include "config_pgraph.h"

#include "config_express.h"
#include "config_util.h"
#include "virtualFileSystem.h"
#include "event.h"
#include "pt_Event.h"
#include "throw_event.h"
#include "eventParameter.h"
#include "circBuffer.h"
#include "filename.h"
#include "load_dso.h"
#include "string_utils.h"
#include "plist.h"
#include "pvector.h"

#include <algorithm>


bool Loader::_file_types_loaded = false;

////////////////////////////////////////////////////////////////////
//      Struct : LoaderToken
// Description : Holds a request for the loader (load or delete), as
//               well as the return information after the request has
//               completed.
////////////////////////////////////////////////////////////////////
class LoaderToken : public ReferenceCount {
public:
  INLINE LoaderToken(uint id, const string &event_name, const Filename &path, 
                     bool search, PandaNode *node=NULL) : 
    _id(id), 
    _event_name(event_name), 
    _path(path),
    _search(search),
    _node(node) 
  { }
  uint _id;
  string _event_name;
  Filename _path;
  bool _search;
  PT(PandaNode) _node;
};

////////////////////////////////////////////////////////////////////
//     Function: Loader::Constructor
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
Loader::
Loader() : AsyncUtility() {
  _token_board = new LoaderTokenBoard;
}

////////////////////////////////////////////////////////////////////
//     Function: Loader::Destructor
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
Loader::
~Loader() {
  destroy_thread();
  delete _token_board;
}

////////////////////////////////////////////////////////////////////
//     Function: Loader::find_all_files
//       Access: Published
//  Description: Searches along the given search path for the given
//               file name, and fills up the results list with all
//               possible matches and their associated types, in
//               order.
////////////////////////////////////////////////////////////////////
int Loader::
find_all_files(const Filename &filename, const DSearchPath &search_path,
               Loader::Results &results) const {
  if (!_file_types_loaded) {
    load_file_types();
  }
  string extension = filename.get_extension();

  int num_added = 0;

  if (!extension.empty()) {
    // If the extension is not empty, it specifies a single file type.
    LoaderFileTypeRegistry *reg = LoaderFileTypeRegistry::get_global_ptr();
    LoaderFileType *requested_type =
      reg->get_type_from_extension(extension);

    if (requested_type != (LoaderFileType *)NULL) {
      if (!filename.is_local()) {
        // Global filename, take it as it is.
        results.add_file(filename, requested_type);
        num_added++;

      } else {
        // Local filename, search along the path.
        DSearchPath::Results files;
        if (use_vfs) {
          VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
          num_added = vfs->find_all_files(filename, search_path, files);
        } else {
          num_added = search_path.find_all_files(filename, files);
        }
        
        for (int i = 0; i < num_added; ++i) {
          results.add_file(files.get_file(i), requested_type);
        }
      }
    }
  } else {
    // If the extension *is* empty, we have to search for all possible
    // file types.
    LoaderFileTypeRegistry *reg = LoaderFileTypeRegistry::get_global_ptr();
    int num_types = reg->get_num_types();

    if (!filename.is_local()) {
      // Global filename, take it as it is.
      for (int t = 0; t < num_types; ++t) {
        LoaderFileType *type = reg->get_type(t);
        Filename file(filename);
        file.set_extension(type->get_extension());
          
        if (use_vfs) {
          VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
          if (vfs->exists(file)) {
            results.add_file(file, type);
            ++num_added;
          }
        } else {
          if (file.exists()) {
            results.add_file(file, type);
            ++num_added;
          }
        }
      }
    } else {
      // Local filename, look it up on the model path.
      int num_dirs = search_path.get_num_directories();
      for (int i = 0; i < num_dirs; ++i) {
        const Filename &directory = search_path.get_directory(i);
        
        for (int t = 0; t < num_types; ++t) {
          LoaderFileType *type = reg->get_type(t);
          Filename file(directory, filename);
          file.set_extension(type->get_extension());
          
          if (use_vfs) {
            VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
            if (vfs->exists(file)) {
              results.add_file(file, type);
              ++num_added;
            }
          } else {
            if (file.exists()) {
              results.add_file(file, type);
              ++num_added;
            }
          }
        }
      }
    }
  }

  return num_added;
}

////////////////////////////////////////////////////////////////////
//     Function: Loader::request_load
//       Access: Published
//  Description: Requests an asynchronous load of a file.  The request
//               will be queued and served by the asynchronous thread.
//               If event_name is nonempty, it is the name of the
//               event that will be thrown (with the uint id as its
//               single parameter) when the loading is completed later.
//
//               The return value is an integer which can be used to
//               identify this particular request later to
//               fetch_load(), or 0 if there has been an error.
//
//               If search is true, the file is searched for along the
//               model path; otherwise, only the exact filename is
//               loaded.
////////////////////////////////////////////////////////////////////
uint Loader::
request_load(const string &event_name, const Filename &filename, bool search) {
  if (!_file_types_loaded) {
    load_file_types();
  }

  PT(LoaderToken) tok;
  if (asynchronous_loads) {

    // Make sure we actually are threaded
    if (!_threaded) {
      loader_cat.info()
        << "Loader::request_load() - create_thread() was "
        << "never called!  Calling it now..." << endl;
      create_thread();
    }

    // We need to grab the lock in order to signal the condition variable
#ifdef OLD_HAVE_IPC
    _lock.lock();
#endif

      if (_token_board->_waiting.full()) {
        loader_cat.error()
          << "Loader::request_load() - Too many pending requests\n";
        return 0;
      }

      if (loader_cat.is_debug()) {
        loader_cat.debug()
          << "Load requested for file: " << filename << "\n";
      }

      tok = new LoaderToken(_next_token++, event_name, filename, search);
      _token_board->_waiting.push_back(tok);

#ifdef OLD_HAVE_IPC
      _request_cond->signal();
    _lock.unlock();
#endif

  } else {
    // If we're not running asynchronously, process the load request
    // directly now.
    if (_token_board->_waiting.full()) {
      loader_cat.error()
        << "Loader::request_load() - Too many pending requests\n";
      return 0;
    }

    if (loader_cat.is_debug()) {
      loader_cat.debug()
        << "Load requested for file: " << filename << "\n";
    }

    tok = new LoaderToken(_next_token++, event_name, filename, search);
    _token_board->_waiting.push_back(tok);
    process_request();
  }

  return tok->_id;
}

////////////////////////////////////////////////////////////////////
//     Function: Loader::check_load
//       Access: Published
//  Description: Returns true if the indicated load-request has
//               completed and not yet been fetched, false otherwise.
////////////////////////////////////////////////////////////////////
bool Loader::
check_load(uint id) {
  return _token_board->is_done_token(id);
}

////////////////////////////////////////////////////////////////////
//     Function: Loader::fetch_load
//       Access: Published
//  Description: Returns the Node associated with the indicated id
//               number (returned by a previous call to request_load),
//               or NULL if the request has not yet completed.
////////////////////////////////////////////////////////////////////
PT(PandaNode) Loader::
fetch_load(uint id) {
  PT(LoaderToken) tok = _token_board->get_done_token(id);
  if (tok.is_null()) {
    loader_cat.debug()
      << "Request to fetch id " << id << " which has not yet completed.\n";
    return NULL;
  }
  PT(PandaNode) node = tok->_node;
  return node;
}

////////////////////////////////////////////////////////////////////
//     Function: Loader::load_file_types
//       Access: Private, Static
//  Description: Loads up all of the dynamic libraries named in a
//               load-file-type Configure variable.  Presumably this
//               will make the various file types available for
//               runtime loading.
////////////////////////////////////////////////////////////////////
void Loader::
load_file_types() {
  if (!_file_types_loaded) {
    int num_unique_values = load_file_type.get_num_unique_values();

    for (int i = 0; i < num_unique_values; i++) {
      string param = load_file_type.get_unique_value(i);

      vector_string words;
      extract_words(param, words);

      if (words.size() == 1) {
        // Exactly one word: load the named library immediately.
        string name = words[0];
        Filename dlname = Filename::dso_filename("lib" + name + ".so");
        loader_cat.info()
          << "loading file type module: " << name << endl;
        void *tmp = load_dso(dlname);
        if (tmp == (void *)NULL) {
          loader_cat.warning()
            << "Unable to load " << dlname.to_os_specific()
            << ": " << load_dso_error() << endl;
        }
        
      } else if (words.size() > 1) {
        // Multiple words: the first n words are filename extensions,
        // and the last word is the name of the library to load should
        // any of those filename extensions be encountered.
        LoaderFileTypeRegistry *registry = LoaderFileTypeRegistry::get_global_ptr();
        size_t num_extensions = words.size() - 1;
        string library_name = words[num_extensions];
        
        for (size_t i = 0; i < num_extensions; i++) {
          string extension = words[i];
          if (extension[0] == '.') {
            extension = extension.substr(1);
          }
          
          registry->register_deferred_type(extension, library_name);
        }
      }
    }

    _file_types_loaded = true;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: Loader::process_request
//       Access: Private
//  Description: Serves any requests on the token board, moving them
//               to the done queue.
////////////////////////////////////////////////////////////////////
bool Loader::
process_request() {
  if (_shutdown) {
    if (loader_cat.is_debug())
      loader_cat.debug()
          << "Loader shutting down...\n";
    return false;
  }

  // If there is actually a request token - process it
  while (!_token_board->_waiting.empty()) {
    PT(LoaderToken) tok = _token_board->_waiting.front();
    _token_board->_waiting.pop_front();
    tok->_node = load_file(tok->_path, tok->_search);
    if (tok->_node == (PandaNode *)NULL) {
      loader_cat.error()
        << "Loader::callback() - couldn't find file: "
        << tok->_path << "\n";
    } else {
      _token_board->_done.push_back(tok);

      // Throw a "done" event now.
      if (!tok->_event_name.empty()) {
        PT_Event done = new Event(tok->_event_name);
        done->add_parameter(EventParameter((int)tok->_id));
        throw_event(done);
      }
    }

    if (loader_cat.is_debug()) {
      loader_cat.debug()
        << "loading complete for " << tok->_path << "\n";
    }
  }

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: Loader::load_file
//       Access: Private
//  Description: Loads a single scene graph file, if possible.
//               Returns the Node that is the root of the file, or
//               NULL if the file cannot be loaded.
//
//               If search is true, the file is searched for along the
//               model path; otherwise, only the exact filename is
//               loaded.
////////////////////////////////////////////////////////////////////
PT(PandaNode) Loader::
load_file(const Filename &filename, bool search) const {
  Results results;
  int num_files;

  if (search) {
    // Look for the file along the model path.
    num_files = find_all_files(filename, get_model_path(), results);
  } else {
    // Look for the file only where it is.
    num_files = find_all_files(filename, DSearchPath("."), results);
  }

  if (num_files == 0) {
    // Couldn't find the file.  Either it doesn't exist, or it's an
    // unknown file type.  Report a useful message either way.
    string extension = filename.get_extension();
    if (!extension.empty()) {
      LoaderFileTypeRegistry *reg = LoaderFileTypeRegistry::get_global_ptr();
      LoaderFileType *requested_type =
        reg->get_type_from_extension(extension);
      if (requested_type == (LoaderFileType *)NULL) {
        loader_cat.error()
          << "Extension of file " << filename
          << " is unrecognized; cannot load.\n";
        loader_cat.error(false)
          << "Currently known scene file types are:\n";
        reg->write(loader_cat.error(false), 2);
        return NULL;
      }
    }

    if (search) {
      loader_cat.error()
        << "Couldn't load file " << filename << ": not found on model path "
        << "(which is currently: \"" << get_model_path() << "\")\n";
    } else {
      loader_cat.error()
        << "Couldn't load file ./" << filename << ": does not exist.\n";
    }
    return NULL;
  }

  for (int i = 0; i < num_files; i++) {
    const Filename &path = results.get_file(i);
    LoaderFileType *type = results.get_file_type(i);
    PT(PandaNode) result = type->load_file(path, true);
    if (result != (PandaNode *)NULL) {
      return result;
    }
  }

  // None of the matching files could be loaded.  Oh well.
  if (search) {
    loader_cat.error()
      << "Couldn't load file " << filename
      << ": all matching files on model path invalid.\n";

  } else {
    loader_cat.error()
      << "Couldn't load file " << filename
      << ": invalid.\n";
  }
  return NULL;
}

