#pragma once

#include <WINDOWS.h>
#include <ceddk.h>
#include <drvmsg.h>

#define WAVE_POWER_NAME L"WAV1:"
#define WAVE_EVENT_NAME L"WAVE_MODE_CHANGE_FINISH"
#define WAVE_EVENT_TIME 3000
#define WAVE_POWER_STATE CEDEVICE_POWER_STATE

#define WAVE_LPMODE WAVE_USR1
#define WAVE_NMMODE WAVE_USR2
#define WAVE_CODEC  WAVE_USR3
#define WAVE_ULPMODE    WAVE_USR4

#define DMA_BUFFER0 0
#define DMA_BUFFER1 1
#define DMA_BUFFER_COUNT 2
#define SHORT_DMA_BUF_SIZE (4096)
#define SHORT_DMA_DBUF_SIZE (SHORT_DMA_BUF_SIZE<<1)
#define SHORT_DMA_BUF_WSIZE (SHORT_DMA_BUF_SIZE>>2)
#define SHORT_DMA_DBUF_WSIZE (SHORT_DMA_BUF_SIZE>>1)
#define LONG_DMA_BUF_SIZE (81920)
//#define LONG_DMA_BUF_SIZE (40960)
//#define LONG_DMA_BUF_SIZE (10240)
#define LONG_DMA_DBUF_SIZE (LONG_DMA_BUF_SIZE<<1)
#define LONG_DMA_BUF_WSIZE (LONG_DMA_BUF_SIZE>>2)
#define LONG_DMA_DBUF_WSIZE (LONG_DMA_BUF_SIZE>>1)
#define IS_WORD_ALIGNMENT(value) (((DWORD)value % 4) ? FALSE : TRUE)
#define IS_8WORD_ALIGNMENT(value) (((DWORD)value % 32) ? FALSE : TRUE)

typedef enum
{
    OUT_ANONYMOUSE_MODE,
    OUT_NORMAL_MODE,
    OUT_LOWPOWER_MODE
} OUT_MODE;

typedef enum
{
    OUT_NM_D0 = 0,
    OUT_NM_D0_REQ, //4 REQ : REQUEST SELF POWER    
    OUT_NM_D2 = 20,
    OUT_NM_D2_REQ,
    OUT_NM_D4 = 40,
    OUT_LP_D0 = 100,
    OUT_LP_D0_REQ, 
    OUT_LP_D0_FRC, //4FRC : FORCE POWER
    OUT_LP_D2 = 120,
    OUT_LP_D2_REQ,
    OUT_LP_D2_FRC,
    OUT_LP_D2_DPL, 
    OUT_LP_D4 = 140,
    OUT_LP_D4_REQ,
    OUT_LP_D4_FRC
} OUT_STATE;

typedef enum
{
    IN_ANONYMOUSE_MODE,
    IN_NORMAL_MODE,
    IN_LOWPOWER_MODE
} IN_MODE;

#define BASE_REG_PA_ASS_CLKCON      0xEEE10000  
#define BASE_REG_PA_ASS_COMMBOX1    0xEEE20000
#define BASE_REG_PA_ASS_COMMBOX2    0xEEE20100
#define BASE_REG_PA_ASS_COMMBOX3    0xEEE20204
#define INTERRUPT_THREAD_PRIORITY_DEFAULT    (150)

//----- Used to track DMA controllers status -----
#define DMA_CLEAR           0x00000000
#define DMA_BIU             0x00000001 //BIU(1) : DMA_BUFFER1 is in use, BIU(0) : DMA_BUFFER0 is in use
#define DMA_DONE0           0x00000F00
#define DMA_DONE1           0x0000F000
#define DMA_DNEMK           0x0000FF00

typedef VOID (*PCB_MODEOP)(VOID);

typedef class ModeInterfaceLayer
{
protected:    
    BOOL                m_bDMARunning;
    DWORD               m_dwDMAStatus;
    DWORD               m_dwTransfered[DMA_BUFFER_COUNT];  

    //DEBUGGING FOR MEMORY TEST
    //PHYSICAL_ADDRESS    m_PhyXferBufAddr;
    //PBYTE               m_pVirXferBufAddr;

    DWORD               m_dwDMABufSize;
    PHYSICAL_ADDRESS    m_PhyDMABufAddr;
    PBYTE               m_pVirDMABufAddr;
    DWORD               m_dwDMABufPhyPage[DMA_BUFFER_COUNT];
    PBYTE               m_pDMABufVirPage[DMA_BUFFER_COUNT];

    DWORD               m_dwSysIntr;
    DWORD               m_dwThreadPriority;
    HANDLE              m_hXferEvent;
    HANDLE              m_hXferThread;

    BOOL                m_bSelfPowerNotify;
    WAVE_POWER_STATE    m_PowerState;

    CRITICAL_SECTION    m_CriticalSection;
    
public:
    ModeInterfaceLayer();
    ~ModeInterfaceLayer();

    CEDEVICE_POWER_STATE GetPowerState(VOID)
    {
        return m_PowerState;
    }
    
    BOOL GetRunningStatus(VOID)
    {
        return m_bDMARunning;
    }

    DWORD GetSysIntr(VOID)
    {
        return m_dwSysIntr;
    }

protected: //VIRTUAL
    virtual BOOL    MapRegister(VOID) = 0;
    virtual BOOL    UnmapRegister(VOID) = 0;
    virtual DWORD   FillBuffer(DWORD dwBufNum)= 0;
    virtual DWORD   TransferBuffer(VOID) = 0;
    
public: //VIRTUAL
    virtual BOOL    Init(LPWSTR lpActivePath) = 0;
    virtual BOOL    Deinit(VOID) = 0;
    virtual BOOL    SetPowerState(CEDEVICE_POWER_STATE PowerState) = 0;
    virtual BOOL    StartTransfer(VOID) = 0;
    virtual BOOL    ResumeTransfer(VOID) = 0;
    virtual BOOL    StopTransfer(BOOL bMediumOff) = 0;
    virtual VOID    InterruptThread(VOID) = 0;

protected: //IMPLEMENTED
    VOID Lock()
    {
        EnterCriticalSection(&m_CriticalSection);
    }

    VOID Unlock()
    {
        LeaveCriticalSection(&m_CriticalSection);
    }

    BOOL    InitTransfer(LPWSTR lpActivePath, DWORD dwIrq);
    BOOL    InitTransfer(LPWSTR lpActivePath, DWORD dwIrq, PDWORD pdwSysIntr, PHANDLE phXferEvent, PHANDLE phXferThread, LPTHREAD_START_ROUTINE CallInterruptThread);
    BOOL    DeinitTransfer(VOID);
    BOOL    DeinitTransfer(DWORD dwSysIntr, HANDLE hXferEvent, HANDLE hXferThread);
    BOOL    MapDMABuffers(PVOID pBufPhyAddr = NULL, DWORD dwBufSize = SHORT_DMA_BUF_SIZE);
    BOOL    UnmapDMABuffers();
    DWORD   GetInterruptThreadPriority(LPWSTR lpActivePath);
} *PModeInterfaceLayer;

