/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * Class definition for DiskEdit dialog.
 */
#ifndef APP_DISKEDITDIALOG_H
#define APP_DISKEDITDIALOG_H

#include "../diskimg/DiskImg.h"
#include "../util/UtilLib.h"
#include "resource.h"

/*
 * An abstract base class to support "sector editing" and "block editing"
 * dialogs, which differ chiefly in how much data they present at a time.
 *
 * NOTE: override OnCancel to insert an "are you sure" message when the
 * block is dirty.
 *
 * NOTE to self: if the initial block/sector read fails, we can be left
 * with invalid stuff in the buffer.  Keep that in mind if editing is
 * enabled.
 */
class DiskEditDialog : public CDialog {
public:
    DiskEditDialog(UINT nIDTemplate, CWnd* pParentWnd = NULL) :
        CDialog(nIDTemplate, pParentWnd)
    {
        fReadOnly = true;
        fpDiskFS = NULL;
        fFileName = "";
        fPositionShift = 0;
        fFirstResize = true;
    }
    virtual ~DiskEditDialog() {}

    void Setup(DiskFS* pDiskFS, const WCHAR* fileName) {
        ASSERT(pDiskFS != NULL);
        ASSERT(fileName != NULL);
        fpDiskFS = pDiskFS;
        fFileName = fileName;
    }

    enum { kSectorSize=256, kBlockSize=512 };

    virtual int LoadData(void) = 0;

    virtual void DisplayData(void) = 0;

    /*
     * Convert a chunk of data into a hex dump, and stuff it into the edit control.
     */
    virtual void DisplayData(const uint8_t* buf, int size);

    /*
     * Display a track full of nibble data.
     */
    virtual void DisplayNibbleData(const uint8_t* srcBuf, int size);

    bool GetReadOnly(void) const { return fReadOnly; }
    void SetReadOnly(bool val) { fReadOnly = val; }
    int GetPositionShift(void) const { return fPositionShift; }
    void SetPositionShift(int val) { fPositionShift = val; }
    DiskFS* GetDiskFS(void) const { return fpDiskFS; }
    const WCHAR* GetFileName(void) const { return fFileName; }

protected:
    // return a low-ASCII character so we can read high-ASCII files
    inline char PrintableChar(unsigned char ch) {
        if (ch < 0x20)
            return '.';
        else if (ch < 0x80)
            return ch;
        else if (ch < 0xa0 || ch == 0xff)   // 0xff becomes 0x7f
            return '.';
        else
            return ch & 0x7f;
    }

    virtual BOOL OnInitDialog(void) override;

    // catch <return> key
    virtual BOOL PreTranslateMessage(MSG* pMsg) override;

    /*
     * Handle the "Done" button.  We don't use IDOK because we don't want
     * <return> to bail out of the dialog.
     */
    afx_msg virtual void OnDone(void);

     /*
      * Toggle the spin button / edit controls.
      */
    afx_msg virtual void OnHexMode(void);

    afx_msg virtual void OnDoRead(void) = 0;
    afx_msg virtual void OnDoWrite(void) = 0;
    afx_msg virtual void OnReadPrev(void) = 0;
    afx_msg virtual void OnReadNext(void) = 0;

    /*
     * Create a new instance of the disk edit dialog, for a sub-volume.
     */
    afx_msg virtual void OnSubVolume(void);

    afx_msg virtual void OnOpenFile(void) = 0;

    /*
     * Change the nibble parms.
     *
     * Assumes the parm list is linear and unbroken.
     */
    afx_msg virtual void OnNibbleParms(void);

    afx_msg void OnHelp(void) {
        MyApp::HandleHelp(this, HELP_TOPIC_DISKEDIT);
    }
    afx_msg BOOL OnHelpInfo(HELPINFO* lpHelpInfo) {
        return MyApp::HandleHelpInfo(lpHelpInfo);
    }

    /*
     * Change the mode of a spin button.  The Windows control doesn't
     * immediately update with a hex display, so we do it manually.  (Our
     * replacement class does this correctly, but I'm leaving the code alone
     * for now.)
     */
    void SetSpinMode(int id, int base);

    /*
     * Read a value from a spin control.
     *
     * Returns 0 on success, -1 if the return value from the spin control was
     * invalid.  In the latter case, an error dialog will be displayed.
     */
    int ReadSpinner(int id, long* pVal);

