/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * Declarations for GenericFD class and sub-classes.
 */
#ifndef DISKIMG_GENERICFD_H
#define DISKIMG_GENERICFD_H

#include "Win32BlockIO.h"

namespace DiskImgLib {

#if 0
/*
 * Embedded file descriptor class, representing an open file on a disk image.
 *
 * Useful for opening disk images that are stored as files inside of other
 * disk images.  For stuff like UNIDOS images, which don't have a file
 * associated with them, we can either open them as raw blocks, or create
 * a "fake" file to access them.  The latter is more general, and will work
 * for sub-volumes of sub-volumes.
 */
class DISKIMG_API EmbeddedFD {
public:
    EmbeddedFD(void) {
        fpDiskFS = NULL;
        fpA2File = NULL;
    }
    virtual ~EmbeddedFD(void) {}

    typedef enum Fork { kForkData = 0, kForkRsrc = 1 } Fork;
    // bit-flag values for Open call's "access" parameter
    enum {
        kAccessNone             = 0,    // somewhat useless
        kAccessRead             = 0x01, // O_RDONLY
        kAccessWrite            = 0x02, // O_WRONLY
        kAccessCreate           = 0x04, // O_CREAT
        kAccessMustNotExist     = 0x08, // O_EXCL, pointless w/o O_CREAT

        kAccessReadWrite        = (kAccessRead | kAccessWrite),
    };

    /*
     * Standard set of calls.
     */
    DIError Open(DiskFS* pDiskFS, const char* filename, Fork fork = kForkData,
        int access = kAccessRead, int fileCreatePerms = 0);
    DIError OpenBlocks(DiskFS* pDiskFS, long blockStart, long blockCount,
        int access = kAccessRead);
    DIError Read(void* buf, size_t length);
    DIError Write(const void* buf, size_t length);
    DIError Seek(di_off_t offset, DIWhence whence);
    DIError Close(void);

private:
    // prevent bitwise copying behavior
    EmbeddedFD& operator=(const EmbeddedFD&);
    EmbeddedFD(const EmbeddedFD&);

    DiskFS*     fpDiskFS;
    A2File*     fpA2File;
};
#endif


/*
 * Generic file source base class.  Allows us to treat files on disk, memory
 * buffers, and files embedded inside disk images equally.
 *
 * The file represented by the class is available in its entirety; skipping
 * past "wrapper headers" is expected to be done by the caller.
 *
 * The Read and Write calls take an optional parameter that allows the caller
 * to see how much data was actually read or written.  If the parameter is
 * not specified (or specified as NULL), then failure to return the exact
 * amount of data requested results an error.
 *
 * This is not meant to be the end-all of file wrapper classes; in
 * particular, it does not support file creation.
 *
 * Some libraries, such as NufxLib, require an actual filename to operate
 * (bad architecture?).  The GetPathName call will return the original
 * filename if one exists, or NULL if there isn't one.  (At which point the
 * caller has the option of creating a temp file, copying the data into
 * it, and cranking up NufxLib or zlib on that.)
 *
 * NOTE to self: see fsopen() to control sharing.
 *
 * NOTE: the Seek() implementations currently do not consistently allow or
 * disallow seeking past the current EOF of a file.  When writing a file this
 * can be very useful, so someday we should implement it for all classes.
 */
class GenericFD {
public:
    GenericFD(void) : fReadOnly(true) {}
    virtual ~GenericFD(void) {} /* = 0 */

    // All sub-classes must provide these, plus a type-specific Open call.
    virtual DIError Read(void* buf, size_t length,
        size_t* pActual = NULL) = 0;
    virtual DIError Write(const void* buf, size_t length,
        size_t* pActual = NULL) = 0;
    virtual DIError Seek(di_off_t offset, DIWhence whence) = 0;
    virtual di_off_t Tell(void) = 0;
    virtual DIError Truncate(void) = 0;
    virtual DIError Close(void) = 0;
    virtual const char* GetPathName(void) const = 0;

    // Flush-data call, only needed for physical devices
    virtual DIError Flush(void) { return kDIErrNone; }

    // Utility functions.
    virtual DIError Rewind(void) { return Seek(0, kSeekSet); }

    virtual bool GetReadOnly(void) const { return fReadOnly; }

    /*
    typedef enum {
        kGFDTypeUnknown = 0,
        kGFDTypeFile,
        kGFDTypeBuffer,
        kGFDTypeWinVolume,
        kGFDTypeGFD
    } GFDType;
    virtual GFDType GetGFDType(void) const = 0;
    */

    /*
     * Utility function to copy data from one GFD to another.  Both GFDs must
     * be seeked to their initial positions.  "length" bytes will be copied.
     */
    static DIError CopyFile(GenericFD* pDst, GenericFD* pSrc, di_off_t length,
            uint32_t* pCRC = NULL);

protected:
    GenericFD& operator=(const GenericFD&);
    GenericFD(const GenericFD&);

    bool        fReadOnly;      // set when file is opened
};

class GFDFile : public GenericFD {
public:
#ifdef HAVE_FSEEKO
    GFDFile(void) : fPathName(NULL), fFp(NULL) {}
#else
    GFDFile(void) : fPathName(NULL), fFd(-1) {}
#endif
    virtual ~GFDFile(void) { Close(); delete[] fPathName; }

