#include <math.h>
#include "sink_dsound.h"
#include "../win32/winspk.h"

#define SAFE_RELEASE(p) { if (p) p->Release(); p = 0; }


DSoundSink::DSoundSink()
{
  hwnd         = 0;
  device       = 0;
  buf_size_ms  = 0;
  preload_ms   = 0;

  spk          = spk_unknown;
  buf_size     = 0;
  preload_size = 0;
  bytes2time   = 0.0;

  ds      = 0;
  ds_buf  = 0;

  cur     = 0;
  time    = 0;
  playing = false;
  paused  = true;

  ev_stop = CreateEvent(0, true, false, 0);
}

DSoundSink::~DSoundSink()
{
  close();
  close_dsound();

  CloseHandle(ev_stop);
}




bool
DSoundSink::open_dsound(HWND _hwnd, int _buf_size_ms, int _preload_ms, LPCGUID _device)
{
  if (ds)
    close_dsound();

  

  if FAILED(DirectSoundCreate(_device, &ds, 0))
    return false;

  if (!_hwnd) _hwnd = GetForegroundWindow();
  if (!_hwnd) _hwnd = GetDesktopWindow();
  if FAILED(ds->SetCooperativeLevel(_hwnd, DSSCL_PRIORITY))
  {
    SAFE_RELEASE(ds);
    return false;
  }

  buf_size_ms = _buf_size_ms;
  preload_ms = _preload_ms;
  device = _device;
  return true;
}

void
DSoundSink::close_dsound()
{
  close();
  if (ds)
    SAFE_RELEASE(ds);
}

bool
DSoundSink::open(Speakers _spk)
{
  if (!ds) return false;
  AutoLock autolock(&dsound_lock);

  WAVEFORMATEXTENSIBLE wfx;
  memset(&wfx, 0, sizeof(wfx));

  close();
  spk = _spk;

  if (spk2wfx(_spk, (WAVEFORMATEX*)(&wfx), true))
    if (open((WAVEFORMATEX*)(&wfx)))
      return true;

  if (spk2wfx(_spk, (WAVEFORMATEX*)&wfx, false))
    if (open((WAVEFORMATEX*)(&wfx)))
      return true;

  close();
  return false;
}

bool
DSoundSink::open(WAVEFORMATEX *wf)
{
  
  

  buf_size = wf->nBlockAlign * wf->nSamplesPerSec * buf_size_ms / 1000;
  preload_size = wf->nBlockAlign * wf->nSamplesPerSec * preload_ms / 1000;
  bytes2time = 1.0 / wf->nAvgBytesPerSec;

  
  DSBUFFERDESC dsbdesc;
  memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
  dsbdesc.dwSize        = sizeof(DSBUFFERDESC);
  dsbdesc.dwFlags       = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
  dsbdesc.dwFlags      |= DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLPAN;
  dsbdesc.dwBufferBytes = buf_size;
  dsbdesc.lpwfxFormat   = wf;

  
  if FAILED(ds->CreateSoundBuffer(&dsbdesc, &ds_buf, 0)) 
  {
    SAFE_RELEASE(ds_buf);

    
    dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
    if FAILED(ds->CreateSoundBuffer(&dsbdesc, &ds_buf, 0))
    {
      SAFE_RELEASE(ds_buf);
      return false;
    }
  }

  
  void *data;
  DWORD data_bytes;
  if FAILED(ds_buf->Lock(0, buf_size, &data, &data_bytes, 0, 0, 0)) 
  {
    SAFE_RELEASE(ds_buf);
    SAFE_RELEASE(ds);
    return false;
  }

  memset(data, 0, data_bytes);
  if FAILED(ds_buf->Unlock(data, data_bytes, 0, 0))
  {
    SAFE_RELEASE(ds_buf);
    SAFE_RELEASE(ds);
    return false;
  }

  
  ds_buf->SetCurrentPosition(0);
  cur = 0;
  time = 0;
  playing = false;
  paused = false;
  return true;
}

bool 
DSoundSink::try_open(Speakers _spk) const
{
  if (!ds) return false;

  
  

  WAVEFORMATEXTENSIBLE wfx;
  memset(&wfx, 0, sizeof(wfx));

  if (spk2wfx(_spk, (WAVEFORMATEX*)(&wfx), true))
    if (try_open((WAVEFORMATEX*)(&wfx)))
      return true;

  if (spk2wfx(_spk, (WAVEFORMATEX*)&wfx, false))
    if (try_open((WAVEFORMATEX*)(&wfx)))
      return true;

  return false;
}

