#include ".\OggDSound.h"

#include  <process.h>           //for threading support

#include <stdio.h>

#pragma comment( lib, "dxguid.lib" )
#pragma comment( lib, "dsound.lib" )

#pragma comment( lib, "ogg_static.lib" )
#pragma comment( lib, "vorbisfile_static.lib" )

const int BUFFER_HALF_SIZE = 25 * 1024;     //15 kb, which is roughly one second of OGG sound

int                   iVolumeRange[101] = { -10000, -6644, -5644, -5059, -4644,
                                             -4322, -4059, -3837, -3644, -3474,
                                             -3322, -3184, -3059, -2943, -2837,
                                             -2737, -2644, -2556, -2474, -2396,
                                             -2322, -2252, -2184, -2120, -2059,
                                             -2000, -1943, -1889, -1837, -1786,
                                             -1737, -1690, -1644, -1599, -1556,
                                             -1515, -1474, -1434, -1396, -1358,
                                             -1322, -1286, -1252, -1218, -1184,
                                             -1152, -1120, -1089, -1059, -1029,
                                             -1000,  -971,  -943,  -916,  -889,
                                              -862,  -837,  -811,  -786,  -761,
                                              -737,  -713,  -690,  -667,  -644,
                                              -621,  -599,  -578,  -556,  -535,
                                              -515,  -494,  -474,  -454,  -434,
                                              -415,  -396,  -377,  -358,  -340,
                                              -322,  -304,  -286,  -269,  -252,
                                              -234,  -218,  -201,  -184,  -168,
                                              -152,  -136,  -120,  -105,   -89,
                                               -74,   -59,   -44,   -29,   -14,
                                               0 };



CDirectSoundOgg::CDirectSoundOgg() : 
  m_pPrimarySoundBuffer( NULL ),
  m_pBuffer(NULL),
  m_hPlayThread(0),
  m_bPlayThreadActive(false),
  m_hStopPlaybackEvent(0),
  m_bPlaybackDone(false),
  m_pDirectSound( NULL ),
  m_bPaused( false ),
  m_bFileLoaded( false ),
  m_dwVolume( 100 )
{
  //----- create critical section to guard shared memory

  InitializeCriticalSection(&m_criticalSection);  
}




bool CDirectSoundOgg::Allocate() 
{
  //----- open OGG file

  //get a handle to the file
  if ( m_strFilename.empty() )
  {
    return false;
  }

  FILE *fileHandle = fopen( m_strFilename.c_str(), "rb");

  //test to make sure that the file was opened succesfully

  if(!fileHandle) return false;

  //open the file as an OGG file (allocates internal OGG buffers)

  if((ov_open(fileHandle, &m_vorbisFile, NULL, 0)) != 0) {
    //close the file

    fclose(fileHandle);
    //return error

    return false;
  }


  //----- Get OGG info

  //get information about OGG file

  vorbis_info *vorbisInfo = ov_info(&m_vorbisFile, -1);


  //----- setup buffer to hold OGG

  //get a wave format structure, which we will use later to create the DirectSound buffer description

  WAVEFORMATEX      waveFormat;
  //clear memory

  memset(&waveFormat, 0, sizeof(waveFormat));
  //set up wave format structure

  waveFormat.cbSize     = sizeof(waveFormat);                 //how big this structure is

  waveFormat.nChannels    = vorbisInfo->channels;                 //how many channelse the OGG contains

  waveFormat.wBitsPerSample = 16;                         //always 16 in OGG

  waveFormat.nSamplesPerSec = vorbisInfo->rate;                   //sampling rate (11 Khz, 22 Khz, 44 KHz, etc.)

    waveFormat.nAvgBytesPerSec  = waveFormat.nSamplesPerSec * waveFormat.nChannels * 2; //average bytes per second

    waveFormat.nBlockAlign    = 2 * waveFormat.nChannels;               //what block boundaries exist

    waveFormat.wFormatTag   = 1;                          //always 1 in OGG



  //----- prepare DirectSound buffer description

  //get a buffer description

  DSBUFFERDESC dsBufferDescription;
  //clear memory

  memset(&dsBufferDescription, 0, sizeof(dsBufferDescription));
  //set up buffer description structure

  dsBufferDescription.dwSize      = sizeof(dsBufferDescription);    //how big this structure is

  dsBufferDescription.lpwfxFormat   = &waveFormat;            //information about the sound that the buffer will contain

  dsBufferDescription.dwBufferBytes = BUFFER_HALF_SIZE * 2;       //total buffer size = 2 * half size

  dsBufferDescription.dwFlags     = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLVOLUME | DSBCAPS_STICKYFOCUS;   //buffer must support notifications



  //----- create sound buffer

  //pointer to old interface, used to obtain pointer to new interface

  if(FAILED(m_pDirectSound->CreateSoundBuffer(&dsBufferDescription, &m_pBuffer, NULL))) {
    //return error

    return false;
  }

  //----- create notification event object used to signal when playback should stop

  m_hStopPlaybackEvent = CreateEvent(NULL, false, false, NULL);


  //----- Fill first half of buffer with initial data

  Fill(true); 


  //----- success

  return true;
}




