/**
 * =========================================================================
 * File        : file_io.h
 * Project     : 0 A.D.
 * Description : provide fast I/O via POSIX aio and splitting into blocks.
 * =========================================================================
 */

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

#ifndef INCLUDED_FILE_IO
#define INCLUDED_FILE_IO

struct FileProvider_VTbl;
struct File;


namespace ERR
{
    const LibError IO     = -110100;
    const LibError IO_EOF = -110101;
}


extern void file_io_init();
extern void file_io_shutdown();


//
// asynchronous IO
//

// this is a thin wrapper on top of the system AIO calls.
// IOs are carried out exactly as requested - there is no caching or
// alignment done here. rationale: see source.

// again chosen for nice alignment; each user checks if big enough.
const size_t FILE_IO_OPAQUE_SIZE = 28;

struct FileIo
{
    const FileProvider_VTbl* type;
    u8 opaque[FILE_IO_OPAQUE_SIZE];
};

// queue the IO; it begins after the previous ones (if any) complete.
//
// rationale: this interface is more convenient than implicitly advancing a
// file pointer because archive.cpp often accesses random offsets.
extern LibError file_io_issue(File* f, off_t ofs, size_t size, void* buf, FileIo* io);

// indicates if the given IO has completed.
// return value: 0 if pending, 1 if complete, < 0 on error.
extern int file_io_has_completed(FileIo* io);

// wait for the given IO to complete. passes back its buffer and size.
extern LibError file_io_wait(FileIo* io, void*& p, size_t& size);

// indicates the IO's buffer is no longer needed and frees that memory.
extern LibError file_io_discard(FileIo* io);

extern LibError file_io_validate(const FileIo* io);


//
// synchronous IO
//

extern size_t file_sector_size;

// called by file_io after a block IO has completed.
// *bytes_processed must be set; file_io will return the sum of these values.
// example: when reading compressed data and decompressing in the callback,
// indicate #bytes decompressed.
// return value: INFO::CB_CONTINUE to continue calling; anything else:
//   abort immediately and return that.
// note: in situations where the entire IO is not split into blocks
// (e.g. when reading from cache or not using AIO), this is still called but
// for the entire IO. we do not split into fake blocks because it is
// advantageous (e.g. for decompressors) to have all data at once, if available
// anyway.
typedef LibError (*FileIOCB)(uintptr_t ctx, const void* block, size_t size, size_t* bytes_processed);


typedef const u8* FileIOBuf;

FileIOBuf* const FILE_BUF_TEMP = (FileIOBuf*)1;
const FileIOBuf FILE_BUF_ALLOC = (FileIOBuf)2;


enum FileBufFlags
{
    // indicates the buffer will not be freed immediately
    // (i.e. before the next buffer alloc) as it normally should.
    // this flag serves to suppress a warning and better avoid fragmentation.
    // caller sets this when FILE_LONG_LIVED is specified.
    //
    // also used by file_cache_retrieve because it may have to
    // 'reactivate' the buffer (transfer from cache to extant list),
    // which requires knowing whether the buffer is long-lived or not.
    FB_LONG_LIVED    = 1,

    // statistics (e.g. # buffer allocs) should not be updated.
    // (useful for simulation, e.g. trace_entry_causes_io)
    FB_NO_STATS      = 2,

    // file_cache_retrieve should not update item credit.
    // (useful when just looking up buffer given atom_fn)
    FB_NO_ACCOUNTING = 4,

    // memory will be allocated from the heap, not the (limited) file cache.
    // this makes sense for write buffers that are never used again,
    // because we avoid having to displace some other cached items.
    FB_FROM_HEAP     = 8
};

// allocate a new buffer of <size> bytes (possibly more due to internal
// fragmentation). never returns 0.
// <atom_fn>: owner filename (buffer is intended to be used for data from
//   this file).
extern FileIOBuf file_buf_alloc(size_t size, const char* atom_fn, uint fb_flags = 0);

// mark <buf> as no longer needed. if its reference count drops to 0,
// it will be removed from the extant list. if it had been added to the
// cache, it remains there until evicted in favor of another buffer.
extern LibError file_buf_free(FileIOBuf buf, uint fb_flags = 0);


// transfer <size> bytes, starting at <ofs>, to/from the given file.
// (read or write access was chosen at file-open time).
//
// if non-NULL, <cb> is called for each block transferred, passing <ctx>.
// it returns how much data was actually transferred, or a negative error
// code (in which case we abort the transfer and return that value).
// the callback mechanism is useful for user progress notification or
// processing data while waiting for the next I/O to complete
// (quasi-parallel, without the complexity of threads).
//
// return number of bytes transferred (see above), or a negative error code.
extern ssize_t file_io(File* f, off_t ofs, size_t size, FileIOBuf* pbuf, FileIOCB cb = 0, uintptr_t ctx = 0);

extern ssize_t file_read_from_cache(const char* atom_fn, off_t ofs, size_t size,
    FileIOBuf* pbuf, FileIOCB cb, uintptr_t ctx);


extern LibError file_io_get_buf(FileIOBuf* pbuf, size_t size,
    const char* atom_fn, uint file_flags, FileIOCB cb);

#endif  // #ifndef INCLUDED_FILE_IO