/**
 * =========================================================================
 * File        : file.h
 * Project     : 0 A.D.
 * Description : file layer on top of POSIX. avoids the need for
 *             : absolute paths and provides fast I/O.
 * =========================================================================
 */

// license: GPL; see lib/license.txt

#ifndef INCLUDED_FILE
#define INCLUDED_FILE

#include "lib/posix/posix_filesystem.h"     // struct stat


namespace ERR
{
    const LibError FILE_ACCESS     = -110000;
    const LibError FILE_NOT_MAPPED = -110001;
    const LibError DIR_END         = -110002;
}

extern LibError file_init();

// used by vfs_redirector to call various file objects' methods.
struct FileProvider_VTbl;


//
// path conversion functions (native <--> portable),
// for external libraries that require the real filename.
//
// replaces '/' with platform's directory separator and vice versa.
// verifies path length < PATH_MAX (otherwise return ERR::PATH_LENGTH).
//

// relative paths (relative to root dir)
extern LibError file_make_native_path(const char* path, char* n_path);
extern LibError file_make_portable_path(const char* n_path, char* path);

// as above, but with full native paths (portable paths are always relative).
// prepends current directory, resp. makes sure it matches the given path.
extern LibError file_make_full_native_path(const char* path, char* n_full_path);
extern LibError file_make_full_portable_path(const char* n_full_path, char* path);


// establish the root directory from <rel_path>, which is treated as
// relative to the executable's directory (determined via argv[0]).
// all relative file paths passed to this module will be based from
// this root dir. 
//
// example: executable in "$install_dir/system"; desired root dir is
// "$install_dir/data" => rel_path = "../data".
//
// argv[0] is necessary because the current directory is unknown at startup
// (e.g. it isn't set when invoked via batch file), and this is the
// easiest portable way to find our install directory.
//
// can only be called once, by design (see below). rel_path is trusted.
extern LibError file_set_root_dir(const char* argv0, const char* rel_path);


// allocate a copy of P_fn in our string pool. strings are equal iff
// their addresses are equal, thus allowing fast comparison.
//
// if the (generous) filename storage is full, 0 is returned.
// this is not ever expected to happen; callers need not check the
// return value because a warning is raised anyway.
extern const char* file_make_unique_fn_copy(const char* P_fn);

extern const char* file_get_random_name();


//
// directory
//

const size_t DIR_ITERATOR_OPAQUE_SIZE = 40;

// layer on top of POSIX opendir/readdir/closedir that handles
// portable -> native path conversion, ignores non-file/directory entries,
// and additionally returns the file status (size and mtime).

// directory state initialized by dir_open.
// rationale: some private storage apart from opendir's DIR* is required
// to support stat(). we prefer having the caller reserve room (on the stack)
// rather than allocating dynamically (less efficient or more complicated).
//
// this is an opaque struct to avoid exposing our internals and insulate
// user code against changes; we verify at compile-time that the
// public/private definitions match.
// note: cannot just typedef to DirIterator_ because other modules
// instantiate this.
struct DirIterator
{
    // safety check - used to verify correct calling of dir_filtered_next_ent
    const char* filter;
    // .. has filter been assigned? this flag is necessary because
    //    there are no "invalid" filter values we can use.
    uint filter_latched : 1;

    const FileProvider_VTbl* type;

    char opaque[DIR_ITERATOR_OPAQUE_SIZE];
};

class TFile;

// information about a directory entry filled by dir_next_ent.
struct DirEnt
{
    // we want to keep this as small as possible because
    // file_enum allocates one copy for each file in the directory.

    // store only required stat fields (in VC's order of decl)
    off_t  size;
    time_t mtime;

    // name (not including path!) of this entry.
    // valid until a subsequent dir_next_ent or dir_close call for the
    // current dir state.
    // rationale: we don't want to return a pointer to a copy because
    // users would have to free it (won't happen).
    const char* name;

    const TFile* tf;
};

// return [bool] indicating whether the given DirEnt* (filled by
// dir_next_ent) represents a directory.
#define DIRENT_IS_DIR(p_ent) ((p_ent)->size == -1)