CDirectSoundOgg::~CDirectSoundOgg() 
{

  if ( IsPlaying() )
  {
    Stop();
  }
  DeleteCriticalSection( &m_criticalSection );

}



void CDirectSoundOgg::Cleanup() 
{

  if ( m_bFileLoaded )
  {
    ov_clear(&m_vorbisFile);
    m_bFileLoaded = false;
  }

  if ( m_hStopPlaybackEvent )
  {
    CloseHandle( m_hStopPlaybackEvent );
    m_hStopPlaybackEvent = NULL;
  }

  if ( m_pBuffer )
  {
    m_pBuffer->Release();
    m_pBuffer = NULL;
  }

}




bool CDirectSoundOgg::Fill( const bool firstHalf ) 
{

  LPVOID  pFirstSegment;              //holds a pointer to the first segment that we lock

  DWORD nFirstSegmentSize = 0;          //holds how big the first segment is

  LPVOID  pSecondSegment;             //holds a pointer to the second segment that we lock

  DWORD nSecondSegmentSize = 0;         //holds how big the second segment is



    //----- lock DirectSound buffer half

  if(FAILED(m_pBuffer->Lock(
    (firstHalf ? 0 : BUFFER_HALF_SIZE),     //if we are locking the first half, lock from 0, otherwise lock from BUFFER_HALF_SIZE

    BUFFER_HALF_SIZE,             //how big a chunk of the buffer to block

    &pFirstSegment,               //pointer that will receive the locked segment start address

    &nFirstSegmentSize,             //will return how big the first segment is (should always be BUFFER_HALF_SIZE)

    &pSecondSegment,              //pointer that will receive the second locked segment start address (in case of wrapping)

    &nSecondSegmentSize,            //how big a chunk we wrapped with (in case of wrapping)

    0                     //flags: no extra settings

    ))) 
  {
    //return error

    return false;
  }


  //----- debug safety: we should always have locked a complete segment of the size we requested
  if ( nFirstSegmentSize != BUFFER_HALF_SIZE )
  {
    return false;
  }

  //----- decode OGG file into buffer

  unsigned int nBytesReadSoFar  = 0; //keep track of how many bytes we have read so far

  long nBytesReadThisTime     = 1; //keep track of how many bytes we read per ov_read invokation (1 to ensure that while loop is entered below)

  int nBitStream          = 0; //used to specify logical bitstream 0


  //decode vorbis file into buffer half (continue as long as the buffer hasn't been filled with something (=sound/silence)

  while(nBytesReadSoFar < BUFFER_HALF_SIZE) {
    //decode

    nBytesReadThisTime = ov_read(
      &m_vorbisFile,                //what file to read from

      (char*)pFirstSegment + nBytesReadSoFar,   //where to put the decoded data

      BUFFER_HALF_SIZE - nBytesReadSoFar,     //how much data to read

      0,                      //0 specifies little endian decoding mode

      2,                      //2 specifies 16-bit samples

      1,                      //1 specifies signed data

      &nBitStream
    );

    //new position corresponds to the amount of data we just read

    nBytesReadSoFar += nBytesReadThisTime;


    //----- do special processing if we have reached end of the OGG file

    if(nBytesReadThisTime == 0) {
      //----- if looping we fill start of OGG, otherwise fill with silence

      if(m_bLooping) {
        //seek back to beginning of file

        ov_time_seek(&m_vorbisFile, 0);
      } else {
        //fill with silence

        for(unsigned int i = nBytesReadSoFar; i < BUFFER_HALF_SIZE; i++) {
          //silence = 0 in 16 bit sampled data (which OGG always is)

          *((char*)pFirstSegment + i) = 0;
        }

        //signal that playback is over

        m_bPlaybackDone = true;

        //and exit the reader loop

        nBytesReadSoFar = BUFFER_HALF_SIZE;
      }
    }
  } 


  //----- unlock buffer

  m_pBuffer->Unlock(pFirstSegment, nFirstSegmentSize, pSecondSegment, nSecondSegmentSize);

  return true;
}