    virtual DIError Open(const char* filename, bool readOnly);
    virtual DIError Read(void* buf, size_t length,
        size_t* pActual = NULL);
    virtual DIError Write(const void* buf, size_t length,
        size_t* pActual = NULL);
    virtual DIError Seek(di_off_t offset, DIWhence whence);
    virtual di_off_t Tell(void);
    virtual DIError Truncate(void);
    virtual DIError Close(void);
    virtual const char* GetPathName(void) const { return fPathName; }

private:
    char*       fPathName;

#ifdef HAVE_FSEEKO
    FILE*       fFp;
#else
    int         fFd;
#endif
};

#ifdef _WIN32
class GFDWinVolume : public GenericFD {
public:
    GFDWinVolume(void) :
        fPathName(NULL),
        fCurrentOffset(0),
        fVolumeEOF(-1),
        fBlockSize(0)
    {}
    virtual ~GFDWinVolume(void) { delete[] fPathName; }

    virtual DIError Open(const char* deviceName, bool readOnly);
    virtual DIError Read(void* buf, size_t length,
        size_t* pActual = NULL);
    virtual DIError Write(const void* buf, size_t length,
        size_t* pActual = NULL);
    virtual DIError Seek(di_off_t offset, DIWhence whence);
    virtual di_off_t Tell(void);
    virtual DIError Truncate(void) { return kDIErrNotSupported; }
    virtual DIError Close(void);
    virtual const char* GetPathName(void) const { return fPathName; }

    virtual DIError Flush(void) { return fVolAccess.FlushCache(false); }

private:
    char*       fPathName;      // for display only
    Win32VolumeAccess   fVolAccess;
    di_off_t    fCurrentOffset;
    di_off_t    fVolumeEOF;
    int         fBlockSize;     // usually 512
};
#endif

class GFDBuffer : public GenericFD {
public:
    GFDBuffer(void) :
        fBuffer(NULL),
        fLength(0),
        fAllocLength(0),
        fDoDelete(false),
        fDoExpand(false),
        fCurrentOffset(0)
    {}
    virtual ~GFDBuffer(void) { Close(); }

    // If "doDelete" is set, the buffer will be freed with delete[] when
    // Close is called.  This should ONLY be used for storage allocated
    // by the DiskImg library, as under Windows it can cause problems
    // because DLLs can have their own heap.
    //
    // "doExpand" will cause writing past the end of the buffer to
    // reallocate the buffer.  Again, for internally-allocated storage
    // only.  We expect the initial size to be close to accurate, so we
    // don't aggressively expand the buffer.
    virtual DIError Open(void* buffer, di_off_t length, bool doDelete,
        bool doExpand, bool readOnly);
    virtual DIError Read(void* buf, size_t length,
        size_t* pActual = NULL);
    virtual DIError Write(const void* buf, size_t length,
        size_t* pActual = NULL);
    virtual DIError Seek(di_off_t offset, DIWhence whence);
    virtual di_off_t Tell(void);
    virtual DIError Truncate(void) {
        fLength = (long) Tell();
        return kDIErrNone;
    }
    virtual DIError Close(void);
    virtual const char* GetPathName(void) const { return NULL; }

    // Back door; try not to use this.
    void* GetBuffer(void) const { return fBuffer; }

private:
    enum { kMaxReasonableSize = 256 * 1024 * 1024 };
    void*       fBuffer;
    long        fLength;        // these sit in memory, so there's no
    long        fAllocLength;   //  value in using di_off_t here
    bool        fDoDelete;
    bool        fDoExpand;
    di_off_t    fCurrentOffset; // actually limited to (long)
};

#if 0
class GFDEmbedded : public GenericFD {
public:
    GFDEmbedded(void) : fEFD(NULL) {}
    virtual ~GFDEmbedded(void) { Close(); }

    virtual DIError Open(EmbeddedFD* pEFD, bool readOnly);
    virtual DIError Read(void* buf, size_t length,
        size_t* pActual = NULL);
    virtual DIError Write(const void* buf, size_t length,
        size_t* pActual = NULL);
    virtual DIError Seek(di_off_t offset, DIWhence whence);
    virtual di_off_t Tell(void);
    virtual DIError Close(void);
    virtual const char* GetPathName(void) const { return NULL; }

private:
    EmbeddedFD* fEFD;
};
#endif

/* pass all requests straight through to another GFD (with offset bias) */
class GFDGFD : public GenericFD {
public:
    GFDGFD(void) : fpGFD(NULL), fOffset(0) {}
    virtual ~GFDGFD(void) { Close(); }

    virtual DIError Open(GenericFD* pGFD, di_off_t offset, bool readOnly) {
        if (pGFD == NULL)
            return kDIErrInvalidArg;
        if (!readOnly && pGFD->GetReadOnly())
            return kDIErrAccessDenied;          // can't convert to read-write
        fpGFD = pGFD;
        fOffset = offset;
        fReadOnly = readOnly;
        Seek(0, kSeekSet);
        return kDIErrNone;
    }
    virtual DIError Read(void* buf, size_t length,
        size_t* pActual = NULL)
    {
        return fpGFD->Read(buf, length, pActual);
    }
    virtual DIError Write(const void* buf, size_t length,
        size_t* pActual = NULL)
    {
        return fpGFD->Write(buf, length, pActual);
    }
    virtual DIError Seek(di_off_t offset, DIWhence whence) {
        return fpGFD->Seek(offset + fOffset, whence);
    }
    virtual di_off_t Tell(void) {
        return fpGFD->Tell() -fOffset;
    }
    virtual DIError Truncate(void) {
        return fpGFD->Truncate();
    }
    virtual DIError Close(void) {
        /* do NOT close underlying descriptor */
        fpGFD = NULL;
        return kDIErrNone;
    }
    virtual const char* GetPathName(void) const { return fpGFD->GetPathName(); }

private:
    GenericFD*  fpGFD;
    di_off_t    fOffset;
};

};  // namespace DiskImgLib

#endif /*__GENERIC_FD__*/