// prepare to iterate (once) over entries in the given directory.
// if INFO::OK is returned, <d> is ready for subsequent dir_next_ent calls and
// must be freed via dir_close.
extern LibError dir_open(const char* P_path, DirIterator* d);

// return ERR::DIR_END if all entries have already been returned once,
// another negative error code, or INFO::OK on success, in which case <ent>
// describes the next (order is unspecified) directory entry.
extern LibError dir_next_ent(DirIterator* d, DirEnt* ent);

// indicate the directory iterator is no longer needed; all resources it
// held are freed.
extern LibError dir_close(DirIterator* d);


extern bool dir_exists(const char* P_path);
extern LibError dir_create(const char* P_path);
extern LibError dir_delete(const char* P_path);


#ifdef __cplusplus

typedef std::vector<DirEnt> DirEnts;
typedef DirEnts::iterator DirEntIt;
typedef DirEnts::const_iterator DirEntCIt;

// enumerate all directory entries in <P_path>; add to container and
// then sort it by filename.
extern LibError file_get_sorted_dirents(const char* P_path, DirEnts& dirents);

#endif  // #ifdef __cplusplus


// called by file_enum for each entry in the directory.
// name doesn't include path!
// return INFO::CB_CONTINUE to continue calling; anything else will cause
// file_enum to abort and immediately return that value.
typedef LibError (*FileCB)(const char* name, const struct stat* s, uintptr_t memento, const uintptr_t user);

// call <cb> for each file and subdirectory in <dir> (alphabetical order),
// passing the entry name (not full path!), stat info, and <user>.
//
// first builds a list of entries (sorted) and remembers if an error occurred.
// if <cb> returns non-zero, abort immediately and return that; otherwise,
// return first error encountered while listing files, or 0 on success.
extern LibError file_enum(const char* dir, FileCB cb, uintptr_t user);

// chosen for semi-nice 48 byte total struct File size.
// each implementation checks if this is enough.
const size_t FILE_OPAQUE_SIZE = 52;

// represents an open file of any type (OS, archive, VFS).
// contains common fields and opaque storage for type-specific fields.
//
// this cannot merely be added in a separate VFS layer: it would want to
// share some common fields, which either requires this approach
// (one publically visible struct with space for private storage), or
// a common struct layout / embedding a FileCommon struct at
// the beginning. the latter is a bit messy since fields must be accessed
// as e.g. af->fc.flags. one shared struct also makes for a common
// interface.
struct File
{
    uint flags;
    off_t size;

    // copy of the filename that is uniquely identified by its address.
    // used as key for file cache.
    // NOTE: not set by file_open! (because the path passed there is
    // a native path; it has no use within VFS and would only
    // unnecessarily clutter the filename storage)
    const char* atom_fn;

    // can be 0 if not currently in use; otherwise, points to
    // the file provider's vtbl.
    const FileProvider_VTbl* type;

    // storage for the provider-specific fields.
    // the implementations cast this to their e.g. PosixFile struct.
    //
    // note: when doing so, there's no need to verify type - if
    // vfs_io dispatches to afile_read, then the File.type must obviously
    // have been "archive".
    // if users call the e.g. archive.h methods directly, we assume they
    // know what they're doing and don't check that.
    u8 opaque[FILE_OPAQUE_SIZE];
};

// note: these are all set during file_open and cannot be changed thereafter.
enum FileFlags
{
    // IO:
    // ------------------------------------------------------------------------

    // write-only access; otherwise, read only.
    //
    // unless FILE_NO_AIO is set, data that is to be written must be
    // aligned and padded to a multiple of file_sector_size bytes;
    // this requirement avoids the need for align buffers.
    //
    // note: only allowing either reads or writes simplifies file cache
    // coherency (need only invalidate when closing a FILE_WRITE file).
    FILE_WRITE        = 0x01,

