/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * Binary II support.
 */
#ifndef APP_BNYARCHIVE_H
#define APP_BNYARCHIVE_H

#include "GenericArchive.h"


class BnyArchive;

/*
 * One file in a BNY archive.
 */
class BnyEntry : public GenericEntry {
public:
    BnyEntry(BnyArchive* pArchive) :
        fpArchive(pArchive), fIsSqueezed(false), fOffset(-1)
        {}
    virtual ~BnyEntry(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;

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

    virtual bool GetFeatureFlag(Feature feature) const override {
        if (feature == kFeaturePascalTypes || feature == kFeatureDOSTypes ||
            feature == kFeatureHasSimpleAccess)
            return false;
        else
            return true;
    }

    /*
     * 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; }

    enum {
        kBNYBlockSize       = 128,
    };

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;
    //NuError BNYUnSqueeze(ExpandBuffer* outExp) const;

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


/*
 * BNY archive definition.
 */
class BnyArchive : public GenericArchive {
public:
    BnyArchive(void) : fIsReadOnly(false), fFp(NULL)
        {}
    virtual ~BnyArchive(void) { (void) Close(); }

    // One-time initialization; returns an error string.
    static CString AppInit(void);

    virtual OpenResult Open(const WCHAR* filename, bool readOnly,
        CString* pErrMsg) override;
    virtual CString New(const WCHAR* filename, const void* options) override;
    virtual CString Flush(void) override { return ""; }
    virtual CString Reload(void) override;
    virtual bool IsReadOnly(void) const override { return fIsReadOnly; };
    virtual bool IsModified(void) const override { return false; }
    virtual CString GetDescription() const override { return L"Binary II"; }
    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 "!"; }
    virtual CString TestPathName(const GenericEntry* pGenericEntry,
        const CString& basePath, const CString& newName,
        char newFssep) const override
        { ASSERT(false); return "!"; }
    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 BnyEntry;

private:
    virtual CString Close(void) {
        if (fFp != NULL) {
            fclose(fFp);
            fFp = NULL;
        }
        return "";
    }
    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 "!"; }
    virtual void XferAbort(CWnd* pMsgWnd) override
        { ASSERT(false); }
    virtual void XferFinish(CWnd* pMsgWnd) override
        { ASSERT(false); }

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

    enum {
        kBNYBlockSize       = BnyEntry::kBNYBlockSize,
        kBNYMaxFileName     = 64,
        kBNYMaxNativeName   = 48,
        kBNYFlagCompressed  = (1<<7),
        kBNYFlagEncrypted   = (1<<6),
        kBNYFlagSparse      = (1),
    };

    /*
     * An entry in a Binary II 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 it into GenericEntry
     * fields in a BnyEntry.
     */
//    struct BnyFileEntry;            // VC++6 needs these to access private enums
//    friend struct BnyFileEntry;     //   in this class
    typedef struct BnyFileEntry {
        uint16_t        access;
        uint16_t        fileType;
        uint32_t        auxType;
        uint8_t         storageType;
        uint32_t        fileSize;           /* in 512-byte blocks */
        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 */
        uint32_t        eof;
        uint32_t        realEOF;            /* eof is bogus for directories */
        char            fileName[kBNYMaxFileName+1];
        char            nativeName[kBNYMaxNativeName+1];
        uint32_t        diskSpace;          /* in 512-byte blocks */
        uint8_t         osType;             /* not exactly same as NuFileSysID */
        uint16_t        nativeFileType;
        uint8_t         phantomFlag;
        uint8_t         dataFlags;          /* advisory flags */
        uint8_t         version;
        uint8_t         filesToFollow;      /* #of files after this one */

        uint8_t         blockBuf[kBNYBlockSize];
    } BnyFileEntry;

    int LoadContents(void);

    /*
     * Given a BnyFileEntry structure, add an appropriate entry to the list.
     *
     * Note this can mangle pEntry (notably the filename).
     */
    NuError LoadContentsCallback(BnyFileEntry* pEntry);

    /*
     * Test for the magic number on a file in SQueezed format.
     */
    bool IsSqueezed(uint8_t one, uint8_t two);

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

    /*
     * Wrapper for fread().  Note the arguments resemble read(2) rather
     * than fread(3S).
     */
    NuError BNYRead(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 BNYSeek(long offset);

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

    /*
     * Decode a Binary II header.
     */
    NuError BNYDecodeHeader(BnyFileEntry* pEntry);

    /*
     * Iterate through a Binary II archive, loading the data.
     */
    NuError BNYIterate(void);

    FILE*       fFp;
    bool        fIsReadOnly;
};

#endif /*APP_BNYARCHIVE_H*/