bool CDirectSoundOgg::IsPlaying() 
{

  return GetPlayThreadActive();

}



void CDirectSoundOgg::Stop() 
{

  if ( !IsPlaying() )
  {
    return;
  }

  m_bPaused = false;


  //----- Signal the play thread to stop

  SetEvent(m_hStopPlaybackEvent);

  //wait for playing thread to exit

  if(WaitForSingleObject(m_hPlayThread, 500 ) == WAIT_ABANDONED) {
    //the thread hasn't terminated as expected. kill it

    TerminateThread(m_hPlayThread, 1);
    
    //not playing any more

    SetPlayThreadActive(false);

    //since playing thread has not cleaned up, this thread will have to

    Cleanup();
    
    //TODO: You should report this error here somehow

  }


  //----- store that we are not playing any longer

  SetPlayThreadActive(false);
}



bool CDirectSoundOgg::Play( const bool looping ) 
{

  if ( IsPlaying() )
  {
    Stop();
    Cleanup();
  }

  Allocate();
  
  SetPlayThreadActive( true );

  m_bLooping = looping;

  unsigned int nThreadID = 0;

  m_hPlayThread = (HANDLE)CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)CDirectSoundOgg::PlayingThread, this, 0, (LPDWORD)&nThreadID );

  return ( m_hPlayThread != NULL );

}




/*!
@returns  a string containing the filename of the OGG that will be played.
*/
std::string CDirectSoundOgg::GetFileName() 
{

  EnterCriticalSection(&m_criticalSection);
  std::string strFilename = m_strFilename;
  LeaveCriticalSection(&m_criticalSection);

  return strFilename;

}




