// Filename: virtualFileSystem.cxx
// Created by:  drose (03Aug02)
//
////////////////////////////////////////////////////////////////////
//
// 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 "virtualFileSystem.h"
#include "virtualFileSimple.h"
#include "virtualFileComposite.h"
#include "virtualFileMount.h"
#include "virtualFileMountMultifile.h"
#include "virtualFileMountSystem.h"
#include "streamWrapper.h"
#include "dSearchPath.h"
#include "dcast.h"
#include "config_express.h"
#include "executionEnvironment.h"
#include "pset.h"

VirtualFileSystem *VirtualFileSystem::_global_ptr = NULL;


////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::Constructor
//       Access: Published
//  Description: 
////////////////////////////////////////////////////////////////////
VirtualFileSystem::
VirtualFileSystem() :
  vfs_case_sensitive
("vfs-case-sensitive", true,
 PRC_DESC("Set this true to make the VirtualFileSystem present the native "
          "OS-provided filesystem as if it were a case-sensitive file "
          "system, even if it is not (e.g. on Windows).  This variable "
          "has no effect if the native filesystem is already case-sensitive, "
          "and it has no effect on mounted multifile systems, which are "
          "always case-sensitive.")),
  vfs_implicit_pz
  ("vfs-implicit-pz", true,
   PRC_DESC("When this is true, the VirtualFileSystem will pretend a named "
            "file exists even if it doesn't, as long as a filename with the "
            "same name and the additional extension .pz does exist.  In this "
            "case, the VirtualFileSystem will implicitly open the .pz file "
            "and decompress it on-the-fly.")),
  vfs_implicit_mf
  ("vfs-implicit-mf", false,
   PRC_DESC("When this is true, the VirtualFileSystem will automatically "
            "mount multifiles on-the-fly when they are used as directories.  "
            "For instance, opening the file /c/files/foo.mf/dirname/mytex.jpg "
            "will implicitly retrieve a file named 'dirname/mytex.jpg' "
            "within the multifile /c/files/foo.mf, even if the multifile "
            "has not already been mounted.  This makes all of your multifiles "
            "act like directories."))
{
  _cwd = "/";
  _mount_seq = 0;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::Destructor
//       Access: Published
//  Description: 
////////////////////////////////////////////////////////////////////
VirtualFileSystem::
~VirtualFileSystem() {
  unmount_all();
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::mount
//       Access: Published
//  Description: Mounts the indicated Multifile at the given mount
//               point.
////////////////////////////////////////////////////////////////////
bool VirtualFileSystem::
mount(Multifile *multifile, const Filename &mount_point, int flags) {
  PT(VirtualFileMountMultifile) new_mount = 
    new VirtualFileMountMultifile(multifile);
  return mount(new_mount, mount_point, flags);
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::mount
//       Access: Published
//  Description: Mounts the indicated system file or directory at the
//               given mount point.  If the named file is a directory,
//               mounts the directory.  If the named file is a
//               Multifile, mounts it as a Multifile.  Returns true on
//               success, false on failure.
//
//               A given system directory may be mounted to multiple
//               different mount point, and the same mount point may
//               share multiple system directories.  In the case of
//               ambiguities (that is, two different files with
//               exactly the same full pathname), the most-recently
//               mounted system wins.
//
//               Note that a mounted VirtualFileSystem directory is
//               fully case-sensitive, unlike the native Windows file
//               system, so you must refer to files within the virtual
//               file system with exactly the right case.
////////////////////////////////////////////////////////////////////
bool VirtualFileSystem::
mount(const Filename &physical_filename, const Filename &mount_point, 
      int flags, const string &password) {
  if (!physical_filename.exists()) {
    express_cat->warning()
      << "Attempt to mount " << physical_filename << ", not found.\n";
    return false;
  }

  if (physical_filename.is_directory()) {
    PT(VirtualFileMountSystem) new_mount =
      new VirtualFileMountSystem(physical_filename);
    return mount(new_mount, mount_point, flags);
  } else {
    // It's not a directory; it must be a Multifile.
    PT(Multifile) multifile = new Multifile;
    multifile->set_encryption_password(password);

    // For now these are always opened read only.  Maybe later we'll
    // support read-write on Multifiles.
    flags |= MF_read_only;
    if (!multifile->open_read(physical_filename)) {
      return false;
    }

    return mount(multifile, mount_point, flags);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::mount
//       Access: Published
//  Description: Adds the given VirtualFileMount object to the mount
//               list.  This is a lower-level function that the other
//               flavors of mount(); it requires you to create a
//               VirtualFileMount object specifically.
////////////////////////////////////////////////////////////////////
bool VirtualFileSystem::
mount(VirtualFileMount *mount, const Filename &mount_point, int flags) {
  _lock.acquire();
  bool result = do_mount(mount, mount_point, flags);
  _lock.release();
  return result;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::unmount
//       Access: Published
//  Description: Unmounts all appearances of the indicated Multifile
//               from the file system.  Returns the number of
//               appearances unmounted.
////////////////////////////////////////////////////////////////////
int VirtualFileSystem::
unmount(Multifile *multifile) {
  _lock.acquire();
  Mounts::iterator ri, wi;
  wi = ri = _mounts.begin();
  while (ri != _mounts.end()) {
    VirtualFileMount *mount = (*ri);
    (*wi) = mount;

    if (mount->is_exact_type(VirtualFileMountMultifile::get_class_type())) {
      VirtualFileMountMultifile *mmount = 
        DCAST(VirtualFileMountMultifile, mount);
      if (mmount->get_multifile() == multifile) {
        // Remove this one.  Don't increment wi.
        mount->_file_system = NULL;

      } else {
        // Don't remove this one.
        ++wi;
      }
    } else {
      // Don't remove this one.
      ++wi;
    }
    ++ri;
  }

  int num_removed = _mounts.end() - wi;
  _mounts.erase(wi, _mounts.end());
  ++_mount_seq;
  _lock.release();
  return num_removed;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::unmount
//       Access: Published
//  Description: Unmounts all appearances of the indicated directory
//               name or multifile name from the file system.  Returns
//               the number of appearances unmounted.
////////////////////////////////////////////////////////////////////
int VirtualFileSystem::
unmount(const Filename &physical_filename) {
  _lock.acquire();
  Mounts::iterator ri, wi;
  wi = ri = _mounts.begin();
  while (ri != _mounts.end()) {
    VirtualFileMount *mount = (*ri);
    (*wi) = mount;

    if (mount->is_exact_type(VirtualFileMountSystem::get_class_type())) {
      VirtualFileMountSystem *smount = 
        DCAST(VirtualFileMountSystem, mount);
      if (smount->get_physical_filename() == physical_filename) {
        // Remove this one.  Don't increment wi.
        mount->_file_system = NULL;
        
      } else {
        // Don't remove this one.
        ++wi;
      }

    } else if (mount->is_exact_type(VirtualFileMountMultifile::get_class_type())) {
      VirtualFileMountMultifile *mmount = 
        DCAST(VirtualFileMountMultifile, mount);
      if (mmount->get_multifile()->get_multifile_name() == physical_filename) {
        // Remove this one.  Don't increment wi.
        mount->_file_system = NULL;

      } else {
        // Don't remove this one.
        ++wi;
      }

    } else {
      // Don't remove this one.
      ++wi;
    }
    ++ri;
  }

  int num_removed = _mounts.end() - wi;
  _mounts.erase(wi, _mounts.end());
  ++_mount_seq;
  _lock.release();
  return num_removed;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::unmount
//       Access: Published
//  Description: Unmounts the indicated VirtualFileMount object
//               from the file system.  Returns the number of
//               appearances unmounted.
////////////////////////////////////////////////////////////////////
int VirtualFileSystem::
unmount(VirtualFileMount *mount) {
  _lock.acquire();
  Mounts::iterator ri, wi;
  wi = ri = _mounts.begin();
  while (ri != _mounts.end()) {
    (*wi) = (*ri);
    if ((*ri) == mount) {
      // Remove this one.  Don't increment wi.
      (*ri)->_file_system = NULL;

    } else {
      // Don't remove this one.
      ++wi;
    }
    ++ri;
  }

  int num_removed = _mounts.end() - wi;
  _mounts.erase(wi, _mounts.end());
  ++_mount_seq;
  _lock.release();
  return num_removed;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::unmount_point
//       Access: Published
//  Description: Unmounts all systems attached to the given mount
//               point from the file system.  Returns the number of
//               appearances unmounted.
////////////////////////////////////////////////////////////////////
int VirtualFileSystem::
unmount_point(const Filename &mount_point) {
  _lock.acquire();
  Filename nmp = normalize_mount_point(mount_point);
  Mounts::iterator ri, wi;
  wi = ri = _mounts.begin();
  while (ri != _mounts.end()) {
    VirtualFileMount *mount = (*ri);
    (*wi) = mount;

    if (mount->get_mount_point() == nmp) {
      // Remove this one.  Don't increment wi.
      mount->_file_system = NULL;

    } else {
      // Don't remove this one.
      ++wi;
    }
    ++ri;
  }

  int num_removed = _mounts.end() - wi;
  _mounts.erase(wi, _mounts.end());
  ++_mount_seq;
  _lock.release();
  return num_removed;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::unmount_all
//       Access: Published
//  Description: Unmounts all files from the file system.  Returns the
//               number of systems unmounted.
////////////////////////////////////////////////////////////////////
int VirtualFileSystem::
unmount_all() {
  _lock.acquire();
  Mounts::iterator ri;
  for (ri = _mounts.begin(); ri != _mounts.end(); ++ri) {
    (*ri)->_file_system = NULL;
  }

  int num_removed = _mounts.size();
  _mounts.clear();
  ++_mount_seq;
  _lock.release();
  return num_removed;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::get_num_mounts
//       Access: Published
//  Description: Returns the number of individual mounts in the
//               system.
////////////////////////////////////////////////////////////////////
int VirtualFileSystem::
get_num_mounts() const {
  ((VirtualFileSystem *)this)->_lock.acquire();
  int result = _mounts.size();
  ((VirtualFileSystem *)this)->_lock.release();
  return result;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::get_mount
//       Access: Published
//  Description: Returns the nth mount in the system.
////////////////////////////////////////////////////////////////////
PT(VirtualFileMount) VirtualFileSystem::
get_mount(int n) const {
  ((VirtualFileSystem *)this)->_lock.acquire();
  nassertd(n >= 0 && n < (int)_mounts.size()) {
    ((VirtualFileSystem *)this)->_lock.release();
    return NULL;
  }
  PT(VirtualFileMount) result = _mounts[n];
  ((VirtualFileSystem *)this)->_lock.release();
  return result;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::chdir
//       Access: Published
//  Description: Changes the current directory.  This is used to
//               resolve relative pathnames in get_file() and/or
//               find_file().  Returns true if successful, false
//               otherwise.
////////////////////////////////////////////////////////////////////
bool VirtualFileSystem::
chdir(const Filename &new_directory) {
  _lock.acquire();
  if (new_directory == "/") {
    // We can always return to the root.
    _cwd = new_directory;
    _lock.release();
    return true;
  }

  PT(VirtualFile) file = do_get_file(new_directory, true);
  if (file != (VirtualFile *)NULL && file->is_directory()) {
    _cwd = file->get_filename();
    _lock.release();
    return true;
  }
  _lock.release();
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::get_cwd
//       Access: Published
//  Description: Returns the current directory name.  See chdir().
////////////////////////////////////////////////////////////////////
Filename VirtualFileSystem::
get_cwd() const {
  ((VirtualFileSystem *)this)->_lock.acquire();
  Filename result = _cwd;
  ((VirtualFileSystem *)this)->_lock.release();
  return result;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::get_file
//       Access: Published
//  Description: Looks up the file by the indicated name in the file
//               system.  Returns a VirtualFile pointer representing
//               the file if it is found, or NULL if it is not.
//
//               If status_only is true, the file will be checked for
//               existence and length and so on, but the returned
//               file's contents cannot be read.  This is an
//               optimization which is especially important for
//               certain mount types, for instance HTTP, for which
//               opening a file to determine its status is
//               substantially less expensive than opening it to read
//               its contents.
////////////////////////////////////////////////////////////////////
PT(VirtualFile) VirtualFileSystem::
get_file(const Filename &filename, bool status_only) const {
  ((VirtualFileSystem *)this)->_lock.acquire();
  PT(VirtualFile) result = do_get_file(filename, status_only);
  ((VirtualFileSystem *)this)->_lock.release();
  return result;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::find_file
//       Access: Published
//  Description: Uses the indicated search path to find the file
//               within the file system.  Returns the first occurrence
//               of the file found, or NULL if the file cannot be
//               found.
////////////////////////////////////////////////////////////////////
PT(VirtualFile) VirtualFileSystem::
find_file(const Filename &filename, const DSearchPath &searchpath,
          bool status_only) const {
  if (!filename.is_local()) {
    return get_file(filename, status_only);
  }

  int num_directories = searchpath.get_num_directories();
  for (int i = 0; i < num_directories; ++i) {
    Filename match(searchpath.get_directory(i), filename);
    if (searchpath.get_directory(i) == "." && 
        filename.is_fully_qualified()) {
      // A special case for the "." directory: to avoid prefixing an
      // endless stream of ./ in front of files, if the filename
      // already has a ./ prefixed (i.e. is_fully_qualified() is
      // true), we don't prefix another one.
      match = filename;
    }
    PT(VirtualFile) found_file = get_file(match, status_only);
    if (found_file != (VirtualFile *)NULL) {
      return found_file;
    }
  }

  return NULL;
}


////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::resolve_filename
//       Access: Public
//  Description: Searches the given search path for the filename.  If
//               it is found, updates the filename to the full
//               pathname found and returns true; otherwise, returns
//               false.
////////////////////////////////////////////////////////////////////
bool VirtualFileSystem::
resolve_filename(Filename &filename,
                 const DSearchPath &searchpath,
                 const string &default_extension) const {
  PT(VirtualFile) found;

  if (filename.is_local()) {
    found = find_file(filename, searchpath, true);

    if (found.is_null()) {
      // We didn't find it with the given extension; can we try the
      // default extension?
      if (filename.get_extension().empty() && !default_extension.empty()) {
        Filename try_ext = filename;
        try_ext.set_extension(default_extension);
        found = find_file(try_ext, searchpath, true);
      }
    }
  } else {
    if (exists(filename)) {
      // The full pathname exists.  Return true.
      return true;
    } else {
      // The full pathname doesn't exist with the given extension;
      // does it exist with the default extension?
      if (filename.get_extension().empty() && !default_extension.empty()) {
        Filename try_ext = filename;
        try_ext.set_extension(default_extension);
        found = get_file(try_ext, true);
      }
    }
  }

  if (!found.is_null()) {
    filename = found->get_original_filename();
    return true;
  }

  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::find_all_files
//       Access: Public
//  Description: Searches all the directories in the search list for
//               the indicated file, in order.  Fills up the results
//               list with *all* of the matching filenames found, if
//               any.  Returns the number of matches found.
//
//               It is the responsibility of the the caller to clear
//               the results list first; otherwise, the newly-found
//               files will be appended to the list.
////////////////////////////////////////////////////////////////////
int VirtualFileSystem::
find_all_files(const Filename &filename, const DSearchPath &searchpath,
               DSearchPath::Results &results) const {
  int num_added = 0;

  if (filename.is_local()) {
    int num_directories = searchpath.get_num_directories();
    for (int i = 0; i < num_directories; ++i) {
      Filename match(searchpath.get_directory(i), filename);
      if (exists(match)) {
        if (searchpath.get_directory(i) == "." &&
            filename.is_fully_qualified()) {
          // A special case for the "." directory: to avoid prefixing
          // an endless stream of ./ in front of files, if the
          // filename already has a ./ prefixed
          // (i.e. is_fully_fully_qualified() is true), we don't
          // prefix another one.
          results.add_file(filename);
        } else {
          results.add_file(match);
        }
        ++num_added;
      }
    }
  }

  return num_added;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::write
//       Access: Published
//  Description: Print debugging information.
//               (e.g. from Python or gdb prompt).
////////////////////////////////////////////////////////////////////
void VirtualFileSystem::
write(ostream &out) const {
  ((VirtualFileSystem *)this)->_lock.acquire();
  Mounts::const_iterator mi;
  for (mi = _mounts.begin(); mi != _mounts.end(); ++mi) {
    VirtualFileMount *mount = (*mi);
    mount->write(out);
  }
  ((VirtualFileSystem *)this)->_lock.release();
}


////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::get_global_ptr
//       Access: Published, Static
//  Description: Returns the default global VirtualFileSystem.  You
//               may create your own personal VirtualFileSystem
//               objects and use them for whatever you like, but Panda
//               will attempt to load models and stuff from this
//               default object.
//
//               Initially, the global VirtualFileSystem is set up to
//               mount the OS filesystem to root; i.e. it is
//               equivalent to the OS filesystem.  This may be
//               subsequently adjusted by the user.
////////////////////////////////////////////////////////////////////
VirtualFileSystem *VirtualFileSystem::
get_global_ptr() {
  if (_global_ptr == (VirtualFileSystem *)NULL) {
    // Make sure this is initialized.
    init_libexpress();

    _global_ptr = new VirtualFileSystem;
    
    // Set up the default mounts.  First, there is always the root
    // mount.
    _global_ptr->mount("/", "/", 0);

    // And our initial cwd comes from the environment.
    _global_ptr->chdir(ExecutionEnvironment::get_cwd());

    // Then, we add whatever mounts are listed in the Configrc file.
    ConfigVariableList mounts
      ("vfs-mount",
       PRC_DESC("vfs-mount system-filename mount-point [options]"));

    int num_unique_values = mounts.get_num_unique_values();
    for (int i = 0; i < num_unique_values; i++) {
      string mount_desc = mounts.get_unique_value(i);

      // The vfs-mount syntax is:
      
      // vfs-mount system-filename mount-point [options]
      
      // The last two spaces mark the beginning of the mount point,
      // and of the options, respectively.  There might be multiple
      // spaces in the system filename, which are part of the
      // filename.
      
      // The last space marks the beginning of the mount point.
      // Spaces before that are part of the system filename.
      size_t space = mount_desc.rfind(' ');
      if (space == string::npos) {
        express_cat.warning()
          << "No space in vfs-mount descriptor: " << mount_desc << "\n";
        
      } else {
        string mount_point = mount_desc.substr(space + 1);
        while (space > 0 && isspace(mount_desc[space - 1])) {
          space--;
        }
        mount_desc = mount_desc.substr(0, space);
        string options;
        
        space = mount_desc.rfind(' ');
        if (space != string::npos) {
          // If there's another space, we have the optional options field.
          options = mount_point;
          mount_point = mount_desc.substr(space + 1);
          while (space > 0 && isspace(mount_desc[space - 1])) {
            --space;
          }
          mount_desc = mount_desc.substr(0, space);
        }
        
        mount_desc = ExecutionEnvironment::expand_string(mount_desc);
        Filename physical_filename = Filename::from_os_specific(mount_desc);
        
        int flags = 0;
        string password;
        
        // Split the options up by commas.
        size_t p = 0;
        size_t q = options.find(',', p);
        while (q != string::npos) {
          parse_option(options.substr(p, q - p),
                       flags, password);
          p = q + 1;
          q = options.find(',', p);
        }
        parse_option(options.substr(p), flags, password);
        
        _global_ptr->mount(physical_filename, mount_point, flags, password);
      }
    }
  }

  return _global_ptr;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::open_read_file
//       Access: Published
//  Description: Convenience function; returns a newly allocated
//               istream if the file exists and can be read, or NULL
//               otherwise.  Does not return an invalid istream.
//
//               If auto_unwrap is true, an explicitly-named .pz file
//               is automatically decompressed and the decompressed
//               contents are returned.  This is different than
//               vfs-implicit-pz, which will automatically decompress
//               a file if the extension .pz is *not* given.
////////////////////////////////////////////////////////////////////
istream *VirtualFileSystem::
open_read_file(const Filename &filename, bool auto_unwrap) const {
  PT(VirtualFile) file = get_file(filename, false);
  if (file == (VirtualFile *)NULL) {
    return NULL;
  }
  istream *str = file->open_read_file(auto_unwrap);
  if (str != (istream *)NULL && str->fail()) {
    close_read_file(str);
    str = (istream *)NULL;
  }
  return str;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::close_read_file
//       Access: Published, Static
//  Description: Closes a file opened by a previous call to
//               open_read_file().  This really just deletes the
//               istream pointer, but it is recommended to use this
//               interface instead of deleting it explicitly, to help
//               work around compiler issues.
////////////////////////////////////////////////////////////////////
void VirtualFileSystem::
close_read_file(istream *stream) {
  if (stream != (istream *)NULL) {
    // For some reason--compiler bug in gcc 3.2?--explicitly deleting
    // the stream 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.
#if !defined(WIN32_VC) && !defined(USE_MEMORY_NOWRAPPERS) && defined(REDEFINE_GLOBAL_OPERATOR_NEW)
    stream->~istream();
    (*global_operator_delete)(stream);
#else
    delete stream;
#endif
  }
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::scan_mount_points
//       Access: Public
//  Description: Adds to names a list of all the mount points in use
//               that are one directory below path, if any.  That is,
//               these are the external files or directories mounted
//               directly to the indicated path.
//
//               The names vector is filled with a set of basenames,
//               the basename part of the mount point.
////////////////////////////////////////////////////////////////////
void VirtualFileSystem::
scan_mount_points(vector_string &names, const Filename &path) const {
  nassertv(!path.empty() && !path.is_local());
  string prefix = path.get_fullpath().substr(1);
  Mounts::const_iterator mi;
  for (mi = _mounts.begin(); mi != _mounts.end(); ++mi) {
    VirtualFileMount *mount = (*mi);
    
    string mount_point = mount->get_mount_point();
    if (prefix.empty()) {
      // The indicated path is the root.  Is the mount point on the
      // root?
      if (mount_point.find('/') == string::npos) {
        // No embedded slashes, so the mount point is only one
        // directory below the root.
        names.push_back(mount_point);
      }
    } else {
      if (mount_point.substr(0, prefix.length()) == prefix &&
          mount_point.length() > prefix.length() &&
          mount_point[prefix.length()] == '/') {
        // This mount point is below the indicated path.  Is it only one
        // directory below?
        string basename = mount_point.substr(prefix.length());
        if (basename.find('/') == string::npos) {
          // No embedded slashes, so it's only one directory below.
          names.push_back(basename);
        }
      }
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::parse_option
//       Access: Public, Static
//  Description: Parses one of the option flags in the options list on
//               the vfs-mount Config.prc line.
////////////////////////////////////////////////////////////////////
void VirtualFileSystem::
parse_option(const string &option, int &flags, string &password) {
  if (option == "0" || option.empty()) {
    // 0 is the null option.
  } else if (option == "ro") {
    flags |= MF_read_only;
  } else if (option.substr(0, 3) == "pw:") {
    password = option.substr(3);
  } else {
    express_cat.warning()
      << "Invalid option on vfs-mount: \"" << option << "\"\n";
  }
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::normalize_mount_point
//       Access: Private
//  Description: Converts the mount point string supplied by the user
//               to standard form (relative to the current directory,
//               with no double slashes, and not terminating with a
//               slash).  The initial slash is removed.
//
//               Assumes the lock is already held.
////////////////////////////////////////////////////////////////////
Filename VirtualFileSystem::
normalize_mount_point(const Filename &mount_point) const {
  Filename nmp = mount_point;
  if (nmp.is_local()) {
    nmp = Filename(_cwd, mount_point);
  }
  nmp.standardize();
  nassertr(!nmp.empty() && nmp[0] == '/', nmp);
  return nmp.get_fullpath().substr(1);
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::do_mount
//       Access: Private
//  Description: The private implementation of mount().  Assumes the
//               lock is already held.
////////////////////////////////////////////////////////////////////
bool VirtualFileSystem::
do_mount(VirtualFileMount *mount, const Filename &mount_point, int flags) {
  nassertr(mount->_file_system == NULL, false);
  mount->_file_system = this;
  mount->_mount_point = normalize_mount_point(mount_point);
  mount->_mount_flags = flags;
  _mounts.push_back(mount);
  ++_mount_seq;
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::do_get_file
//       Access: Private
//  Description: The private implementation of get_file().  Assumes
//               the lock is already held.
////////////////////////////////////////////////////////////////////
PT(VirtualFile) VirtualFileSystem::
do_get_file(const Filename &filename, bool status_only) const {
  nassertr(!filename.empty(), NULL);
  Filename pathname(filename);
  if (pathname.is_local()) {
    pathname = Filename(_cwd, filename);
    if (filename.is_text()) {
      pathname.set_text();
    }
  }
  pathname.standardize();
  Filename strpath = pathname.get_filename_index(0).get_fullpath().substr(1);
  // Also transparently look for a regular file suffixed .pz.
  Filename strpath_pz = strpath + ".pz";

  // Now scan all the mount points, from the back (since later mounts
  // override more recent ones), until a match is found.
  PT(VirtualFile) found_file = NULL;
  VirtualFileComposite *composite_file = NULL;

  // We use an index instead of an iterator, since the vector might
  // change if implicit mounts are added during this loop.
  unsigned int start_seq = _mount_seq;

  size_t i = _mounts.size();
  while (i > 0) {
    --i;
    VirtualFileMount *mount = _mounts[i];
    Filename mount_point = mount->get_mount_point();
    if (strpath == mount_point) {
      // Here's an exact match on the mount point.  This filename is
      // the root directory of this mount object.
      if (consider_match(found_file, composite_file, mount, "", pathname,
                         false, status_only)) {
        return found_file;
      }
    } else if (mount_point.empty()) {
      // This is the root mount point; all files are in here.
      if (consider_match(found_file, composite_file, mount, strpath, 
                         pathname, false, status_only)) {
        return found_file;
      }
#ifdef HAVE_ZLIB
      if (vfs_implicit_pz) {
        if (consider_match(found_file, composite_file, mount, strpath_pz, 
                           pathname, true, status_only)) {
          return found_file;
        }
      }
#endif  // HAVE_ZLIB

    } else if (strpath.length() > mount_point.length() &&
               mount_point == strpath.substr(0, mount_point.length()) &&
               strpath[mount_point.length()] == '/') {
      // This pathname falls within this mount system.
      Filename local_filename = strpath.substr(mount_point.length() + 1);
      Filename local_filename_pz = strpath_pz.substr(mount_point.length() + 1);
      if (consider_match(found_file, composite_file, mount, local_filename, 
                         pathname, false, status_only)) {
        return found_file;
      }
#ifdef HAVE_ZLIB
      if (vfs_implicit_pz) {
        // Bingo!
        if (consider_match(found_file, composite_file, mount, local_filename_pz,
                           pathname, true, status_only)) {
          return found_file;
        }
      }
#endif  // HAVE_ZLIB
    }

    // If we discover that a file has been implicitly mounted during
    // one of the above operations, start over from the beginning of
    // the loop.
    if (start_seq != _mount_seq) {
      start_seq = _mount_seq;
      i = _mounts.size();
    }
  }

  if (found_file == (VirtualFile *)NULL && vfs_implicit_mf) {
    // The file wasn't found, as-is.  Does it appear to be an implicit
    // .mf file reference?
    ((VirtualFileSystem *)this)->consider_mount_mf(filename);

    if (start_seq != _mount_seq) {
      // Yes, it was, or some nested file was.  Now that we've
      // implicitly mounted the .mf file, go back and look again.
      return do_get_file(filename, status_only);
    }
  }

  return found_file;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::consider_match
//       Access: Private
//  Description: Evaluates one possible filename match found during a
//               get_file() operation.  There may be multiple matches
//               for a particular filename due to the ambiguities
//               introduced by allowing multiple mount points, so we
//               may have to keep searching even after the first match
//               is found.
//
//               Returns true if the search should terminate now, or
//               false if it should keep iterating.
////////////////////////////////////////////////////////////////////
bool VirtualFileSystem::
consider_match(PT(VirtualFile) &found_file, VirtualFileComposite *&composite_file,
               VirtualFileMount *mount, const Filename &local_filename,
               const Filename &original_filename, bool implicit_pz_file,
               bool status_only) const {
  PT(VirtualFile) vfile = 
    mount->make_virtual_file(local_filename, original_filename, false, status_only);
  if (!vfile->has_file()) {
    // Keep looking.
    return false;
  }

  if (found_file == (VirtualFile *)NULL) {
    // This was our first match.  Save it.
    found_file = vfile;
    if (!found_file->is_directory()) {
      // If it's not a directory, we're done.
      return true;
    }
    // It is a directory, so save it for later.
    if (implicit_pz_file) {
      // Don't look for directories named file.pz.
      found_file = NULL;
    }

  } else {
    // This was our second match.  The previous match(es) must
    // have been directories.
    if (!vfile->is_directory()) {
      // However, this one isn't a directory.  We're done.
      return true;
    }

    if (!implicit_pz_file) {
      // At least two directories matched to the same path.  We
      // need a composite directory.
      if (composite_file == (VirtualFileComposite *)NULL) {
        composite_file =
          new VirtualFileComposite((VirtualFileSystem *)this, found_file->get_original_filename());
        composite_file->set_original_filename(original_filename);
        composite_file->add_component(found_file);
        found_file = composite_file;
      }

      composite_file->add_component(vfile);
    }
  }

  // Keep going, looking for more directories.
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileSystem::consider_mount_mf
//       Access: Private
//  Description: The indicated filename was not found.  Check to see
//               if it is using an implicit reference to a .mf file as
//               a directory, that hasn't already been mounted.  If it
//               is, mount the .mf file in-place, and return true; if
//               it is not, or if its .mf file is already mounted
//               in-place, return false.
//
//               Assumes the lock is already held.
////////////////////////////////////////////////////////////////////
bool VirtualFileSystem::
consider_mount_mf(const Filename &filename) {
  Filename dirname = filename.get_dirname();
  if (dirname.empty() || dirname == filename) {
    // Reached the top directory; no .mf file references.
    return false;
  }
  if (is_directory(dirname)) {
    // Reached a real (or already-mounted) directory; no unmounted .mf
    // file references.
    return false;
  }
  if (dirname.get_extension() == "mf") {
    // Hey, here's a multifile reference!
    dirname.set_binary();
    PT(VirtualFile) file = do_get_file(dirname, false);
    if (file == (VirtualFile *)NULL || !file->is_regular_file()) {
      // Oh, never mind.  Not a real file.
      return false;
    }

    PT(Multifile) multifile = new Multifile;
   
    istream *stream = file->open_read_file(false);
    if (stream == (istream *)NULL) {
      // Couldn't read file.
      return false;
    }

    // Wrap a thread-safe wrapper around that stream, so multiple
    // threads can safely read the multifile simultaneously.
    IStreamWrapper *streamw = new IStreamWrapper(stream, true);

    if (!multifile->open_read(streamw, true)) {
      // Invalid multifile.
      return false;
    }

    multifile->set_multifile_name(dirname.get_basename());
    express_cat->info()
      << "Implicitly mounting " << dirname << "\n";

    PT(VirtualFileMountMultifile) new_mount = 
      new VirtualFileMountMultifile(multifile);
    return do_mount(new_mount, dirname, MF_read_only);
  }

  // Recurse.
  return consider_mount_mf(dirname);
}
