//
// Copyright (c) 2006, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   26 Mar 06  Brian Frank  Creation
//

**
** File is used to represent a Uri path to a file or directory.
** See [examples]`examples::sys-files`.
**
abstract const class File
{

//////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////

  **
  ** Make a File for the Uri which represents a file on the local
  ** file system.  If creating a Uri to a directory, then the Uri
  ** must end in a trailing "/" slash or IOErr is thrown - or you
  ** may pass false for checkSlash in which case the trailing slash
  ** is implicitly added.  However if a trailing slash is added, then
  ** the resulting File's uri will not match the uri passed to this method.
  ** If the file doesn't exist, then it is assumed to be to a directory
  ** based on a trailing slash (see `isDir`).  If the Uri has a relative
  ** path, then it is assumed to be relative to the current working
  ** directory.  Throw ArgErr if the Uri has a scheme other than null
  ** or "file:".
  **
  static new make(Uri uri, Bool checkSlash := true)

  **
  ** Make a File for the specified operating system specific path
  ** on the local file system.
  **
  static File os(Str osPath)

  **
  ** Get the root directories of the operating system's local file system.
  **
  static File[] osRoots()

  **
  ** Create a temporary file which is guaranteed to be a new, empty
  ** file with a unique name.  The file name will be generated using
  ** the specified prefix and suffix.  If dir is non-null then it is used
  ** as the file's parent directory, otherwise the system's default
  ** temporary directory is used.  If dir is specified it must be a
  ** directory on the local file system.  See `deleteOnExit` if you wish
  ** to have the file automatically deleted on exit.  Throw IOErr on error.
  **
  ** Examples:
  **   File.createTemp("x", ".txt") => `/tmp/x67392.txt`
  **   File.createTemp.deleteOnExit => `/tmp/fan5284.tmp`
  **
  static File createTemp(Str prefix := "fan", Str suffix := ".tmp", File? dir := null)

  **
  ** Protected constructor for subclasses.
  **
  protected new makeNew(Uri uri)

//////////////////////////////////////////////////////////////////////////
// Identity
//////////////////////////////////////////////////////////////////////////

  **
  ** File equality is based on the un-normalized Uri used to create the File.
  **
  override Bool equals(Obj? that)

  **
  ** Return 'uri.hash'.
  **
  override Int hash()

  **
  ** Return 'uri.toStr'.
  **
  override Str toStr()

//////////////////////////////////////////////////////////////////////////
// Uri
//////////////////////////////////////////////////////////////////////////

  **
  ** Return the Uri path used to create this File.
  ** This Uri may be absolute or relative.
  **
  Uri uri()

  **
  ** Convenience for [uri.isDir]`Uri.isDir`
  **
  Bool isDir()

  **
  ** Convenience for [uri.path]`Uri.path`.
  **
  Str[] path()

  **
  ** Convenience for [uri.pathStr]`Uri.pathStr`.
  **
  Str pathStr()

  **
  ** Convenience for [uri.name]`Uri.name`.
  **
  Str name()

  **
  ** Convenience for [uri.basename]`Uri.basename`.
  **
  Str basename()

  **
  ** Convenience for [uri.ext]`Uri.ext`.
  **
  Str? ext()

  **
  ** Convenience for [uri.mimeType]`Uri.mimeType`.
  **
  MimeType? mimeType()

//////////////////////////////////////////////////////////////////////////
// Access
//////////////////////////////////////////////////////////////////////////

  **
  ** Return if this file exists.
  **
  abstract Bool exists()

  **
  ** Return the size of the file in bytes, otherwise null if a
  ** directory or unknown.
  **
  abstract Int? size()

  **
  ** If this is a file, return if the file size is zero or null.
  ** If this is a directory, return if this directory has no
  ** files without reading a full listing.
  **
  virtual Bool isEmpty()

  **
  ** Get time the file was last modified or null if unknown.
  **
  abstract DateTime? modified

  **
  ** Return if this file should be hidden to users.  On Unix hidden
  ** files start with a dot and on Windows a metadata flag is used.
  **
  virtual Bool isHidden()

  **
  ** Return if this file is readable.
  **
  virtual Bool isReadable()

  **
  ** Return if this file is writable.
  **
  virtual Bool isWritable()

  **
  ** Return if this file is executable.
  **
  virtual Bool isExecutable()

  **
  ** Get this File as an operating system specific path on
  ** the local system.  If this File doesn't represent a
  ** path on the local file system then return null.
  **
  abstract Str? osPath()

  **
  ** Get the parent directory of this file or null.
  ** Also see `Uri.parent`.
  **
  abstract File? parent()

  **
  ** List the files contained by this directory.  This list includes
  ** both child sub-directories and normal files.  If the directory
  ** is empty or this file doesn't represent a directory, then return
  ** an empty list.  If pattern is non-null then only filenames matched
  ** are returned or if null then all filenames.
  **
  abstract File[] list(Regex? pattern := null)

  **
  ** List the child sub-directories contained by this directory.  If
  ** the directory doesn't contain any sub-directories or this file
  ** doesn't represent a directory, then return an empty list.  If
  ** pattern is non-null then only filenames matched are returned or
  ** if null then all filenames.
  **
  virtual File[] listDirs(Regex? pattern := null)

  **
  ** List the child files (excludes directories) contained by this
  ** directory.  If the directory doesn't contain any child files
  ** or this file doesn't represent a directory, then return an
  ** empty list.  If pattern is non-null then only filenames matched
  ** are returned or if null then all filenames.
  **
  virtual File[] listFiles(Regex? pattern := null)

  **
  ** Recursively walk this file/directory top down.  If this
  ** file is not a directory then the callback is invoked exactly
  ** once with this file.  If a directory, then the callback
  ** is invoked with this file, then recursively for each child
  ** file.
  **
  virtual Void walk(|File f| c)

  **
  ** Normalize this file path to its canonical representation.
  ** If a file on the local file system, then the uri will
  ** include the "file:" scheme.  Throw IOErr on error.
  **
  abstract File normalize()

  **
  ** Make a new File instance by joining this file's Uri
  ** together with the specified path.  If the file maps
  ** to a directory and the resulting Uri doesn't end in
  ** slash then an IOErr is thrown - or pass false for
  ** checkSlash to have the slash implicitly added.
  **
  ** Examples:
  **   File(`a/b/`) + `c` => File(`a/b/c`)
  **   File(`a/b`) + `c`  => File(`a/c`)
  **
  @Operator abstract File plus(Uri path, Bool checkSlash := true)

  **
  ** Get the store instance which models the storage pool, device,
  ** partition, or volume used to store this file.  Raise UnsupportedErr
  ** if this file is not associated with a store.
  **
  virtual FileStore store()

//////////////////////////////////////////////////////////////////////////
// Management
//////////////////////////////////////////////////////////////////////////

  **
  ** Create a file or directory represented by this Uri.  If isDir() is
  ** false then create an empty file, or if the file already exists
  ** overwrite it to empty.  If isDir() is true then create a directory,
  ** or if the directory already exists do nothing.  This method will
  ** automatically create any parent directories.  Throw IOErr on error.
  ** Return this.
  **
  abstract File create()

  **
  ** Create a file under this directory.  Convenience for `create`:
  **   return (this+name.toUri).create
  ** Throw IOErr if this file is not a directory or if there is an
  ** error creating the new file.  Return the file created.
  **
  File createFile(Str name)

  **
  ** Create a sub-directory under this directory.  Convenience
  ** for `create`:
  **   return (this+name/.toUri).create
  ** Throw IOErr if this file is not a directory or if there is an
  ** error creating the new directory.  Return the directory created.
  **
  File createDir(Str name)

  **
  ** Copy this file or directory to the new specified location.
  ** If this file represents a directory, then it recursively
  ** copies the entire directory tree.
  **
  ** The options map is used to customize how the copy is performed.
  ** The following summarizes the options:
  **   - exclude:   Regex or |File f->Bool|
  **   - overwrite: Bool or |File f->Bool|
  **
  ** If the "exclude" option is a Regex - each source file's Uri string
  ** is checked for a match to skip.  If a directory is skipped, then
  ** its children are skipped also.  The exclude option can also be a
  ** function of type '|File f->Bool|' to check each file.  Exclude
  ** processing is performed first before checking for an overwrite.
  **
  ** If during the copy, an existing file of the same name is found,
  ** then the "overwrite" option should be 'true' to overwrite or
  ** 'false' to skip.  The overwrite option can also be a function
  ** of type '|File to,File from->Bool|' which is passed every to/from file
  ** to be overwritten.  If the overwrite function throws an exception,
  ** it is raised to the 'copyTo' caller.  If a directory overwrite is
  ** skipped, then its children are skipped too.  If options are null
  ** or overwrite is unspecified then the copy is immediately terminated
  ** with an IOErr.
  **
  ** Any IOErr or other error encountered during the file copy immediately
  ** terminates the copy and is raised to the caller, which might leave
  ** the copy in an unfinished state.
  **
  ** Return the 'to' destination file.
  **
  virtual File copyTo(File to, [Str:Obj]? options := null)

  **
  ** Copy this file under the specified directory and return
  ** the destination file.  This method is a convenience for:
  **   return this.copyTo(dir + this.name, options)
  **
  virtual File copyInto(File dir, [Str:Obj]? options := null)

  **
  ** Move this file to the specified location.  If this file is
  ** a directory, then the entire directory is moved.  If the
  ** target file already exists or the move fails, then an IOErr
  ** is thrown.  Return the 'to' destination file.
  **
  abstract File moveTo(File to)

  **
  ** Move this file under the specified directory and return
  ** the destination file.  This method is a convenience for:
  **   return this.moveTo(dir + this.name)
  **
  virtual File moveInto(File dir)

  **
  ** Renaming this file within its current directory.
  ** This method is a convenience for:
  **   return this.moveTo(parent + newName)
  **
  virtual File rename(Str newName)

  **
  ** Delete this file.  If this file represents a directory, then
  ** recursively delete it.  If the file does not exist, then no
  ** action is taken.  Throw IOErr on error.
  **
  abstract Void delete()

  **
  ** Request that the file or directory represented by this File
  ** be deleted when the virtual machine exits.  Long running applications
  ** should use this method with care since each file marked to delete will
  ** consume resources.  Throw IOErr on error.  Return this.
  **
  abstract File deleteOnExit()

//////////////////////////////////////////////////////////////////////////
// IO
//////////////////////////////////////////////////////////////////////////

  **
  ** Open this file for random access.  Modes are:
  **   - "r": open the file for reading only.  Throws IOErr
  **     if file does not exist.
  **   - "rw": open the file for reading and writing; create
  **     if the file does not exist.
  **
  ** The Buf instance returned is backed by a random access file
  ** pointer. It provides the same functionality as a memory backed
  ** buffer, except for a couple of exceptions such as `Buf.unread`.
  ** The resulting Buf is a raw interface to the random access
  ** file, no buffering is provided at the framework level - so
  ** use methods which only access a few bytes carefully.  However
  ** methods which transfer data with other Bufs and IO streams
  ** will use an internal buffer for efficiency.
  **
  abstract Buf open(Str mode := "rw")

  **
  ** Memory map the region of the file specified by 'pos' and 'size'.
  ** If size is null, then use the file's size as a default.  The
  ** file is paged into virtual memory on demand.  Modes are:
  **   - "r": map the file for reading only.  Throws IOErr
  **     if file does not exist.
  **   - "rw": open the file for reading and writing; create
  **     if the file does not exist.
  **   - "p": private read/write mode will not propagate changes
  **     to other processes which have mapped the file.
  **
  abstract Buf mmap(Str mode := "rw", Int pos := 0, Int? size := null)

  **
  ** Open a new buffered InStream used to read from this file.  A
  ** bufferSize of null or zero will return an unbuffered input stream.
  ** Throw IOErr on error.
  **
  abstract InStream in(Int? bufferSize := 4096)

  **
  ** Open a new buffered OutStream used to write to this file.  If append is
  ** true, then we open the file to append to the end, otherwise it is
  ** opened as an empty file.  A bufferSize of null or zero will return an
  ** unbuffered output stream.  Throw IOErr on error.
  **
  abstract OutStream out(Bool append := false, Int? bufferSize := 4096)

  **
  ** Convenience for [in.readAllBuf]`InStream.readAllBuf`.
  ** The input stream is guaranteed to be closed.
  **
  Buf readAllBuf()

  **
  ** Convenience for [in.readAllLines]`InStream.readAllLines`.
  ** The input stream is guaranteed to be closed.
  **
  Str[] readAllLines()

  **
  ** Convenience for [in.eachLine]`InStream.eachLine`.
  ** The input stream is guaranteed to be closed.
  **
  Void eachLine(|Str line| f)

  **
  ** Convenience for [in.readAllStr]`InStream.readAllStr`.
  ** The input stream is guaranteed to be closed.
  **
  Str readAllStr(Bool normalizeNewlines := true)

  **
  ** Convenience for [in.readProps()]`InStream.readProps`.
  ** The input stream is guaranteed to be closed.
  **
  Str:Str readProps()

  **
  ** Convenience for [out.writeProps()]`OutStream.writeProps`.
  ** The output stream is guaranteed to be closed.
  **
  Void writeProps(Str:Str props)

  **
  ** Convenience for [in.readObj]`InStream.readObj`
  ** The input stream is guaranteed to be closed.
  **
  Obj? readObj([Str:Obj]? options := null)

  **
  ** Convenience for [out.writeObj]`OutStream.writeObj`
  ** The output stream is guaranteed to be closed.
  **
  Void writeObj(Obj? obj, [Str:Obj]? options := null)

  **
  ** Return the platform's separator for names within
  ** a path: backslash on Windows, forward slash on Unix.
  **
  static const Str sep

  **
  ** Return the platform's separator for a list of
  ** paths: semicolon on Windows, colon on Unix.
  **
  static const Str pathSep

}

