/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * AppleLink Compression Utility archive support (read-only).
 */
#ifndef APP_ACUARCHIVE_H
#define APP_ACUARCHIVE_H

#include "GenericArchive.h"


class AcuArchive;

/*
 * One file in an ACU archive.
 */
class AcuEntry : public GenericEntry {
public:
    AcuEntry(AcuArchive* pArchive) :
        fpArchive(pArchive), fIsSqueezed(false), fOffset(-1)
        {}
    virtual ~AcuEntry(void) {}

    virtual int ExtractThreadToBuffer(int which, char** ppText, long* pLength,
        CString* pErrMsg) const override;
    virtual int ExtractThreadToFile(int which, FILE* outfp, ConvertEOL conv,
        ConvertHighASCII convHA, CString* pErrMsg) const override;

    // doesn't matter
    virtual long GetSelectionSerial(void) const override { return -1; }

    virtual bool GetFeatureFlag(Feature feature) const override {
        if (feature == kFeatureHasFullAccess ||
            feature == kFeatureCanChangeType ||
            feature == kFeatureHasInvisibleFlag)
        {
            return true;
        } else {
            return false;
        }
    }

    /*
     * Test this entry by extracting it.
     *
     * If the file isn't compressed, just make sure the file is big enough.  If
     * it's squeezed, invoke the un-squeeze function with a "NULL" buffer pointer.
     */
    NuError TestEntry(CWnd* pMsgWnd);

    bool GetSqueezed(void) const { return fIsSqueezed; }
    void SetSqueezed(bool val) { fIsSqueezed = val; }
    long GetOffset(void) const { return fOffset; }
    void SetOffset(long offset) { fOffset = offset; }

private:
    /*
     * Copy data from the seeked archive to outfp, possibly converting EOL along
     * the way.
     */
    NuError CopyData(FILE* outfp, ConvertEOL conv, ConvertHighASCII convHA,
        CString* pMsg) const;

    AcuArchive* fpArchive;      // holds FILE* for archive
    bool        fIsSqueezed;
    long        fOffset;
};


/*
 * ACU archive definition.
 */
class AcuArchive : public GenericArchive {
public:
    AcuArchive(void) : fFp(NULL) {}
    virtual ~AcuArchive(void) { (void) Close(); }

    /*
     * Perform one-time initialization.  There really isn't any for us.
     *
     * Returns an error string on failure.
     */
    static CString AppInit(void);

    /*
     * Open an ACU archive.
     *
     * Returns an error string on failure, or "" on success.
     */
    virtual OpenResult Open(const WCHAR* filename, bool readOnly,
        CString* pErrMsg) override;

    /*
     * Finish instantiating an AcuArchive object by creating a new archive.
     *
     * This isn't implemented, and will always return an error.
     */
    virtual CString New(const WCHAR* filename, const void* options) override;

    virtual CString Flush(void) override { return L""; }

    virtual CString Reload(void) override;
    virtual bool IsReadOnly(void) const override { return true; };
    virtual bool IsModified(void) const override { return false; }
    virtual CString GetDescription() const override { return L"AppleLink ACU"; }
    virtual bool BulkAdd(ActionProgressDialog* pActionProgress,
        const AddFilesDialog* pAddOpts) override
        { ASSERT(false); return false; }
    virtual bool AddDisk(ActionProgressDialog* pActionProgress,
        const AddFilesDialog* pAddOpts) override
        { ASSERT(false); return false; }
    virtual bool CreateSubdir(CWnd* pMsgWnd, GenericEntry* pParentEntry,
        const WCHAR* newName) override
        { ASSERT(false); return false; }
    virtual bool TestSelection(CWnd* pMsgWnd, SelectionSet* pSelSet) override;
    virtual bool DeleteSelection(CWnd* pMsgWnd, SelectionSet* pSelSet) override
        { ASSERT(false); return false; }
    virtual bool RenameSelection(CWnd* pMsgWnd, SelectionSet* pSelSet) override
        { ASSERT(false); return false; }
    virtual bool RenameVolume(CWnd* pMsgWnd, DiskFS* pDiskFS,
        const WCHAR* newName) override
        { ASSERT(false); return false; }
    virtual CString TestVolumeName(const DiskFS* pDiskFS,
        const WCHAR* newName) const override
        { ASSERT(false); return L"!"; }
    virtual CString TestPathName(const GenericEntry* pGenericEntry,
        const CString& basePath, const CString& newName, char newFssep) const override
        { ASSERT(false); return L"!"; }
    virtual bool RecompressSelection(CWnd* pMsgWnd, SelectionSet* pSelSet,
        const RecompressOptionsDialog* pRecompOpts) override
        { ASSERT(false); return false; }
    virtual XferStatus XferSelection(CWnd* pMsgWnd, SelectionSet* pSelSet,
        ActionProgressDialog* pActionProgress,
        const XferFileOptions* pXferOpts) override
        { ASSERT(false); return kXferFailed; }
    virtual bool GetComment(CWnd* pMsgWnd, const GenericEntry* pEntry,
        CString* pStr) override
        { ASSERT(false); return false; }
    virtual bool SetComment(CWnd* pMsgWnd, GenericEntry* pEntry,
        const CString& str) override
        { ASSERT(false); return false; }
    virtual bool DeleteComment(CWnd* pMsgWnd, GenericEntry* pEntry) override
        { ASSERT(false); return false; }
    virtual bool SetProps(CWnd* pMsgWnd, GenericEntry* pEntry,
        const FileProps* pProps) override
        { ASSERT(false); return false; }
    virtual void PreferencesChanged(void) override {}
    virtual long GetCapability(Capability cap) override;