unsigned int CDirectSoundOgg::PlayingThread(LPVOID lpParam) 
{

  //cast thread parameter to a CDirectSoundOgg pointer

  CDirectSoundOgg *oggInstance = static_cast<CDirectSoundOgg*>(lpParam);

  //allocate all resources

  if ( oggInstance->m_pBuffer == NULL )
  {
    return 1;
  }

  //----- allocations

  bool bErrorOccured = false;   //assume everything will go ok



  //----- indicate that we are playing

  oggInstance->m_bPlaybackDone = false;


  //----- start playing the buffer (looping because we are going to refill the buffer)
  oggInstance->m_pBuffer->SetVolume( iVolumeRange[oggInstance->m_dwVolume] );

  HRESULT   hRes = oggInstance->m_pBuffer->Play( 0, 0, DSBPLAY_LOOPING );
  if ( FAILED( hRes ) )
  {
    return 0;
  }


  //----- go into loop waiting on notification event objects

  //create tracker of what half we have are playing

  bool bPlayingFirstHalf = true;

  //keep going in the loop until we need to stop

  bool  bContinuePlaying = true;    //used to keep track of when to stop the while loop

  bool  bPlaybackDoneProcessed = false; //used ot only process m_bPlaybackDone once

  int   nStopAtNextHalf = 0;      //0 signals "do not stop", 1 signals "stop at first half", 2 signals "stop at second half"


  //enter refill loop
  DWORD   dwPrevPos = BUFFER_HALF_SIZE * 2;

  while(bContinuePlaying && (!bErrorOccured)) 
  {
    

    Sleep( 30 );

    int   start = 0;

    if ( WaitForSingleObject( oggInstance->m_hStopPlaybackEvent, 0 ) == WAIT_OBJECT_0 ) 
    {
      break;
    }

    bool    bRefillNeeded = false;

    {

			if (!bContinuePlaying) break;

      // Ersatz-Code
      //soundBufferCurrentPosition = pSoundClass->GetPos( dwSoundBufferHandle );

      // Orig-Code
      DWORD   soundBufferCurrentPosition = 0;
      oggInstance->m_pBuffer->GetCurrentPosition( &soundBufferCurrentPosition, NULL );

      DWORD     dwCurFragment = soundBufferCurrentPosition / BUFFER_HALF_SIZE;

      //dh::Log( "CurPos %d", soundBufferCurrentPosition );

      //dh::Log( "CurFrag %d / PrevFrag %d", dwCurFragment, dwPrevPos / BUFFER_HALF_SIZE );

      if ( dwCurFragment != dwPrevPos / BUFFER_HALF_SIZE )
      {
        dwPrevPos = soundBufferCurrentPosition;
        bRefillNeeded = true;
			  if ( soundBufferCurrentPosition < (DWORD)BUFFER_HALF_SIZE )
        {
				  start = BUFFER_HALF_SIZE;
        }
			  else
        {
				  start = 0;
        }
      }
    }

    //switch(WaitForMultipleObjects((DWORD)stlvEventObjects.size(), &(stlvEventObjects[0]), FALSE, INFINITE)) {
      
      //----- first half was reached

      //case WAIT_OBJECT_0:
    if ( bRefillNeeded )
    {
      //dh::Log( "Refill half %d", start );
      if ( start > 0 )
      {
          //check if we should stop playing back

          if(nStopAtNextHalf == 1) {
            //stop playing

            bContinuePlaying = false;
            //leave and do not fill the next buffer half

            break;
          }

          //fill second half with sound

          if(!(oggInstance->Fill(false))) bErrorOccured = true;

          //if the last fill was the final fill, we should stop next time we reach this half (i.e. finish playing whatever audio we do have)

          if((oggInstance->m_bPlaybackDone) && (!bPlaybackDoneProcessed)) {
            //make the while loop stop after playing the next half of the buffer

            nStopAtNextHalf = 1;
            //indicate that we have already processed the playback done flag

            bPlaybackDoneProcessed = true;
          }

      }

        //----- second half was reached
          else
          {

          //check if we should stop playing back

          if(nStopAtNextHalf == 2) {
            //stop playing

            bContinuePlaying = false;
            //leave and do not fill the next buffer half

            break;
          }

          //fill first half with sound

          if(!(oggInstance->Fill(true))) bErrorOccured = true;

          //if this last fill was the final fill, we should stop next time we reach this half (i.e. finish playing whatever audio we do have)

          if((oggInstance->m_bPlaybackDone) && (!bPlaybackDoneProcessed)) {
            //make the while loop stop after playing the next half of the buffer

            nStopAtNextHalf = 2;
            //indicate that we have already processed the playback done flag

            bPlaybackDoneProcessed = true;
          }
        }
    }

        /*
      //----- stop event has happened

      case WAIT_OBJECT_0 + 2:
        //exit the while loop

        bContinuePlaying = false;

        break;
        */
  }
  //----- stop the buffer from playing

  oggInstance->m_pBuffer->Stop();


  //----- perform cleanup

  oggInstance->Cleanup();


  //----- thread no longer active

  oggInstance->SetPlayThreadActive(false);


  //----- done

  return (bErrorOccured ? 1 : 0);

}



/*!
@returns  true if the playing thread is currently running, otherwise false
*/
bool CDirectSoundOgg::GetPlayThreadActive() {
  EnterCriticalSection(&m_criticalSection);
  bool bActive = m_bPlayThreadActive;
  LeaveCriticalSection(&m_criticalSection);

  return bActive;
}