bool
DSoundSink::try_open(WAVEFORMATEX *wf) const
{
  
  

  IDirectSoundBuffer *test_ds_buf = 0;

  DWORD test_buf_size = wf->nBlockAlign * wf->nSamplesPerSec * buf_size_ms / 1000;
  DWORD test_preload_size = wf->nBlockAlign * wf->nSamplesPerSec * preload_ms / 1000;

  
  DSBUFFERDESC dsbdesc;
  memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
  dsbdesc.dwSize        = sizeof(DSBUFFERDESC);
  dsbdesc.dwFlags       = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
  dsbdesc.dwFlags      |= DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLPAN;
  dsbdesc.dwBufferBytes = test_buf_size;
  dsbdesc.lpwfxFormat   = wf;

  
  if FAILED(ds->CreateSoundBuffer(&dsbdesc, &test_ds_buf, 0)) 
  {
    SAFE_RELEASE(test_ds_buf);

    
    dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
    if FAILED(ds->CreateSoundBuffer(&dsbdesc, &test_ds_buf, 0))
    {
      SAFE_RELEASE(test_ds_buf);
      return false;
    }
  }

  SAFE_RELEASE(test_ds_buf);
  return true;
}

void
DSoundSink::close()
{
  AutoLock autolock(&dsound_lock);

  
  
  
  

  SetEvent(ev_stop);
  AutoLock playback(&playback_lock);

  
  

  spk          = spk_unknown;
  buf_size     = 0;
  preload_size = 0;
  bytes2time   = 0.0;

  SAFE_RELEASE(ds_buf);

  cur     = 0;
  time    = 0;
  playing = false;
  paused  = false;

  
  

  ResetEvent(ev_stop);
}







void
DSoundSink::pause()
{
  AutoLock autolock(&dsound_lock);
  if (ds_buf)
    ds_buf->Stop();
  paused = true;
}

void 
DSoundSink::unpause()
{
  AutoLock autolock(&dsound_lock);
  if (ds_buf && playing)
    ds_buf->Play(0, 0, DSBPLAY_LOOPING);
  paused = false;
}

bool 
DSoundSink::is_paused() const
{
  return paused;
}

vtime_t 
DSoundSink::get_playback_time() const
{
  return time - get_data_time();
}

size_t 
DSoundSink::get_buffer_size() const
{
  return buf_size;
}

vtime_t
DSoundSink::get_buffer_time() const
{
  return buf_size * bytes2time;
}


size_t 
DSoundSink::get_data_size() const
{
  if (!ds_buf) return 0;
  AutoLock autolock(&dsound_lock);

  DWORD play_cur;
  ds_buf->GetCurrentPosition(&play_cur, 0);

  if (play_cur > cur)
    return buf_size + cur - play_cur;
  else if (play_cur < cur)
    return cur - play_cur;
  else
    
    
    
    
    
    return playing? buf_size: 0;
}

vtime_t
DSoundSink::get_data_time() const
{
  return get_data_size() * bytes2time;
}

void 
DSoundSink::stop()
{
  AutoLock autolock(&dsound_lock);

  
  
  
  

  SetEvent(ev_stop);
  AutoLock playback(&playback_lock);

  
  

  if (ds_buf)
  {
    ds_buf->Stop();
    ds_buf->SetCurrentPosition(0);
  }

  cur = 0;
  time = 0;
  playing = false;

  ResetEvent(ev_stop);
}

void 
DSoundSink::flush()
{
  
  
  

  AutoLock autolock(&playback_lock);

  void *data1, *data2;
  DWORD data1_bytes, data2_bytes;
  DWORD play_cur;
  size_t data_size;

  
  
  

  if FAILED(ds_buf->GetCurrentPosition(&play_cur, 0))
    return;

  if (cur < play_cur)
    data_size = buf_size + cur - play_cur;
  else if (cur > play_cur)
    data_size = cur - play_cur;
  else
  {
    if (!playing)
      
      return;
    else
      data_size = buf_size;
  }

  
  
  

  if (data_size > buf_size / 2)
  {
    data_size -= buf_size / 2;
    if (WaitForSingleObject(ev_stop, DWORD(data_size * bytes2time * 1000 + 1)) == WAIT_OBJECT_0)
      return;
  }

  
  
  

  if FAILED(ds_buf->GetCurrentPosition(&play_cur, 0))
    return;

  data_size = buf_size + play_cur - cur;
  if (data_size >= buf_size)
    data_size -= buf_size;

  if FAILED(ds_buf->Lock(cur, data_size, &data1, &data1_bytes, &data2, &data2_bytes, 0))
    return;

  memset(data1, 0, data1_bytes);
  if (data2_bytes)
    memset(data2, 0, data2_bytes);

  if FAILED(ds_buf->Unlock(data1, data1_bytes, data2, data2_bytes))
    return;

  
  

  if (!playing)
  {
    ds_buf->Play(0, 0, DSBPLAY_LOOPING);
    playing = true;
  }

  
  
  

  data_size = buf_size - data_size;
  if (WaitForSingleObject(ev_stop, DWORD(data_size * bytes2time * 1000 + 1)) == WAIT_OBJECT_0)
    return;

  
  

  stop();
}

