// Filename: virtualFileMountRamdisk.cxx
// Created by:  drose (19Sep11)
//
////////////////////////////////////////////////////////////////////
//
// 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 "virtualFileMountRamdisk.h"
#include "subStream.h"
#include "dcast.h"

TypeHandle VirtualFileMountRamdisk::_type_handle;
TypeHandle VirtualFileMountRamdisk::FileBase::_type_handle;
TypeHandle VirtualFileMountRamdisk::File::_type_handle;
TypeHandle VirtualFileMountRamdisk::Directory::_type_handle;

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::Constructor
//       Access: Public
//  Description: 
////////////////////////////////////////////////////////////////////
VirtualFileMountRamdisk::
VirtualFileMountRamdisk() : _root("") {
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::has_file
//       Access: Public, Virtual
//  Description: Returns true if the indicated file exists within the
//               mount system.
////////////////////////////////////////////////////////////////////
bool VirtualFileMountRamdisk::
has_file(const Filename &file) const {
  ((VirtualFileMountRamdisk *)this)->_lock.acquire();
  PT(FileBase) f = _root.do_find_file(file);
  ((VirtualFileMountRamdisk *)this)->_lock.release();
  return (f != NULL);
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::create_file
//       Access: Public, Virtual
//  Description: Attempts to create the indicated file within the
//               mount, if it does not already exist.  Returns true on
//               success (or if the file already exists), or false if
//               it cannot be created.
////////////////////////////////////////////////////////////////////
bool VirtualFileMountRamdisk::
create_file(const Filename &file) {
  _lock.acquire();
  PT(File) f = _root.do_create_file(file);
  _lock.release();
  return (f != NULL);
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::delete_file
//       Access: Public, Virtual
//  Description: Attempts to delete the indicated file or directory
//               within the mount.  This can remove a single file or
//               an empty directory.  It will not remove a nonempty
//               directory.  Returns true on success, false on
//               failure.
////////////////////////////////////////////////////////////////////
bool VirtualFileMountRamdisk::
delete_file(const Filename &file) {
  _lock.acquire();
  PT(FileBase) f = _root.do_delete_file(file);
  _lock.release();
  return (f != NULL);
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::rename_file
//       Access: Public
//  Description: Attempts to rename the contents of the indicated file
//               to the indicated file.  Both filenames will be within
//               the mount.  Returns true on success, false on
//               failure.  If this returns false, this will be
//               attempted again with a copy-and-delete operation.
////////////////////////////////////////////////////////////////////
bool VirtualFileMountRamdisk::
rename_file(const Filename &orig_filename, const Filename &new_filename) {
  _lock.acquire();
  PT(FileBase) orig_fb = _root.do_find_file(orig_filename);
  if (orig_fb == NULL) {
    _lock.release();
    return false;
  }

  if (orig_fb->is_directory()) {
    // Rename the directory.
    Directory *orig_d = DCAST(Directory, orig_fb);
    PT(Directory) new_d = _root.do_make_directory(new_filename);
    if (new_d == NULL || !new_d->_files.empty()) {
      _lock.release();
      return false;
    }

    if (express_cat.is_debug()) {
      express_cat.debug()
        << "Renaming ramdisk directory " << orig_filename << " to " << new_filename << "\n";
    }

    new_d->_files.swap(orig_d->_files);
    _root.do_delete_file(orig_filename);
    _lock.release();
    return true;
  }

  // Rename the file.
  File *orig_f = DCAST(File, orig_fb);
  PT(File) new_f = _root.do_create_file(new_filename);
  if (new_f == NULL) {
    _lock.release();
    return false;
  }

  if (express_cat.is_debug()) {
    express_cat.debug()
      << "Renaming ramdisk file " << orig_filename << " to " << new_filename << "\n";
  }

  new_f->_data.str(orig_f->_data.str());
  _root.do_delete_file(orig_filename);

  _lock.release();
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::copy_file
//       Access: Public
//  Description: Attempts to copy the contents of the indicated file
//               to the indicated file.  Both filenames will be within
//               the mount.  Returns true on success, false on
//               failure.  If this returns false, the copy will be
//               performed by explicit read-and-write operations.
////////////////////////////////////////////////////////////////////
bool VirtualFileMountRamdisk::
copy_file(const Filename &orig_filename, const Filename &new_filename) {
  _lock.acquire();
  PT(FileBase) orig_fb = _root.do_find_file(orig_filename);
  if (orig_fb == NULL || orig_fb->is_directory()) {
    _lock.release();
    return false;
  }

  // Copy the file.
  File *orig_f = DCAST(File, orig_fb);
  PT(File) new_f = _root.do_create_file(new_filename);
  if (new_f == NULL) {
    _lock.release();
    return false;
  }

  if (express_cat.is_debug()) {
    express_cat.debug()
      << "Copying ramdisk file " << orig_filename << " to " << new_filename << "\n";
  }

  new_f->_data.str(orig_f->_data.str());

  _lock.release();
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::make_directory
//       Access: Public, Virtual
//  Description: Attempts to create the indicated file within the
//               mount, if it does not already exist.  Returns true on
//               success, or false if it cannot be created.  If the
//               directory already existed prior to this call, may
//               return either true or false.
////////////////////////////////////////////////////////////////////
bool VirtualFileMountRamdisk::
make_directory(const Filename &file) {
  _lock.acquire();
  PT(Directory) f = _root.do_make_directory(file);
  _lock.release();
  return (f != NULL);
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::is_directory
//       Access: Public, Virtual
//  Description: Returns true if the indicated file exists within the
//               mount system and is a directory.
////////////////////////////////////////////////////////////////////
bool VirtualFileMountRamdisk::
is_directory(const Filename &file) const {
  ((VirtualFileMountRamdisk *)this)->_lock.acquire();
  PT(FileBase) f = _root.do_find_file(file);
  ((VirtualFileMountRamdisk *)this)->_lock.release();
  return (f != NULL && f->is_directory());
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::is_regular_file
//       Access: Public, Virtual
//  Description: Returns true if the indicated file exists within the
//               mount system and is a regular file.
////////////////////////////////////////////////////////////////////
bool VirtualFileMountRamdisk::
is_regular_file(const Filename &file) const {
  ((VirtualFileMountRamdisk *)this)->_lock.acquire();
  PT(FileBase) f = _root.do_find_file(file);
  ((VirtualFileMountRamdisk *)this)->_lock.release();
  return (f != NULL && !f->is_directory());
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::is_writable
//       Access: Public, Virtual
//  Description: Returns true if the named file or directory may be
//               written to, false otherwise.
////////////////////////////////////////////////////////////////////
bool VirtualFileMountRamdisk::
is_writable(const Filename &file) const {
  return has_file(file);
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::open_read_file
//       Access: Public, Virtual
//  Description: Opens the file for reading, if it exists.  Returns a
//               newly allocated istream on success (which you should
//               eventually delete when you are done reading).
//               Returns NULL on failure.
////////////////////////////////////////////////////////////////////
istream *VirtualFileMountRamdisk::
open_read_file(const Filename &file) const {
  ((VirtualFileMountRamdisk *)this)->_lock.acquire();
  PT(FileBase) f = _root.do_find_file(file);
  ((VirtualFileMountRamdisk *)this)->_lock.release();
  if (f == (FileBase *)NULL || f->is_directory()) {
    return false;
  }

  File *f2 = DCAST(File, f);
  return new ISubStream(&f2->_wrapper, 0, 0);
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::open_write_file
//       Access: Published, Virtual
//  Description: Opens the file for writing.  Returns a newly
//               allocated ostream on success (which you should
//               eventually delete when you are done writing).
//               Returns NULL on failure.
////////////////////////////////////////////////////////////////////
ostream *VirtualFileMountRamdisk::
open_write_file(const Filename &file, bool truncate) {
  _lock.acquire();
  PT(File) f = _root.do_create_file(file);
  _lock.release();
  if (f == (File *)NULL) {
    return false;
  }

  if (truncate) {
    // Reset to an empty string.
    f->_data.str(string());
  }

  return new OSubStream(&f->_wrapper, 0, 0);
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::open_append_file
//       Access: Published
//  Description: Works like open_write_file(), but the file is opened
//               in append mode.  Like open_write_file, the returned
//               pointer should eventually be passed to
//               close_write_file().
////////////////////////////////////////////////////////////////////
ostream *VirtualFileMountRamdisk::
open_append_file(const Filename &file) {
  _lock.acquire();
  PT(File) f = _root.do_create_file(file);
  _lock.release();
  if (f == (File *)NULL) {
    return false;
  }

  return new OSubStream(&f->_wrapper, 0, 0, true);
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::open_read_write_file
//       Access: Published, Virtual
//  Description: Opens the file for writing.  Returns a newly
//               allocated iostream on success (which you should
//               eventually delete when you are done writing).
//               Returns NULL on failure.
////////////////////////////////////////////////////////////////////
iostream *VirtualFileMountRamdisk::
open_read_write_file(const Filename &file, bool truncate) {
  _lock.acquire();
  PT(File) f = _root.do_create_file(file);
  _lock.release();
  if (f == (File *)NULL) {
    return false;
  }

  return new SubStream(&f->_wrapper, 0, 0);
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::open_read_append_file
//       Access: Published, Virtual
//  Description: Works like open_read_write_file(), but the file is opened
//               in append mode.  Like open_read_write_file, the returned
//               pointer should eventually be passed to
//               close_read_write_file().
////////////////////////////////////////////////////////////////////
iostream *VirtualFileMountRamdisk::
open_read_append_file(const Filename &file) {
  _lock.acquire();
  PT(FileBase) f = _root.do_find_file(file);
  _lock.release();
  if (f == (FileBase *)NULL || f->is_directory()) {
    return false;
  }

  File *f2 = DCAST(File, f);
  return new SubStream(&f2->_wrapper, 0, 0, true);
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::get_file_size
//       Access: Published, Virtual
//  Description: Returns the current size on disk (or wherever it is)
//               of the already-open file.  Pass in the stream that
//               was returned by open_read_file(); some
//               implementations may require this stream to determine
//               the size.
////////////////////////////////////////////////////////////////////
off_t VirtualFileMountRamdisk::
get_file_size(const Filename &file, istream *stream) const {
  ((VirtualFileMountRamdisk *)this)->_lock.acquire();
  PT(FileBase) f = _root.do_find_file(file);
  ((VirtualFileMountRamdisk *)this)->_lock.release();
  if (f == (FileBase *)NULL || f->is_directory()) {
    return false;
  }

  File *f2 = DCAST(File, f);
  return f2->_data.str().length();
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::get_file_size
//       Access: Published, Virtual
//  Description: Returns the current size on disk (or wherever it is)
//               of the file before it has been opened.
////////////////////////////////////////////////////////////////////
off_t VirtualFileMountRamdisk::
get_file_size(const Filename &file) const {
  ((VirtualFileMountRamdisk *)this)->_lock.acquire();
  PT(FileBase) f = _root.do_find_file(file);
  ((VirtualFileMountRamdisk *)this)->_lock.release();
  if (f == (FileBase *)NULL || f->is_directory()) {
    return false;
  }

  File *f2 = DCAST(File, f);
  return f2->_data.str().length();
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::get_timestamp
//       Access: Published, Virtual
//  Description: Returns a time_t value that represents the time the
//               file was last modified, to within whatever precision
//               the operating system records this information (on a
//               Windows95 system, for instance, this may only be
//               accurate to within 2 seconds).
//
//               If the timestamp cannot be determined, either because
//               it is not supported by the operating system or
//               because there is some error (such as file not found),
//               returns 0.
////////////////////////////////////////////////////////////////////
time_t VirtualFileMountRamdisk::
get_timestamp(const Filename &file) const {
  return 0;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::scan_directory
//       Access: Public, Virtual
//  Description: Fills the given vector up with the list of filenames
//               that are local to this directory, if the filename is
//               a directory.  Returns true if successful, or false if
//               the file is not a directory or cannot be read.
////////////////////////////////////////////////////////////////////
bool VirtualFileMountRamdisk::
scan_directory(vector_string &contents, const Filename &dir) const {
  ((VirtualFileMountRamdisk *)this)->_lock.acquire();
  PT(FileBase) f = _root.do_find_file(dir);
  if (f == (FileBase *)NULL || !f->is_directory()) {
    ((VirtualFileMountRamdisk *)this)->_lock.release();
    return false;
  }

  Directory *f2 = DCAST(Directory, f);
  bool result = f2->do_scan_directory(contents);

  ((VirtualFileMountRamdisk *)this)->_lock.release();
  return result;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::atomic_compare_and_exchange_contents
//       Access: Public, Virtual
//  Description: See Filename::atomic_compare_and_exchange_contents().
////////////////////////////////////////////////////////////////////
bool VirtualFileMountRamdisk::
atomic_compare_and_exchange_contents(const Filename &file, string &orig_contents,
                                     const string &old_contents, 
                                     const string &new_contents) {
  _lock.acquire();
  PT(FileBase) f = _root.do_find_file(file);
  if (f == (FileBase *)NULL || f->is_directory()) {
    _lock.release();
    return false;
  }

  bool retval = false;
  File *f2 = DCAST(File, f);
  orig_contents = f2->_data.str();
  if (orig_contents == old_contents) {
    f2->_data.str(new_contents);
    retval = true;
  }

  _lock.release();
  return retval;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::atomic_read_contents
//       Access: Public, Virtual
//  Description: See Filename::atomic_read_contents().
////////////////////////////////////////////////////////////////////
bool VirtualFileMountRamdisk::
atomic_read_contents(const Filename &file, string &contents) const {
  ((VirtualFileMountRamdisk *)this)->_lock.acquire();
  PT(FileBase) f = _root.do_find_file(file);
  if (f == (FileBase *)NULL || f->is_directory()) {
    ((VirtualFileMountRamdisk *)this)->_lock.release();
    return false;
  }

  File *f2 = DCAST(File, f);
  contents = f2->_data.str();

  ((VirtualFileMountRamdisk *)this)->_lock.release();
  return true;
}


////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::output
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
void VirtualFileMountRamdisk::
output(ostream &out) const {
  out << "VirtualFileMountRamdisk";
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::FileBase::Destructor
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
VirtualFileMountRamdisk::FileBase::
~FileBase() {
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::FileBase::is_directory
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
bool VirtualFileMountRamdisk::FileBase::
is_directory() const {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::Directory::is_directory
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
bool VirtualFileMountRamdisk::Directory::
is_directory() const {
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::Directory::do_find_file
//       Access: Public
//  Description: Recursively search for the file with the indicated
//               name in this directory hierarchy.
////////////////////////////////////////////////////////////////////
PT(VirtualFileMountRamdisk::FileBase) VirtualFileMountRamdisk::Directory::
do_find_file(const string &filename) const {
  size_t slash = filename.find('/');
  if (slash == string::npos) {
    // Search for a file within the local directory.
    FileBase tfile(filename);
    tfile.local_object();
    Files::const_iterator fi = _files.find(&tfile);
    if (fi != _files.end()) {
      return (*fi);
    }
    return NULL;
  }

  // A nested directory.  Search for the directory name, then recurse.
  string dirname = filename.substr(0, slash);
  string remainder = filename.substr(slash + 1);
  FileBase tfile(dirname);
  tfile.local_object();
  Files::const_iterator fi = _files.find(&tfile);
  if (fi != _files.end()) {
    PT(FileBase) file = (*fi);
    if (file->is_directory()) {
      return DCAST(Directory, file.p())->do_find_file(remainder);
    }
  }

  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::Directory::do_create_file
//       Access: Public
//  Description: Recursively search for the file with the indicated
//               name in this directory hierarchy.  If not found,
//               creates a new file.
////////////////////////////////////////////////////////////////////
PT(VirtualFileMountRamdisk::File) VirtualFileMountRamdisk::Directory::
do_create_file(const string &filename) {
  size_t slash = filename.find('/');
  if (slash == string::npos) {
    // Search for a file within the local directory.
    FileBase tfile(filename);
    tfile.local_object();
    Files::iterator fi = _files.find(&tfile);
    if (fi != _files.end()) {
      PT(FileBase) file = (*fi);
      if (!file->is_directory()) {
        return DCAST(File, file.p());
      }
      // Cannot create: a directory by the same name already exists.
      return NULL;
    }

    // Create a new file.
    if (express_cat.is_debug()) {
      express_cat.debug()
        << "Making ramdisk file " << filename << "\n";
    }
    PT(File) file = new File(filename);
    _files.insert(file.p());
    return file;
  }

  // A nested directory.  Search for the directory name, then recurse.
  string dirname = filename.substr(0, slash);
  string remainder = filename.substr(slash + 1);
  FileBase tfile(dirname);
  tfile.local_object();
  Files::iterator fi = _files.find(&tfile);
  if (fi != _files.end()) {
    PT(FileBase) file = (*fi);
    if (file->is_directory()) {
      return DCAST(Directory, file.p())->do_create_file(remainder);
    }
  }

  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::Directory::do_make_directory
//       Access: Public
//  Description: Recursively search for the file with the indicated
//               name in this directory hierarchy.  If not found,
//               creates a new directory.
////////////////////////////////////////////////////////////////////
PT(VirtualFileMountRamdisk::Directory) VirtualFileMountRamdisk::Directory::
do_make_directory(const string &filename) {
  size_t slash = filename.find('/');
  if (slash == string::npos) {
    // Search for a file within the local directory.
    FileBase tfile(filename);
    tfile.local_object();
    Files::iterator fi = _files.find(&tfile);
    if (fi != _files.end()) {
      PT(FileBase) file = (*fi);
      if (file->is_directory()) {
        return DCAST(Directory, file.p());
      }
      // Cannot create: a file by the same name already exists.
      return NULL;
    }

    // Create a new directory.
    if (express_cat.is_debug()) {
      express_cat.debug()
        << "Making ramdisk directory " << filename << "\n";
    }
    PT(Directory) file = new Directory(filename);
    _files.insert(file.p());
    return file;
  }

  // A nested directory.  Search for the directory name, then recurse.
  string dirname = filename.substr(0, slash);
  string remainder = filename.substr(slash + 1);
  FileBase tfile(dirname);
  tfile.local_object();
  Files::iterator fi = _files.find(&tfile);
  if (fi != _files.end()) {
    PT(FileBase) file = (*fi);
    if (file->is_directory()) {
      return DCAST(Directory, file.p())->do_make_directory(remainder);
    }
  }

  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::Directory::do_delete_file
//       Access: Public
//  Description: Recursively search for the file with the indicated
//               name in this directory hierarchy, and removes it.
//               Returns the removed FileBase object.
////////////////////////////////////////////////////////////////////
PT(VirtualFileMountRamdisk::FileBase) VirtualFileMountRamdisk::Directory::
do_delete_file(const string &filename) {
  size_t slash = filename.find('/');
  if (slash == string::npos) {
    // Search for a file within the local directory.
    FileBase tfile(filename);
    tfile.local_object();
    Files::iterator fi = _files.find(&tfile);
    if (fi != _files.end()) {
      PT(FileBase) file = (*fi);
      if (file->is_directory()) {
        Directory *dir = DCAST(Directory, file.p());
        if (!dir->_files.empty()) {
          // Can't delete a nonempty directory.
          return NULL;
        }
      }
      _files.erase(fi);
      return file;
    }
    return NULL;
  }

  // A nested directory.  Search for the directory name, then recurse.
  string dirname = filename.substr(0, slash);
  string remainder = filename.substr(slash + 1);
  FileBase tfile(dirname);
  tfile.local_object();
  Files::iterator fi = _files.find(&tfile);
  if (fi != _files.end()) {
    PT(FileBase) file = (*fi);
    if (file->is_directory()) {
      return DCAST(Directory, file.p())->do_delete_file(remainder);
    }
  }

  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFileMountRamdisk::Directory::do_scan_directory
//       Access: Public
//  Description: 
////////////////////////////////////////////////////////////////////
bool VirtualFileMountRamdisk::Directory::
do_scan_directory(vector_string &contents) const {
  Files::const_iterator fi;
  for (fi = _files.begin(); fi != _files.end(); ++fi) {
    FileBase *file = (*fi);
    contents.push_back(file->_basename);
  }

  return true;
}