/*!
@param  bActive is true if you want CDirectSoundOgg::GetPlayThreadActive() to return true, or false if you want it to return false
*/
void CDirectSoundOgg::SetPlayThreadActive(bool bActive) {
  EnterCriticalSection(&m_criticalSection);
  m_bPlayThreadActive = bActive;
  LeaveCriticalSection(&m_criticalSection);
}



bool CDirectSoundOgg::Initialize( HWND hwndMain )
{

  if ( m_pDirectSound )
  {
    return false;
  }
  HRESULT hResult = DirectSoundCreate( NULL, &m_pDirectSound, NULL );

  if ( ( FAILED( hResult ) )
  &&   ( hResult != DSERR_NODRIVER ) )
  {
    return false;
  }

  m_hwndMain = hwndMain;
  m_pDirectSound->SetCooperativeLevel( m_hwndMain, DSSCL_PRIORITY );

  /*
  DSBUFFERDESC    soundBufferFormat;

  ::memset(&soundBufferFormat,0,sizeof(DSBUFFERDESC));
    soundBufferFormat.dwSize       =sizeof(DSBUFFERDESC);
    soundBufferFormat.dwFlags      =DSBCAPS_PRIMARYBUFFER;
    soundBufferFormat.dwBufferBytes=0;
    soundBufferFormat.lpwfxFormat  =NULL;

	if (m_pDirectSound->CreateSoundBuffer
				(&soundBufferFormat,&m_pPrimarySoundBuffer,NULL)!=DS_OK) {
		return false;
	}

        WAVEFORMATEX pcmwf;

  ::memset(&pcmwf,0,sizeof(WAVEFORMATEX));
	pcmwf.wFormatTag     =WAVE_FORMAT_PCM;
	pcmwf.nChannels      = 1;
	pcmwf.nSamplesPerSec = 44100;
	pcmwf.wBitsPerSample = 16;
	pcmwf.nBlockAlign    =(pcmwf.wBitsPerSample * pcmwf.nChannels) / 8;
	pcmwf.nAvgBytesPerSec=pcmwf.nSamplesPerSec*pcmwf.nBlockAlign;

    if (m_pPrimarySoundBuffer->SetFormat
				(&pcmwf)!=DS_OK) {
		return false;
	}
    m_pPrimarySoundBuffer->Play(0,0,DSBPLAY_LOOPING);
    */

  return true;

}



bool CDirectSoundOgg::Release()
{

  Stop();
  if ( m_pPrimarySoundBuffer )
  {
    m_pPrimarySoundBuffer->Stop();
    m_pPrimarySoundBuffer->Release();
    m_pPrimarySoundBuffer = NULL;
  }
  if ( m_pDirectSound )
  {
    m_pDirectSound->Release();
    m_pDirectSound = NULL;
  }

  return true;

}



bool CDirectSoundOgg::IsInitialized()
{

  return ( m_pBuffer != NULL );

}



int CDirectSoundOgg::Volume()
{

  return m_dwVolume;

}



bool CDirectSoundOgg::SetVolume( int iVolume )
{

  if ( iVolume < 0 )
  {
    iVolume = 0;
  }
  if ( iVolume > 100 )
  {
    iVolume = 100;
  }
  m_dwVolume = iVolume;
  if ( m_pBuffer )
  {
    return SUCCEEDED( m_pBuffer->SetVolume( iVolumeRange[iVolume] ) );
  }
  return true;

}




bool CDirectSoundOgg::LoadMusic( const char* szFileName )
{

  Stop();
  Cleanup();

  m_strFilename = szFileName;

  if ( !Allocate() )
  {
    return false;
  }
  m_bFileLoaded = true;

  return true;

}



bool CDirectSoundOgg::Resume()
{

  if ( !m_bPaused )
  {
    return false;
  }
  m_pBuffer->Play( 0, 0, m_bLooping ? DSBPLAY_LOOPING : 0 );

  m_bPaused = false;
  return true;

}



bool CDirectSoundOgg::Pause()
{

  if ( m_bPaused )
  {
    return true;
  }
  if ( !IsPlaying() )
  {
    return false;
  }
  m_pBuffer->Stop();
  m_bPaused = true;

  return true;

}
