//////////////////////////////////////////////////////
//
// VisualStudio 2005 PWLib Port, 
// (c) 2007 Dinsk.net
// developer@dinsk.net 
//
//////////////////////////////////////////////////////
//
// (c) Yuri Kiryanov, openh323@kiryanov.com
// and Yuriy Gorvitovskiy
//
// Portions (c) 1997 Tim Kientzle
// from ``The Programmer's Guide to Sound.''
//
// Windows CE port of OpenH323 Open Source Project, www.openh323.org
// Extra Multimedia functionality 
// 

#include "ptlib.h"

#ifdef _WIN32_WCE

#include <ptlib/wm/stdlibx.h>
#include <ptlib/wm/mmsystemx.h>


////////////////////////////////////////////////////////////////////
// Chunks
class ChunkFinder 
{
    struct Chunk {
      unsigned long type; // Type of chunk
      unsigned long size; // Size of chunk
      unsigned long remaining; // Bytes left to read
      bool isContainer;   // true if this is a container
      unsigned long containerType; // type of container
    } m_chunkStack[5];

public:
    ChunkFinder(HANDLE hFile) : m_hFile(hFile), 
        m_fGoodFile(false), m_currentChunk(-1), m_fFormatErrorsFound(false),
        m_type(0L), m_size(0L)
    {
        m_fGoodFile = !(SetFilePointer(hFile, 0, NULL, FILE_BEGIN) == -1L);
        memset(m_chunkStack, 0, sizeof(m_chunkStack));
    };

    HANDLE m_hFile;
    bool m_fGoodFile;
    bool m_fFormatErrorsFound;

    int m_currentChunk; // top of stack
    DWORD m_type;
    DWORD m_size;

    bool FindNext(void) 
    {
        if(!m_fGoodFile)
            return false;
        
        if ((m_currentChunk >= 0) && (!m_chunkStack[m_currentChunk].isContainer)) {
           unsigned long lastChunkSize = m_chunkStack[m_currentChunk].size;
           if (lastChunkSize & 1) {  // Is there padding?
              m_chunkStack[m_currentChunk].remaining++;
              lastChunkSize++; // Account for padding in the container update
           }
           SetFilePointer(m_hFile, m_chunkStack[m_currentChunk].remaining, NULL, FILE_CURRENT); // Flush the chunk
           m_currentChunk--;  // Drop chunk from the stack
           
           // Sanity check: containing chunk must be container
           if ((m_currentChunk < 0) || (!m_chunkStack[m_currentChunk].isContainer)) {
              // Chunk contained in non-Container
              m_fFormatErrorsFound = true;
              return false;
           }
           // Reduce size of container
           if (m_currentChunk >= 0) {
              // Sanity check: make sure container is big enough.
              // Also, avoid a really nasty underflow situation.
              if ((lastChunkSize+8) > m_chunkStack[m_currentChunk].remaining) {
                 m_fFormatErrorsFound = true; // Chunk is too large to fit in container
                 m_chunkStack[m_currentChunk].remaining = 0; // container is empty
              } else
                 m_chunkStack[m_currentChunk].remaining -= lastChunkSize + 8;
           }
        }
   
        // There may be forms that are finished, drop them too
        while (  (m_currentChunk >= 0)  // there is a chunk
              &&  (m_chunkStack[m_currentChunk].remaining < 8)
              )
        {
           SetFilePointer(m_hFile, m_chunkStack[m_currentChunk].remaining, NULL, FILE_CURRENT); // Flush the chunk
           unsigned long lastChunkSize = m_chunkStack[m_currentChunk].size;
           m_currentChunk--;  // Drop container chunk

           // Sanity check, containing chunk must be container
           if (!m_chunkStack[m_currentChunk].isContainer) {
              // Chunk contained in non-container
              return false;
           }
           // Reduce size of container
           if (m_currentChunk >= 0) {
              if ((lastChunkSize+8) > m_chunkStack[m_currentChunk].remaining) {
                 // Error in WAVE file: Chunk is too large to fit
                 lastChunkSize = m_chunkStack[m_currentChunk].remaining;
              }
              m_chunkStack[m_currentChunk].remaining -= lastChunkSize + 8;
           }
        }

       // Read the next chunk
       DWORD dwRead = 0L;
       DWORD dwResult = ReadFile(m_hFile, &m_type, sizeof(long), &dwRead, NULL);
       if ((dwResult != 0) && (dwRead == 0L)) 
       {
          m_currentChunk = -1; // empty the stack
          return false;
       }

       dwResult = ReadFile(m_hFile, &m_size, sizeof(long), &dwRead, NULL);

       if ((dwResult != 0) && (dwRead == 0L)) 
       {
          m_currentChunk = -1; // empty the stack
          return false;
       }

       // Put this chunk on the stack
       m_currentChunk++;
       m_chunkStack[m_currentChunk].type = m_type;
       m_chunkStack[m_currentChunk].size = m_size;
       m_chunkStack[m_currentChunk].remaining = m_size;
       m_chunkStack[m_currentChunk].isContainer = false;
       m_chunkStack[m_currentChunk].containerType = 0;

         if ((m_currentChunk >= 0) && 
            (m_chunkStack[0].type != MAKEFOURCC('R','I','F','F')))
        {
           // Outermost chunk is not RIFF 
           m_currentChunk = -1;
           return false;
        }
        
        if (m_type == MAKEFOURCC('R','I','F','F')) 
        {
           m_chunkStack[m_currentChunk].isContainer = true;
        
           // Need to check size of container first.
           dwResult = ReadFile(m_hFile, &m_chunkStack[m_currentChunk].containerType, 
                sizeof(long), &dwRead, NULL);
           
           if ((dwResult != 0) && (dwRead == 0L)) 
           {
              m_currentChunk = -1; // empty the stack
              return false;
           }

           m_chunkStack[m_currentChunk].remaining -= 4;
           if (m_currentChunk > 0) 
              m_fFormatErrorsFound = true; // RIFF chunk seen at inner level

           return true;
        }
        
        if (m_type == MAKEFOURCC('f','m','t',' ')) 
        {
           if (m_currentChunk != 1) 
              m_fFormatErrorsFound = true; // FMT chunk seen at wrong level?!?!\n";

           m_chunkStack[m_currentChunk].remaining = 0;
           return true;
        }
        
        if (m_type == MAKEFOURCC('d','a','t','a')) 
        {
           return true;
        }

        // Some unknown chunk found
        return true;
    }

