// Filename: virtualFile.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 "virtualFile.h"
#include "virtualFileSystem.h"
#include "virtualFileList.h"
#include "config_express.h"
#include "pvector.h"
#include <iterator>

TypeHandle VirtualFile::_type_handle;

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::has_file
//       Access: Published, Virtual
//  Description: Returns true if this file exists, false otherwise.
////////////////////////////////////////////////////////////////////
bool VirtualFile::
has_file() const {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::is_directory
//       Access: Published, Virtual
//  Description: Returns true if this file represents a directory (and
//               scan_directory() may be called), false otherwise.
////////////////////////////////////////////////////////////////////
bool VirtualFile::
is_directory() const {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::is_regular_file
//       Access: Published, Virtual
//  Description: Returns true if this file represents a regular file
//               (and read_file() may be called), false otherwise.
////////////////////////////////////////////////////////////////////
bool VirtualFile::
is_regular_file() const {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::is_writable
//       Access: Published, Virtual
//  Description: Returns true if this file may be written to, which
//               implies write_file() may be called (unless it is a
//               directory instead of a regular file).
////////////////////////////////////////////////////////////////////
bool VirtualFile::
is_writable() const {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::delete_file
//       Access: Public
//  Description: Attempts to delete this file or directory.  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 VirtualFile::
delete_file() {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::rename_file
//       Access: Public
//  Description: Attempts to move or rename this file or directory.
//               If the original file is an ordinary file, it will
//               quietly replace any already-existing file in the new
//               filename (but not a directory).  If the original file
//               is a directory, the new filename must not already
//               exist.
//
//               If the file is a directory, the new filename must be
//               within the same mount point.  If the file is an
//               ordinary file, the new filename may be anywhere; but
//               if it is not within the same mount point then the
//               rename operation is automatically performed as a
//               two-step copy-and-delete operation.
////////////////////////////////////////////////////////////////////
bool VirtualFile::
rename_file(VirtualFile *new_file) {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::copy_file
//       Access: Public
//  Description: Attempts to copy the contents of this file to the
//               indicated file.  Returns true on success, false on
//               failure.
////////////////////////////////////////////////////////////////////
bool VirtualFile::
copy_file(VirtualFile *new_file) {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::scan_directory
//       Access: Published
//  Description: If the file represents a directory (that is,
//               is_directory() returns true), this returns the list
//               of files within the directory at the current time.
//               Returns NULL if the file is not a directory or if the
//               directory cannot be read.
////////////////////////////////////////////////////////////////////
PT(VirtualFileList) VirtualFile::
scan_directory() const {
  // First, we have to make sure there aren't any mount points attached
  // under this directory.  These will override any local filenames.
  VirtualFileSystem *file_system = get_file_system();
  Filename this_filename = get_filename();
  vector_string mount_points_flat;
  file_system->scan_mount_points(mount_points_flat, this_filename);

  // Copy the set of nested mount points to a sorted list so we can
  // search it quickly.
  ov_set<string> mount_points;
  copy(mount_points_flat.begin(), mount_points_flat.end(),
       back_inserter(mount_points));
  mount_points.sort();

  
  PT(VirtualFileList) file_list = new VirtualFileList;

  // Each of those mount points maps to a directory root or something
  // from the file system.
  ov_set<string>::const_iterator mi;
  for (mi = mount_points.begin(); mi != mount_points.end(); ++mi) {
    const string &basename = (*mi);
    Filename filename(this_filename, basename);
    PT(VirtualFile) file = file_system->get_file(filename);
    file_list->add_file(file);
  }

  // Now, get the actual local files in this directory.
  vector_string names;
  if (!scan_local_directory(file_list, mount_points)) {
    // Not a directory, or unable to read directory.
    if (file_list->get_num_files() == 0) {
      return NULL;
    }

    // We couldn't read the physical directory, but we do have some
    // mounted files to return.
    return file_list;
  }

  return file_list;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::output
//       Access: Published
//  Description: 
////////////////////////////////////////////////////////////////////
void VirtualFile::
output(ostream &out) const {
  out << get_filename();
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::ls
//       Access: Published
//  Description: If the file represents a directory, lists its
//               contents.
////////////////////////////////////////////////////////////////////
void VirtualFile::
ls(ostream &out) const {
  CPT(VirtualFileList) contents = scan_directory();
  if (contents == (VirtualFileList *)NULL) {
    if (!is_directory()) {
      out << get_filename() << "\n";
    } else {
      out << get_filename() << " cannot be read.\n";
    }
    return;
  }

  int num_files = contents->get_num_files();
  for (int i = 0; i < num_files; i++) {
    VirtualFile *file = contents->get_file(i);
    out << file->get_filename().get_basename() << "\n";
  }
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::ls_all
//       Access: Published
//  Description: If the file represents a directory, recursively lists
//               its contents and those of all subdirectories.
////////////////////////////////////////////////////////////////////
void VirtualFile::
ls_all(ostream &out) const {
  if (!is_directory()) {
    out << get_filename() << " is not a directory.\n";
  } else {
    r_ls_all(out, get_filename());
  }
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::open_read_file
//       Access: Published, Virtual
//  Description: Opens the file for reading.  Returns a newly
//               allocated istream on success (which you should
//               eventually delete when you are done reading).
//               Returns NULL on failure.
////////////////////////////////////////////////////////////////////
istream *VirtualFile::
open_read_file(bool auto_unwrap) const {
  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::close_read_file
//       Access: Published
//  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 VirtualFile::
close_read_file(istream *stream) const {
  nassertv(false);
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::was_read_successful
//       Access: Published, Virtual
//  Description: Call this method after a reading the istream returned
//               by open_read_file() to completion.  If it returns
//               true, the file was read completely and without error;
//               if it returns false, there may have been some errors
//               or a truncated file read.  This is particularly
//               likely if the stream is a VirtualFileHTTP.
////////////////////////////////////////////////////////////////////
bool VirtualFile::
was_read_successful() const {
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::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 *VirtualFile::
open_write_file(bool auto_wrap, bool truncate) {
  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::open_append_file
//       Access: Published, Virtual
//  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 *VirtualFile::
open_append_file() {
  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::close_write_file
//       Access: Published
//  Description: Closes a file opened by a previous call to
//               open_write_file().  This really just deletes the
//               ostream pointer, but it is recommended to use this
//               interface instead of deleting it explicitly, to help
//               work around compiler issues.
////////////////////////////////////////////////////////////////////
void VirtualFile::
close_write_file(ostream *stream) {
  nassertv(false);
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::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 *VirtualFile::
open_read_write_file(bool truncate) {
  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::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 *VirtualFile::
open_read_append_file() {
  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::close_read_write_file
//       Access: Published
//  Description: Closes a file opened by a previous call to
//               open_read_write_file().  This really just deletes the
//               iostream pointer, but it is recommended to use this
//               interface instead of deleting it explicitly, to help
//               work around compiler issues.
////////////////////////////////////////////////////////////////////
void VirtualFile::
close_read_write_file(iostream *stream) {
  nassertv(false);
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::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 VirtualFile::
get_file_size(istream *stream) const {
  return get_file_size();
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::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 VirtualFile::
get_file_size() const {
  return 0;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::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 VirtualFile::
get_timestamp() const {
  return 0;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::get_system_info
//       Access: Published, Virtual
//  Description: Populates the SubfileInfo structure with the data
//               representing where the file actually resides on disk,
//               if this is knowable.  Returns true if the file might
//               reside on disk, and the info is populated, or false
//               if it does not (or it is not known where the file
//               resides), in which case the info is meaningless.
////////////////////////////////////////////////////////////////////
bool VirtualFile::
get_system_info(SubfileInfo &info) {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::atomic_compare_and_exchange_contents
//       Access: Public, Virtual
//  Description: See Filename::atomic_compare_and_exchange_contents().
////////////////////////////////////////////////////////////////////
bool VirtualFile::
atomic_compare_and_exchange_contents(string &orig_contents,
                                     const string &old_contents, 
                                     const string &new_contents) {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::atomic_read_contents
//       Access: Public, Virtual
//  Description: See Filename::atomic_read_contents().
////////////////////////////////////////////////////////////////////
bool VirtualFile::
atomic_read_contents(string &contents) const {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::read_file
//       Access: Public
//  Description: Fills up the indicated string with the contents of
//               the file, if it is a regular file.  Returns true on
//               success, false otherwise.
////////////////////////////////////////////////////////////////////
bool VirtualFile::
read_file(string &result, bool auto_unwrap) const {
  result = string();

  pvector<unsigned char> pv;
  if (!read_file(pv, auto_unwrap)) {
    return false;
  }

  if (!pv.empty()) {
    result.append((const char *)&pv[0], pv.size());
  }

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::read_file
//       Access: Public, Virtual
//  Description: Fills up the indicated pvector with the contents of
//               the file, if it is a regular file.  Returns true on
//               success, false otherwise.
////////////////////////////////////////////////////////////////////
bool VirtualFile::
read_file(pvector<unsigned char> &result, bool auto_unwrap) const {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::write_file
//       Access: Public, Virtual
//  Description: Writes the indicated data to the file, if it is
//               writable.  Returns true on success, false otherwise.
////////////////////////////////////////////////////////////////////
bool VirtualFile::
write_file(const unsigned char *data, size_t data_size, bool auto_wrap) {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::simple_read_file
//       Access: Public, Static
//  Description: Fills up the indicated pvector with the contents of
//               the just-opened file.  Returns true on success, false
//               otherwise.  If the pvector was not empty on entry, the
//               data read from the file will be appended onto it.
////////////////////////////////////////////////////////////////////
bool VirtualFile::
simple_read_file(istream *in, pvector<unsigned char> &result) {
  static const size_t buffer_size = 4096;
  char buffer[buffer_size];

  in->read(buffer, buffer_size);
  size_t count = in->gcount();
  while (count != 0) {
    thread_consider_yield();
    result.insert(result.end(), buffer, buffer + count);
    in->read(buffer, buffer_size);
    count = in->gcount();
  }

  return (!in->fail() || in->eof());
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::simple_read_file
//       Access: Public
//  Description: As in simple_read_file() with two parameters, above,
//               but only reads up to max_bytes bytes from the file.
////////////////////////////////////////////////////////////////////
bool VirtualFile::
simple_read_file(istream *in, pvector<unsigned char> &result, size_t max_bytes) {
  static const size_t buffer_size = 4096;
  char buffer[buffer_size];

  in->read(buffer, min(buffer_size, max_bytes));
  size_t count = in->gcount();
  while (count != 0) {
    thread_consider_yield();
    nassertr(count <= max_bytes, false);
    result.insert(result.end(), buffer, buffer + count);
    max_bytes -= count;
    in->read(buffer, min(buffer_size, max_bytes));
    count = in->gcount();
  }

  return (!in->fail() || in->eof());
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::scan_local_directory
//       Access: Protected, Virtual
//  Description: Fills file_list up with the list of files that are
//               within this directory, excluding those whose
//               basenames are listed in mount_points.  Returns true
//               if successful, false if the file is not a directory
//               or the directory cannot be read.
////////////////////////////////////////////////////////////////////
bool VirtualFile::
scan_local_directory(VirtualFileList *, const ov_set<string> &) const {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: VirtualFile::r_ls_all
//       Access: Private
//  Description: The recursive implementation of ls_all().
////////////////////////////////////////////////////////////////////
void VirtualFile::
r_ls_all(ostream &out, const Filename &root) const {
  CPT(VirtualFileList) contents = scan_directory();
  if (contents == (VirtualFileList *)NULL) {
    return;
  }

  int num_files = contents->get_num_files();
  for (int i = 0; i < num_files; i++) {
    VirtualFile *file = contents->get_file(i);
    Filename filename = file->get_filename();
    filename.make_relative_to(root);
    out << filename << "\n";
    if (file->is_directory()) {
      file->r_ls_all(out, root);
    }
  }
}