    /*
     * Set the value of a spin control.
     */
    void SetSpinner(int id, long val);

    /*
     * Open a file in a disk image.
     *
     * Returns a pointer to the A2File and A2FileDescr structures on success, NULL
     * on failure.  The pointer placed in "ppOpenFile" must be freed by invoking
     * its Close function.
     */
    DIError OpenFile(const WCHAR* fileName, bool openRsrc, A2File** ppFile,
        A2FileDescr** ppOpenFile);

    DiskFS*     fpDiskFS;
    CString     fFileName;
    CString     fAlertMsg;
    bool        fReadOnly;
    int         fPositionShift;

private:
    /*
     * Initialize the nibble parm drop-list.
     */
    void InitNibbleParmList(void);

    /*
     * Replace a spin button with our improved version.
     */
    int ReplaceSpinCtrl(MySpinCtrl* pNewSpin, int idSpin, int idEdit);

    MySpinCtrl  fTrackSpinner;
    MySpinCtrl  fSectorSpinner;
    bool        fFirstResize;

    //afx_msg void OnPaint();
    DECLARE_MESSAGE_MAP()
};


/*
 * The "sector edit" dialog, which displays 256 bytes at a time, and
 * accesses a disk by track/sector.
 */
class SectorEditDialog : public DiskEditDialog {
public:
    SectorEditDialog(CWnd* pParentWnd = NULL) :
        DiskEditDialog(IDD_DISKEDIT, pParentWnd),
        fTrack(0),
        fSector(0),
        fSectorData()
    { }
    virtual ~SectorEditDialog() {}

    virtual int LoadData(void) override;    // load the current track/sector
    virtual void DisplayData(void) override {
        DiskEditDialog::DisplayData(fSectorData, kSectorSize);
    }

    //void SetTrack(int val) { fTrack = val; }
    //void SetSector(int val) { fSector = val; }

protected:
    virtual BOOL OnInitDialog(void) override;

    afx_msg virtual void OnDoRead(void);
    afx_msg virtual void OnDoWrite(void);

    /*
     * Back up to the previous track/sector.
     */
    afx_msg virtual void OnReadPrev(void);

    /*
     * Advance to the next track/sector.
     */
    afx_msg virtual void OnReadNext(void);

    /*
     * Open a file on the disk image.  If successful, open a new edit dialog
     * that's in "file follow" mode.
     */
    afx_msg virtual void OnOpenFile(void);

    long        fTrack;
    long        fSector;
    uint8_t     fSectorData[kSectorSize];
};

/*
 * Edit a file sector-by-sector.
 */
class SectorFileEditDialog : public SectorEditDialog {
public:
    SectorFileEditDialog(SectorEditDialog* pSectEdit, CWnd* pParentWnd = NULL):
        SectorEditDialog(pParentWnd),
        fOpenRsrcFork(false),
        fpFile(NULL),
        fpOpenFile(NULL),
        fSectorIdx(0),
        fLength(0)
    {
        DiskEditDialog::Setup(pSectEdit->GetDiskFS(),
            pSectEdit->GetFileName());
    }
    virtual ~SectorFileEditDialog() {}

    /* we do NOT own pOpenFile, and should not delete it */
    void SetupFile(const WCHAR* fileName, bool rsrcFork, A2File* pFile,
        A2FileDescr* pOpenFile)
    {
        fOpenFileName = fileName;
        fOpenRsrcFork = rsrcFork;
        fpFile = pFile;
        fpOpenFile = pOpenFile;
        fLength = 0;
        if (fpOpenFile->Seek(0, DiskImgLib::kSeekEnd) == kDIErrNone)
            fLength = fpOpenFile->Tell();
    }

    virtual int LoadData(void);     // load data from the current offset

private:
    // overrides
    virtual BOOL OnInitDialog(void);

    afx_msg virtual void OnReadPrev(void);
    afx_msg virtual void OnReadNext(void);

    CString     fOpenFileName;
    bool        fOpenRsrcFork;
    A2File*     fpFile;
    A2FileDescr* fpOpenFile;
    //long      fOffset;
    long        fSectorIdx;
    di_off_t    fLength;
};


/*
 * The "block edit" dialog, which displays 512 bytes at a time, and
 * accesses a disk by linear block number.
 */