    bool FindRiffHeader() 
    {
        if ( !FindNext() || (m_currentChunk != 0)
          || (m_chunkStack[0].type != MAKEFOURCC('R','I','F','F'))
          || (m_chunkStack[0].isContainer != true)
          || (m_chunkStack[0].containerType != MAKEFOURCC('W','A','V','E'))
          )
          return false;
        return true;
    }

    bool FindFmtChunk() 
    {
        bool found = false;
        if( m_currentChunk < 0 )
            found = FindRiffHeader();

        if ( !found || !FindNext() || (m_currentChunk != 1)
          || (m_chunkStack[1].type != MAKEFOURCC('f','m','t',' '))
          ) return false;

        return true;
    }
    
    bool FindDataChunk() 
    {
        bool found = false;
        if( m_currentChunk < 1 )
            found = FindFmtChunk();

        if (!found) // Skip format 
            return false;
        else
        {
            SetFilePointer(m_hFile, m_size, NULL, FILE_CURRENT);

            found = FindNext();
            while (               
                (m_chunkStack[m_currentChunk].type != MAKEFOURCC('d','a','t','a'))
              ) { found = FindNext(); };
        
            return found && 
                (m_chunkStack[m_currentChunk].type == MAKEFOURCC('d','a','t','a'));
        }
    }
};

HMMIO WINAPI mmioOpen(LPSTR pszFileName, LPMMIOINFO pmmioinfo, DWORD fdwOpen)
{
  DWORD dwAccess = fdwOpen & MMIO_READ ? GENERIC_READ : 0;
  dwAccess |= (fdwOpen & MMIO_WRITE) ? GENERIC_WRITE : 0xFFFF; 
  DWORD dwFlags = fdwOpen & MMIO_CREATE ? CREATE_ALWAYS : OPEN_EXISTING;

  PVarString filename = pszFileName;
  HANDLE hFile = CreateFile(filename,
                            dwAccess,
                            FILE_SHARE_READ|FILE_SHARE_WRITE,
                            NULL,
                            dwFlags,
                            FILE_ATTRIBUTE_NORMAL,
                            NULL);
    
  if (pmmioinfo != NULL) 
  {
    memset(pmmioinfo, 0, sizeof(MMIOINFO));
    pmmioinfo->wErrorRet = GetLastError();

    if( pmmioinfo->wErrorRet == ERROR_ALREADY_EXISTS )
        pmmioinfo->wErrorRet = 0L;
  }
    
  return (HMMIO) hFile;
}