**************************************************************************
** LocalFile
**************************************************************************

internal const class LocalFile : File
{
  private new init()
  override Bool exists()
  override Int? size()
  override DateTime? modified
  override Str? osPath()
  override File? parent()
  override File[] list(Regex? pattern := null)
  override File normalize()
  override File plus(Uri uri, Bool checkSlash := true)
  override File create()
  override File moveTo(File to)
  override Void delete()
  override File deleteOnExit()
  override Buf open(Str mode := "rw")
  override Buf mmap(Str mode := "rw", Int pos := 0, Int? size := this.size)
  override InStream in(Int? bufferSize := 4096)
  override OutStream out(Bool append := false, Int? bufferSize := 4096)
}

**************************************************************************
** MemFile
**************************************************************************

internal const class MemFile : File
{
  internal new init()
  override Bool exists()
  override Int? size()
  override DateTime? modified
  override Str? osPath()
  override File? parent()
  override File[] list(Regex? pattern := null)
  override File normalize()
  override File plus(Uri uri, Bool checkSlash := true)
  override File create()
  override File moveTo(File to)
  override Void delete()
  override File deleteOnExit()
  override Buf open(Str mode := "rw")
  override Buf mmap(Str mode := "rw", Int pos := 0, Int? size := this.size)
  override InStream in(Int? bufferSize := 4096)
  override OutStream out(Bool append := false, Int? bufferSize := 4096)
}