class BlockEditDialog : public DiskEditDialog {
public:
    BlockEditDialog(CWnd* pParentWnd = NULL) :
        DiskEditDialog(IDD_DISKEDIT, pParentWnd)
    {
        fBlock = 0;
    }
    virtual ~BlockEditDialog() {}

    virtual int LoadData(void) override;    // load the current block
    virtual void DisplayData(void) override {
        DiskEditDialog::DisplayData(fBlockData, kBlockSize);
    }

protected:
    virtual BOOL OnInitDialog(void) override;

    afx_msg virtual void OnDoRead(void);
    afx_msg virtual void OnDoWrite(void);

    /*
     * Back up to the previous track/sector, or (in follow-file mode) to the
     * previous sector in the file.
     */
    afx_msg virtual void OnReadPrev(void);

    /*
     * Same as OnReadPrev, but moving forward.
     */
    afx_msg virtual void OnReadNext(void);

    /*
     * Open a file on the disk image.  If successful, open a new edit dialog
     * that's in "file follow" mode.
     */
    afx_msg virtual void OnOpenFile(void);

    long        fBlock;
    uint8_t     fBlockData[kBlockSize];
};


/*
 * Edit a file block-by-block.
 */
class BlockFileEditDialog : public BlockEditDialog {
public:
    BlockFileEditDialog(BlockEditDialog* pBlockEdit, CWnd* pParentWnd = NULL) :
        BlockEditDialog(pParentWnd),
        fOpenRsrcFork(false),
        fpFile(NULL),
        fpOpenFile(NULL),
        fBlockIdx(0),
        fLength(0)
    {
        DiskEditDialog::Setup(pBlockEdit->GetDiskFS(),
            pBlockEdit->GetFileName());
        fBlockIdx = 0;
    }
    virtual ~BlockFileEditDialog() {}

    /* we do NOT own pOpenFile, and should not delete it */
    void SetupFile(const WCHAR* fileName, bool rsrcFork, A2File* pFile,
        A2FileDescr* pOpenFile)
    {
        fOpenFileName = fileName;
        fOpenRsrcFork = rsrcFork;
        fpFile = pFile;
        fpOpenFile = pOpenFile;
        fLength = 0;
        if (fpOpenFile->Seek(0, DiskImgLib::kSeekEnd) == kDIErrNone)
            fLength = fpOpenFile->Tell();
    }

    virtual int LoadData(void);     // load data from the current offset

private:
    // overrides
    virtual BOOL OnInitDialog(void);

    /*
     * Move to the previous Block in the file.
     */
    afx_msg virtual void OnReadPrev(void);

    /*
     * Move to the next Block in the file.
     */
    afx_msg virtual void OnReadNext(void);

    CString     fOpenFileName;
    bool        fOpenRsrcFork;
    A2File*     fpFile;
    A2FileDescr* fpOpenFile;
    //long      fOffset;
    long        fBlockIdx;
    di_off_t    fLength;
};

/*
 * The "sector edit" dialog, which displays 256 bytes at a time, and
 * accesses a disk by track/sector.
 */
class NibbleEditDialog : public DiskEditDialog {
public:
    NibbleEditDialog(CWnd* pParentWnd = NULL) :
        DiskEditDialog(IDD_DISKEDIT, pParentWnd),
        fTrack(0),
        fNibbleData(),
        fNibbleDataLen(0)
    { }
    virtual ~NibbleEditDialog() {}

    virtual int LoadData(void) override;    // load the current track/sector
    virtual void DisplayData(void) override {
        DiskEditDialog::DisplayNibbleData(fNibbleData, fNibbleDataLen);
    }

protected:
    /*
     * Rearrange the DiskEdit dialog (which defaults to SectorEdit mode) to
     * accommodate nibble editing.
     */
    virtual BOOL OnInitDialog(void) override;

    afx_msg virtual void OnDoRead(void);
    afx_msg virtual void OnDoWrite(void);
    afx_msg virtual void OnReadPrev(void);
    afx_msg virtual void OnReadNext(void);
    afx_msg virtual void OnOpenFile(void) { ASSERT(false); }
    afx_msg virtual void OnNibbleParms(void) { ASSERT(false); }

    long        fTrack;
    uint8_t     fNibbleData[DiskImgLib::kTrackAllocSize];
    long        fNibbleDataLen;
};

#endif /*APP_DISKEDITDIALOG_H*/