double 
DSoundSink::get_vol() const
{
  if (!ds_buf) return 0;
  AutoLock autolock(&dsound_lock);

  LONG vol;
  if SUCCEEDED(ds_buf->GetVolume(&vol))
    return (double)(vol / 100);
  return 0;
}

void 
DSoundSink::set_vol(double vol)
{
  if (!ds_buf) return;
  AutoLock autolock(&dsound_lock);
  ds_buf->SetVolume((LONG)(vol * 100));
}

double 
DSoundSink::get_pan() const
{
  if (!ds_buf) return 0;
  AutoLock autolock(&dsound_lock);

  LONG pan;
  if SUCCEEDED(ds_buf->GetPan(&pan))
    return (double)(pan / 100);
  return 0;
}

void 
DSoundSink::set_pan(double pan)
{
  if (!ds_buf) return;
  AutoLock autolock(&dsound_lock);
  ds_buf->SetPan((LONG)(pan * 100));
}





bool 
DSoundSink::query_input(Speakers _spk) const
{
  return try_open(_spk);
}

bool 
DSoundSink::set_input(Speakers _spk)
{
  AutoLock autolock(&dsound_lock);

  close();
  return open(_spk);
}

Speakers
DSoundSink::get_input() const
{
  return spk;
}

bool DSoundSink::process(const Chunk *_chunk)
{
  
  
  

  AutoLock autolock(&playback_lock);

  if (_chunk->is_dummy())
    return true;

  
  
  
  

  if (_chunk->spk != spk)
    if (!set_input(_chunk->spk))
      return false;

  void *data1, *data2;
  DWORD data1_bytes, data2_bytes;
  DWORD play_cur;
  size_t data_size;

  size_t  size = _chunk->size;
  uint8_t *buf = _chunk->rawdata;

  while (size)
  {
    
    
    
    
    
    

    
    
    
    

    if FAILED(ds_buf->GetCurrentPosition(&play_cur, 0))
      return false;

    if (play_cur > cur)
      data_size = play_cur - cur;
    else if (play_cur < cur)
      data_size = buf_size + play_cur - cur;
    else
      
      
      
      
      
      data_size = playing? 0: buf_size;

    if (data_size > size)
      data_size = size;

    
    

    if (data_size)
    {
      if FAILED(ds_buf->Lock(cur, data_size, &data1, &data1_bytes, &data2, &data2_bytes, 0))
        return false;

      memcpy(data1, buf, data1_bytes);
      buf += data1_bytes;
      size -= data1_bytes;
      cur += data1_bytes;

      if (data2_bytes)
      {
        memcpy(data2, buf, data2_bytes);
        buf += data2_bytes;
        size -= data2_bytes;
        cur += data2_bytes;
      }

      if FAILED(ds_buf->Unlock(data1, data1_bytes, data2, data2_bytes))
        return false;
    }

    
    

    if (!playing && cur > preload_size)
    {
      if (!paused)
        HRESULT hr = ds_buf->Play(0, 0, DSBPLAY_LOOPING);
      playing = true;
    }

    if (cur >= buf_size)
      cur -= buf_size;

    
    
    
    
    
    
    

    if (size)
    {
      
      
      

      data_size = min(size, buf_size / 2);
      if (WaitForSingleObject(ev_stop, DWORD(data_size * bytes2time * 1000 + 1)) == WAIT_OBJECT_0)
        return true;
      continue;
    }
  }

  if (_chunk->sync)
    time = _chunk->time + _chunk->size * bytes2time;
  else
    time += _chunk->size * bytes2time;

  if (_chunk->eos)
    flush();

  return true;
}