    friend class AcuEntry;

private:
    virtual CString Close(void) {
        if (fFp != NULL) {
            fclose(fFp);
            fFp = NULL;
        }
        return L"";
    }
    virtual void XferPrepare(const XferFileOptions* pXferOpts) override
        { ASSERT(false); }
    virtual CString XferFile(LocalFileDetails* pDetails, uint8_t** pDataBuf,
        long dataLen, uint8_t** pRsrcBuf, long rsrcLen) override
        { ASSERT(false); return L"!"; }
    virtual void XferAbort(CWnd* pMsgWnd) override
        { ASSERT(false); }
    virtual void XferFinish(CWnd* pMsgWnd) override
        { ASSERT(false); }

    virtual ArchiveKind GetArchiveKind(void) override { return kArchiveACU; }
    virtual NuError DoAddFile(const AddFilesDialog* pAddOpts,
        LocalFileDetails* pDetails) override
        { ASSERT(false); return kNuErrGeneric; }

    enum {
        kAcuMaxFileName     = 256,      // nice big number

        kAcuMasterHeaderLen = 20,
        kAcuEntryHeaderLen  = 54,
    };

    /*
     * The header at the front of an ACU archive.
     */
    struct AcuMasterHeader {
        uint16_t    fileCount;
        uint16_t    unknown1;       // 0x01 00 -- might be "version 1?"
        uint8_t     fZink[6];       // "fZink", low ASCII
        uint8_t     unknown2[11];   // 0x01 36 00 00 00 00 00 00 00 00 dd
    };

    /*
     * An entry in an ACU archive.  Each archive is essentially a stream
     * of files; only the "filesToFollow" value gives any indication that
     * something else follows this entry.
     *
     * We read this from the archive and then unpack the interesting parts
     * into GenericEntry fields in an AcuEntry.
     */
    //struct AcuFileEntry;
    //friend struct AcuFileEntry;
    struct AcuFileEntry {
        uint8_t     compressionType;
        uint16_t    dataChecksum;       // ??
        uint16_t    blockCount;         // total blocks req'd to hold file
        uint32_t    dataStorageLen;     // length of data within archive
        uint16_t    access;
        uint16_t    fileType;
        uint32_t    auxType;
        uint8_t     storageType;
        uint32_t    dataEof;
        uint16_t    prodosModDate;
        uint16_t    prodosModTime;
        NuDateTime  modWhen;            // computed from previous two fields
        uint16_t    prodosCreateDate;
        uint16_t    prodosCreateTime;
        NuDateTime  createWhen;         // computed from previous two fields
        uint16_t    fileNameLen;
        uint16_t    headerChecksum;     // ??
        char        fileName[kAcuMaxFileName+1];    // ASCII

        // possibilities for mystery fields:
        // - OS type (note ProDOS is $00)
        // - forked file support
    };

    /* known compression types */
    enum CompressionType {
        kAcuCompNone = 0,
        kAcuCompSqueeze = 3,
    };

    /*
     * Load the contents of the archive.
     *
     * Returns 0 on success, < 0 if this is not an ACU archive, or > 0 if
     * this appears to be an ACU archive but it's damaged.
     */
    int LoadContents(void);

    /*
     * Read the archive header.  The archive file is left seeked to the point
     * at the end of the header.
     *
     * Returns 0 on success, -1 on failure.  Sets *pNumEntries to the number of
     * entries in the archive.
     */
    int ReadMasterHeader(int* pNumEntries);

    /*
     * Read and decode an AppleLink Compression Utility file entry header.
     * This leaves the file seeked to the point immediately past the filename.
     */
    NuError ReadFileHeader(AcuFileEntry* pEntry);

    /*
     * Dump the contents of an AcuFileEntry struct.
     */
    void DumpFileHeader(const AcuFileEntry* pEntry);

    /*
     * Given an AcuFileEntry structure, add an appropriate entry to the list.
     */
    int CreateEntry(const AcuFileEntry* pEntry);

    /*
     * Test if this entry is a directory.
     */
    bool IsDir(const AcuFileEntry* pEntry);

    /*
     * Wrapper for fread().  Note the arguments resemble read(2) rather
     * than fread(3S).
     */
    NuError AcuRead(void* buf, size_t nbyte);

    /*
     * Seek within an archive.  Because we need to handle streaming archives,
     * and don't need to special-case anything, we only allow relative
     * forward seeks.
     */
    NuError AcuSeek(long offset);

    /*
     * Convert from ProDOS compact date format to the expanded DateTime format.
     */
    void AcuConvertDateTime(uint16_t prodosDate,
        uint16_t prodosTime, NuDateTime* pWhen);

    FILE*       fFp;
    //bool        fIsReadOnly;
};

#endif /*APP_ACUARCHIVE_H*/