class OutputDeviceContext;
class InModeInterfaceLayer;

typedef class OutModeInterfaceLayer : public ModeInterfaceLayer
{
private:
    PBYTE                   m_pSBCurrPtr;
    DWORD                   m_dwFillSize;
    BYTE                    m_SavingBuffer[LONG_DMA_BUF_SIZE];
    
protected:
    OUT_MODE                m_Mode;
    OutputDeviceContext*    m_pOutDevCtxt;
    InModeInterfaceLayer**  m_ppInMode;
    PCB_MODEOP              m_pStartPlayback;
    PCB_MODEOP              m_pStopPlayback;

    OUT_STATE               m_CurrState;
    OUT_STATE               m_PrioState;
    DWORD                   m_dwStateIndex;
    
    HANDLE                  m_hWaveEvent;
    DWORD                   m_dwElapsedTime;
    
    HANDLE                  m_hDumpFile;
public:
    BOOL                    m_bModeChange;
    PVOID                   m_pModeChangeParam1;  
    
public:
    OutModeInterfaceLayer(OutputDeviceContext *pDevCtxt, InModeInterfaceLayer** ppInMode, PCB_MODEOP pStart, PCB_MODEOP pStop);
    ~OutModeInterfaceLayer();

    BOOL        SavePCMBuffer(PBYTE pSrc, DWORD dwSize, BOOL bCopy = TRUE);
    VOID     SetMode(OUT_MODE Mode) {m_Mode = Mode;}
    OUT_MODE GetMode(VOID) {return m_Mode;}

protected:
    virtual BOOL    IsBufferInXfer(DWORD dwBufNum) = 0;
    virtual BOOL    PauseTransfer(DWORD dwParam) = 0;
    
protected:
    BOOL    IsAllStreamReady(DWORD dwMinBuffedSize);
    DWORD   GetBufferedPCMSize(VOID);
    DWORD   FillBuffer(PBYTE pStart, DWORD dwSize);
    DWORD   FillBuffer(DWORD dwBufNum);
    DWORD   TransferBuffer(VOID);
    PBYTE   TransferBufferFromSB(PBYTE pStart, PBYTE pEnd);
    VOID        DumpPCMData(PBYTE pBuffer, DWORD dwBytes, BOOL bNeedAlignment);

protected:
    BOOL SetState(DWORD dwState)
    {
        if(m_CurrState != dwState)
        {
            m_PrioState = m_CurrState;
            m_CurrState = (OUT_STATE) dwState;
            //DBGMSG(WAVE_INFO,(L"[WAV] CurrState : %d, PrioState : %d, BufSize : %d\r\n", m_CurrState, m_PrioState, m_dwDMABufSize));
        }

        return TRUE;
    }
    BOOL GetCurrState(VOID) {return m_CurrState;}
    BOOL GetPrioState(VOID) {return m_PrioState;}
    VOID SelfPowerNotify(WAVE_POWER_STATE PowerState)
    {
        if(m_PowerState != PowerState)
        {
            m_bSelfPowerNotify = TRUE;
            SetState(m_dwStateIndex + (DWORD)PowerState*10+1);
            //DBGMSG(WAVE_INFO,(L"[WAV] Self Power Request : D%d\r\n", PowerState));
            DevicePowerNotify(WAVE_POWER_NAME, PowerState, POWER_NAME);
        }
    }
} *POutModeInterfaceLayer, **PPOutModeInterfaceLayer;

class InputDeviceContext;

typedef class InModeInterfaceLayer  : public ModeInterfaceLayer
{
protected:
    IN_MODE                 m_Mode;
    InputDeviceContext *    m_pInDevCtxt;
    OutModeInterfaceLayer** m_ppOutMode;
    PCB_MODEOP              m_pStartPlayback;
    PCB_MODEOP              m_pStopPlayback;
    
public:
    InModeInterfaceLayer(InputDeviceContext *pDevCtxt, OutModeInterfaceLayer** ppOutMode, PCB_MODEOP pStart, PCB_MODEOP pStop);
    ~InModeInterfaceLayer();

    IN_MODE GetMode(VOID)
    {
        return m_Mode;
    }

protected:
    DWORD   FillBuffer(DWORD dwBufNum);
    DWORD   TransferBuffer(VOID);
} *PInModeInterfaceLayer, **PPInModeInterfaceLayer;
