/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * Apple II cassette I/O functions.
 */
#ifndef APP_CASSETTEDIALOG_H
#define APP_CASSETTEDIALOG_H

/*
 * The dialog box is primarily concerned with extracting the original data
 * from a WAV file recording of an Apple II cassette tape.
 */
class CassetteDialog : public CDialog {
public:
    CassetteDialog(CWnd* pParentWnd = NULL) :
        CDialog(IDD_IMPORTCASSETTE, pParentWnd), fDirty(false)
        {}
    virtual ~CassetteDialog(void) {}

    CString fFileName;      // file to open

    bool IsDirty(void) const { return fDirty; }

private:
    virtual BOOL OnInitDialog(void) override;
    //virtual void DoDataExchange(CDataExchange* pDX);
    //virtual void OnOK(void);
    
    //enum { WMU_DIALOG_READY = WM_USER+2 };

    /*
     * Something changed in the list.  Update the "OK" button.
     */
    afx_msg void OnListChange(NMHDR* pNotifyStruct, LRESULT* pResult);

    /*
     * The volume filter drop-down box has changed.
     */
    afx_msg void OnAlgorithmChange(void);

    /*
     * User pressed "import" button.  Add the selected item to the current
     * archive or disk image.
     */
    afx_msg void OnImport(void);

    afx_msg void OnListDblClick(NMHDR* pNotifyStruct, LRESULT* pResult);
    afx_msg void OnHelp(void) {
        MyApp::HandleHelp(this, HELP_TOPIC_IMPORT_CASSETTE);
    }

    /*
     * This holds converted data from the WAV file, plus some meta-data
     * like what type of file we think this is.
     */
    class CassetteData {
    public:
        CassetteData(void) : fFileType(0x00), fOutputBuf(NULL), fOutputLen(-1),
            fStartSample(-1), fEndSample(-1), fChecksum(0x00),
            fChecksumGood(false)
            {}
        virtual ~CassetteData(void) { delete[] fOutputBuf; }

        /*
         * Algorithm to use.  This must match up with the order of the items
         * in the dialog IDC_CASSETTE_ALG combo box.
         */
        typedef enum Algorithm {
            kAlgorithmMIN = -1,

            kAlgorithmZero = 0,
            kAlgorithmSharpPeak,
            kAlgorithmRoundPeak,
            kAlgorithmShallowPeak,

            kAlgorithmMAX
        } Algorithm;

        /*
         * Scan the WAV file, starting from the specified byte offset.
         *
         * Returns "true" if we found a file, "false" if not (indicating that the
         * end of the input has been reached).  Updates "*pStartOffset" to point
         * past the end of the data we've read.
         */
        bool Scan(SoundFile* pSoundFile, Algorithm alg, long* pSampleOffset);
        unsigned char* GetDataBuf(void) const { return fOutputBuf; }
        int GetDataLen(void) const { return fOutputLen; }
        int GetDataOffset(void) const { return fStartSample; }
        int GetDataEndOffset(void) const { return fEndSample; }
        unsigned char GetDataChecksum(void) const { return fChecksum; }
        bool GetDataChkGood(void) const { return fChecksumGood; }

        long GetFileType(void) const { return fFileType; }
        void SetFileType(long fileType) { fFileType = fileType; }

    private:
        typedef enum Phase {
            kPhaseUnknown = 0,
            kPhaseScanFor770Start,
            kPhaseScanning770,
            kPhaseScanForShort0,
            kPhaseShort0B,
            kPhaseReadData,
            kPhaseEndReached,
        //  kPhaseError,
        } Phase;
        typedef enum Mode {
            kModeUnknown = 0,
            kModeInitial0,
            kModeInitial1,

            kModeInTransition,
            kModeAtPeak,

            kModeRunning,
        } Mode;

        typedef struct ScanState {
            Algorithm algorithm;
            Phase   phase;
            Mode    mode;
            bool    positive;           // rising or at +peak if true

            long    lastZeroIndex;      // in samples
            long    lastPeakStartIndex; // in samples
            float   lastPeakStartValue;

            float   prevSample;

            float   halfCycleWidth;     // in usec
            long    num770;             // #of consecutive 770Hz cycles
            long    dataStart;
            long    dataEnd;

            /* constants */
            float   usecPerSample;
        } ScanState;

        /*
         * Convert a block of samples from PCM to float.
         *
         * Only the first (left) channel is converted in multi-channel formats.
         */
        void ConvertSamplesToReal(const WAVEFORMATEX* pFormat,
            const unsigned char* buf, long chunkLen, float* sampleBuf);

        /*
         * Process one audio sample.  Updates "pScanState" appropriately.
         *
         * If we think we found a bit, this returns "true" with 0 or 1 in "*pBitVal".
         */
        bool ProcessSample(float sample, long sampleIndex,
            ScanState* pScanState, int* pBitVal);

        /*
         * Process the data by measuring the distance between zero crossings.
         *
         * This is very similar to the way the Apple II does it, though
         * we have to scan for the 770Hz lead-in instead of simply assuming the
         * the user has queued up the tape.
         *
         * To offset the effects of DC bias, we examine full cycles instead of
         * half cycles.
         */
        bool ProcessSampleZero(float sample, long sampleIndex,
            ScanState* pScanState, int* pBitVal);

        /*
         * Process the data by finding and measuring the distance between peaks.
         */
        bool ProcessSamplePeak(float sample, long sampleIndex,
            ScanState* pScanState, int* pBitVal);

        /*
         * Given the width of a half-cycle, update "phase" and decide whether or not
         * it's time to emit a bit.
         *
         * Updates "halfCycleWidth" too, alternating between 0.0 and a value.
         *
         * The "sampleIndex" parameter is largely just for display.  We use it to
         * set the "start" and "end" pointers, but those are also ultimately just
         * for display to the user.
         */
        bool UpdatePhase(ScanState* pScanState, long sampleIndex,
            float halfCycleUsec, int* pBitVal);

        enum {
            kMaxFileLen = 65535+2+1+1,  // 64K + length + checksum + 1 slop
        };

        long            fFileType;      // 0x06, 0xfa, or 0xfc
        unsigned char*  fOutputBuf;
        int             fOutputLen;
        long            fStartSample;
        long            fEndSample;
        unsigned char   fChecksum;
        bool            fChecksumGood;
    };

    /*
     * Analyze the contents of a WAV file.
     *
     * Returns true if it found anything at all, false if not.
     */
    bool AnalyzeWAV(void);

    /*
     * Add an entry to the list.
     *
     * Layout: index format length checksum start-offset
     */
    void AddEntry(int idx, CListCtrl* pListCtrl, long* pFileType);

    enum {
        kMaxRecordings = 100,       // max A2 files per WAV file
    };

    /* array with one entry per file */
    CassetteData    fDataArray[kMaxRecordings];

    CassetteData::Algorithm fAlgorithm;
    bool    fDirty;

    DECLARE_MESSAGE_MAP()
};

#endif /*APP_CASSETTEDIALOG_H*/