    // translate newlines: convert from/to native representation when
    // reading/writing. this is useful if files we create need to be
    // edited externally - e.g. Notepad requires \r\n.
    // caveats:
    // - FILE_NO_AIO must be set; translation is done by OS read()/write().
    // - not supported by POSIX, so this currently only has meaning on Win32.
    FILE_TEXT         = 0x02,

    // skip the aio path and use the OS-provided synchronous blocking
    // read()/write() calls. this avoids the need for buffer alignment
    // set out below, so it's useful for writing small text files.
    FILE_NO_AIO       = 0x04,

    // caching:
    // ------------------------------------------------------------------------

    // do not add the (entire) contents of this file to the cache.
    // this flag should be specified when the data is cached at a higher
    // level (e.g. OpenGL textures) to avoid wasting previous cache space.
    FILE_CACHED_AT_HIGHER_LEVEL = 0x10,

    // enable caching individual blocks read from a file. the block cache
    // is small, organized as LRU and incurs some copying overhead, so it
    // should only be enabled when needed. this is the case for archives,
    // where the cache absorbs overhead of block-aligning all IOs.
    FILE_CACHE_BLOCK  = 0x20,

    // notify us that the file buffer returned by file_io will not be
    // freed immediately (i.e. before the next allocation).
    // allocation policy may differ and a warning is suppressed.
    FILE_LONG_LIVED   = 0x40,

    // misc:
    // ------------------------------------------------------------------------

    // instruct file_open not to set FileCommon.atom_fn.
    // this is a slight optimization used by VFS code: file_open
    // would store the portable name, which is only used when calling
    // the OS's open(); this would unnecessarily waste atom_fn memory.
    //
    // note: other file.cpp functions require atom_fn to be set,
    // so this behavior is only triggered via flag (caller is
    // promising they will set atom_fn).
    FILE_DONT_SET_FN  = 0x80,

    // (only relevant for VFS) file will be written into the
    // appropriate subdirectory of the mount point established by
    // vfs_set_write_target. see documentation there.
    FILE_WRITE_TO_TARGET  = FILE_WRITE|0x100,

    // sum of all flags above. used when validating flag parameters.
    FILE_FLAG_ALL     = 0x1FF
};


// get file information. output param is zeroed on error.
extern LibError file_stat(const char* path, struct stat*);

// does the given file exist? (implemented via file_stat)
extern bool file_exists(const char* fn);

// permanently delete the file. be very careful with this!
extern LibError file_delete(const char* fn);

// <tf> is ignored here.
// rationale: all file providers' open() routines should ideally take the
// same parameters. since afile_open requires archive Handle and
// memento, we need some way of passing them; TFile is sufficient
// (via vfs_tree accessor methods).
extern LibError file_open(const char* fn, uint flags, File* f);

// note: final file size is calculated and returned in f->size.
// see implementation for rationale.
extern LibError file_close(File* f);

extern LibError file_validate(const File* f);
#define CHECK_FILE(f) RETURN_ERR(file_validate(f))


// remove all blocks loaded from the file <fn>. used when reloading the file.
extern LibError file_cache_invalidate(const char* fn);


#include "file_io.h"


//
// memory mapping
//

// useful for files that are too large to be loaded into memory,
// or if only (non-sequential) portions of a file are needed at a time.


// map the entire file <f> into memory. if already currently mapped,
// return the previous mapping (reference-counted).
// output parameters are zeroed on failure.
//
// the mapping will be removed (if still open) when its file is closed.
// however, map/unmap calls should still be paired so that the mapping
// may be removed when no longer needed.
//
// rationale: reference counting is required for zip_map: several
// Zip "mappings" each reference one ZArchive's actual file mapping.
// implement it here so that we also get refcounting for normal files.
extern LibError file_map(File* f, void*& p, size_t& size);

// decrement the reference count for the mapping belonging to file <f>.
// fail if there are no references; remove the mapping if the count reaches 0.
//
// the mapping will be removed (if still open) when its file is closed.
// however, map/unmap calls should still be paired so that the mapping
// may be removed when no longer needed.
extern LibError file_unmap(File* f);


extern LibError file_shutdown();

#endif  // #ifndef INCLUDED_FILE