MMRESULT WINAPI mmioClose(HMMIO hmmio, UINT fuClose)
{
    UNREFERENCED_PARAMETER(fuClose);
    return CloseHandle(hmmio) ? MMSYSERR_NOERROR : MMSYSERR_INVALHANDLE;
}

LONG WINAPI mmioRead(HMMIO hmmio, HPSTR pch, LONG cch)
{
    DWORD dwRead = 0L;
    ReadFile(hmmio, pch, cch, &dwRead, NULL);
    return dwRead;
}

LONG WINAPI mmioWrite(HMMIO hmmio, const char * pch, LONG cch)
{
    DWORD dwWritten = 0L;
    WriteFile(hmmio, pch, cch, &dwWritten, NULL);

    return dwWritten;
}

MMRESULT WINAPI mmioDescend(HMMIO hmmio, LPMMCKINFO pmmcki,
    const MMCKINFO FAR* pmmckiParent, UINT fuDescend)
{
  UNREFERENCED_PARAMETER(pmmckiParent);

  if( fuDescend & MMIO_FINDRIFF )
  {    
    // Locate a 'RIFF' chunk with a 'WAVE' form type
    ChunkFinder cf(hmmio);
    if( !cf.FindRiffHeader() )
        return MMSYSERR_ERROR;
  }

  if( fuDescend & MMIO_FINDCHUNK && pmmcki )
  {    
     if(pmmcki->ckid == mmioFOURCC('f', 'm', 't', ' '))
     {
        // Find the format chunk
        ChunkFinder cf(hmmio);
        if( !cf.FindFmtChunk() )
            return MMSYSERR_ERROR;

        pmmcki->cksize = cf.m_size + 2; 
     }

     if(pmmcki->ckid == mmioFOURCC('d', 'a', 't', 'a'))
     {
        // Find the data chunk
        ChunkFinder cf(hmmio);
        if( !cf.FindDataChunk() )
            return MMSYSERR_ERROR;

        pmmcki->cksize = cf.m_size; 
     }
  }
  return MMSYSERR_NOERROR;
}

MMRESULT WINAPI mmioAscend(HMMIO hmmio, LPMMCKINFO pmmcki, UINT fuAscend)
{
  UNREFERENCED_PARAMETER(hmmio);
  UNREFERENCED_PARAMETER(pmmcki);
  UNREFERENCED_PARAMETER(fuAscend);

  // Do nothing - mmioDescend traverses from beginning, no need to move up
  return MMSYSERR_NOERROR;
}

MMRESULT WINAPI mmioCreateChunk(HMMIO hmmio, LPMMCKINFO pmmcki, UINT fuCreate)
{
   UNREFERENCED_PARAMETER(hmmio);
   UNREFERENCED_PARAMETER(pmmcki);
   UNREFERENCED_PARAMETER(fuCreate);

   return MMSYSERR_NOERROR;
}

BOOL WINAPI PlaySound( LPCSTR pszSound, HMODULE hmod, DWORD fdwSound)
{
  PVarString sound = pszSound;
  return ::PlaySound(sound, hmod, fdwSound);
}

MMRESULT WINAPI waveInGetErrorText(MMRESULT mmrError, char* pszText, UINT cchText)
{
    TCHAR tch[1024];
    MMRESULT mmResult = waveInGetErrorText(mmrError, tch, 1024);
    wcstombs(pszText, tch, cchText);

    return mmResult;
}

MMRESULT WINAPI waveOutGetErrorText(MMRESULT mmrError, char* pszText, UINT cchText)
{
    TCHAR tch[1024];
    MMRESULT mmResult = waveOutGetErrorText(mmrError, tch, 1024);
    wcstombs(pszText, tch, cchText);

    return mmResult;
}

#endif // _WIN32_WCE