**************************************************************************
** ZipEntryFile
**************************************************************************

internal const class ZipEntryFile : File
{
  internal new init()
  override Bool exists()
  override Int? size()
  override DateTime? modified
  override Str? osPath()
  override File? parent()
  override File[] list(Regex? pattern := null)
  override File normalize()
  override File plus(Uri uri, Bool checkSlash := true)
  override File create()
  override File moveTo(File to)
  override Void delete()
  override File deleteOnExit()
  override Buf open(Str mode := "rw")
  override Buf mmap(Str mode := "rw", Int pos := 0, Int? size := this.size)
  override InStream in(Int? bufferSize := 4096)
  override OutStream out(Bool append := false, Int? bufferSize := 4096)
}

**************************************************************************
** ClassLoaderFile
**************************************************************************

internal const class ClassLoaderFile : ZipEntryFile
{
  internal new init()
}

**************************************************************************
** PathFile
**************************************************************************

internal const class PathFile : File
{
  private new init()
  override Bool exists()
  override Int? size()
  override DateTime? modified
  override Str? osPath()
  override File? parent()
  override File[] list(Regex? pattern := null)
  override File normalize()
  override File plus(Uri uri, Bool checkSlash := true)
  override File create()
  override File moveTo(File to)
  override Void delete()
  override File deleteOnExit()
  override Buf open(Str mode := "rw")
  override Buf mmap(Str mode := "rw", Int pos := 0, Int? size := this.size)
  override InStream in(Int? bufferSize := 4096)
  override OutStream out(Bool append := false, Int? bufferSize := 4096)
}