#include "pch.h"
#include "sf_memory.h"
#include "SoundDriver.h"
using namespace std;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Devices::Enumeration;
using namespace Windows::Media::Devices;
using namespace Concurrency;
using namespace Platform;
#pragma comment(lib, "mmdevapi.lib")

using namespace std::placeholders;
using namespace Microsoft::WRL;

namespace {
  void makeWaveFormat(WAVEFORMATEXTENSIBLE& format,
    int sample_rate = 44100,int channels = 2,int bits_per_sample = 32,int valid_bits_per_sample = 32,
    uint32_t type = WAVE_FORMAT_EXTENSIBLE,
    const GUID& sub_type = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)
  {
    ZeroMemory(&format,sizeof(WAVEFORMATEXTENSIBLE));
    format.Format.wFormatTag = type;
    format.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
    format.SubFormat = sub_type;
    format.Format.nSamplesPerSec = sample_rate;
    format.Format.nChannels = channels;
    format.Format.wBitsPerSample = bits_per_sample;
    format.Format.nBlockAlign = (format.Format.wBitsPerSample / 8) * format.Format.nChannels;
    format.Format.nAvgBytesPerSec = format.Format.nSamplesPerSec  * format.Format.nBlockAlign;
    format.Samples.wValidBitsPerSample = valid_bits_per_sample;
    format.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
  }

  void makeWaveFormat(WAVEFORMATEX& format,int sample_rate = 44100,int channels = 2,int bits = 16,uint32_t type = WAVE_FORMAT_PCM)
  {
    ZeroMemory(&format,sizeof(WAVEFORMATEX));
    format.wFormatTag = type;
    format.nSamplesPerSec = sample_rate;
    format.nChannels = channels;
    format.wBitsPerSample = bits;
    format.nBlockAlign = (format.wBitsPerSample / 8) * format.nChannels;
    format.nAvgBytesPerSec = format.nSamplesPerSec  * format.nBlockAlign;
  };
}

namespace sf {

  struct ActivateAudioInterfaceCompletionHandler 
    : public  RuntimeClass< RuntimeClassFlags< ClassicCom >, FtmBase, IActivateAudioInterfaceCompletionHandler >
  {
    ActivateAudioInterfaceCompletionHandler() :
      hr_(S_OK),
      ptr_(nullptr),
      eventHolder_(::CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE)) {};

    ~ActivateAudioInterfaceCompletionHandler(){};

    // IActivateAudioInterfaceCompletionHandler
    STDMETHOD(ActivateCompleted)( IActivateAudioInterfaceAsyncOperation *operation )
    {
      HRESULT hr = S_OK;
      HRESULT hrActivateResult = S_OK;
      IUnknown *punkAudioInterface = nullptr;
      // Check for a successful activation result
      hr = operation->GetActivateResult( &hrActivateResult, &punkAudioInterface );
      if(SUCCEEDED(hr)){
        if (SUCCEEDED( hrActivateResult ))
        {
          hr_ = S_OK;
          // Get the pointer for the Audio Client
          punkAudioInterface->QueryInterface( IID_PPV_ARGS(&ptr_) );
          if( nullptr == ptr_ )
          {
            hr_ = E_FAIL;
            ::SetEvent(eventHolder_.get());
            return hr_;
          }
          ::SetEvent(eventHolder_.get());
        } else {
          hr_ = hr = hrActivateResult;
          ::SetEvent(eventHolder_.get());
        }
      } else {
        hr_ = hr;
        ::SetEvent(eventHolder_.get());
      }
      return hr;
      //operation
      //    concurrency::wait(0);
    }

    void wait()
    {
      ::WaitForSingleObjectEx(eventHolder_.get(),INFINITE,FALSE);
    }

    HRESULT ResultCode() {return hr_;}
    IAudioClient2Ptr AudioClient(){return ptr_;}
  private:
    HRESULT hr_;
    sf::handle_holder eventHolder_;
    IAudioClient2Ptr ptr_;
  };

  struct SoundDriver::impl 
    //    : public RuntimeClass< RuntimeClassFlags< ClassicCom >, FtmBase, IActivateAudioInterfaceCompletionHandler > 
  {

    impl() : bufferIndex_(0),eventHolder_(nullptr),processBuffer_(std::bind(&SoundDriver::impl::DefaultProcessBuffer,this,_1,_2)),isStart_(false)
    {
      //::AvMMAvSetMmThreadCharacteristics
      String^ deviceID = MediaDevice::GetDefaultAudioRenderId(AudioDeviceRole::Default);

      {
        ComPtr<IActivateAudioInterfaceAsyncOperation> asyncOpPtr;
        IActivateAudioInterfaceAsyncOperation* asyncOp;
        ComPtr<ActivateAudioInterfaceCompletionHandler> handler(Make<ActivateAudioInterfaceCompletionHandler>());
        HRESULT hr = ActivateAudioInterfaceAsync(deviceID->Data(),__uuidof(IAudioClient2),nullptr,handler.Get(),&asyncOp);
        asyncOpPtr.Attach(asyncOp);
        if(SUCCEEDED(hr)){
          handler->wait();
          if(handler->ResultCode() == S_OK)
          {
            audioClient_ = handler->AudioClient();
          } else {
            throw AudioClientNullException(L"既定のAudioClientをアクティベートできませんでした。");
          }
        } else {
          throw AudioClientNullException(L"既定のAudioClientをアクティベートできませんでした。");
        }
      }
      // オーディオフォーマットのチェック
      makeWaveFormat(format_);
      //sf::co_task_memory<WAVEFORMATEXTENSIBLE> mix_format;
      //audioClient_->GetMixFormat(reinterpret_cast<WAVEFORMATEX**>(&mix_format));
      // format_ = *mix_format.get();

      sf::co_task_memory<WAVEFORMATEXTENSIBLE>  alt_format;
      HRESULT hr = audioClient_->IsFormatSupported(
        AUDCLNT_SHAREMODE_SHARED,
        reinterpret_cast<WAVEFORMATEX*>(&format_),
        reinterpret_cast<WAVEFORMATEX**>(&alt_format));
      int sample_rate_backup = 0;
      if(hr == S_FALSE) // FALSEの時はおそらくサンプリングレートが違う
      {
        // サンプリングレートのみが異なるかチェックする
        if(	alt_format->Format.wFormatTag == format_.Format.wFormatTag &&
          alt_format->Format.nChannels == format_.Format.nChannels &&
          alt_format->Format.wBitsPerSample == format_.Format.wBitsPerSample &&
          alt_format->Samples.wValidBitsPerSample == format_.Samples.wValidBitsPerSample && 
          alt_format->Format.nSamplesPerSec != format_.Format.nSamplesPerSec
          )
        {
          sample_rate_backup = format_.Format.nSamplesPerSec;
          // 代替フォーマットのサンプルレートをセットする
          format_.Format.nSamplesPerSec = alt_format->Format.nSamplesPerSec;
          // 再計算する
          format_.Format.nAvgBytesPerSec = alt_format->Format.nSamplesPerSec * format_.Format.nBlockAlign;
          // もう一回チェックする。
          // サンプルレート以外でサポートしていない設定があれば例外が発生する。
          alt_format.reset();
          ThrowIfErr(audioClient_->IsFormatSupported(
            AUDCLNT_SHAREMODE_SHARED,
            reinterpret_cast<WAVEFORMATEX*>(&format_),
            reinterpret_cast<WAVEFORMATEX**>(&alt_format)));
        } else {
          throw AudioFormatNotSupportedException(L"このオーディオデバイスで設定しているフォーマットは対応していません。");
        }
      }

      // Audio Clientの初期化

      REFERENCE_TIME defaultDevicePeriod,minDevicePriod;
      audioClient_->GetDevicePeriod(&defaultDevicePeriod,&minDevicePriod);
      hr = audioClient_->Initialize(
        AUDCLNT_SHAREMODE_SHARED,
        AUDCLNT_STREAMFLAGS_NOPERSIST | 
        AUDCLNT_STREAMFLAGS_EVENTCALLBACK ,
        minDevicePriod,0,
        reinterpret_cast<WAVEFORMATEX*>(&format_),NULL);
      if(hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED)
      {
        ThrowIfErr(audioClient_->GetBufferSize(&bufferSize_));
        latency_ = (double)10000000 * (bufferSize_) / format_.Format.nSamplesPerSec;
        audioClient_.Reset();

        ComPtr<IActivateAudioInterfaceAsyncOperation> asyncOp;
        ActivateAudioInterfaceCompletionHandler handler;
        HRESULT hr = ActivateAudioInterfaceAsync(deviceID->Data(),__uuidof(IAudioClient2),nullptr,&handler,&asyncOp);
        if(SUCCEEDED(hr)){
          handler.wait();
          if(handler.ResultCode() == S_OK)
          {
            audioClient_ = handler.AudioClient();
          } else {
            throw AudioClientNullException(L"既定のAudioClientをアクティベートできませんでした。");
          }
        } else {
          throw AudioClientNullException(L"既定のAudioClientをアクティベートできませんでした。");
        }

        ThrowIfErr(audioClient_->Initialize(
          AUDCLNT_SHAREMODE_SHARED,
          AUDCLNT_STREAMFLAGS_NOPERSIST | 
          AUDCLNT_STREAMFLAGS_EVENTCALLBACK ,
          latency_,0,
          reinterpret_cast<WAVEFORMATEX*>(&format_),NULL));
      } else {
        ThrowIfErr(hr);
      }

      ThrowIfErr(audioClient_->GetBufferSize(&bufferSize_));

      buffer_[0].reset((float*)_aligned_malloc( bufferSize_ * format_.Format.nBlockAlign,16));    
      buffer_[1].reset((float*)_aligned_malloc( bufferSize_ * format_.Format.nBlockAlign,16));    
      bufferByteCount_ = bufferSize_ * format_.Format.nBlockAlign;

      audioClient_->GetStreamLatency(&latency_);
      ThrowIfErr(audioClient_->GetService(__uuidof(IAudioRenderClient),&audioRenderClient_));
      eventHolder_.reset(::CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE));
      audioClient_->SetEventHandle(eventHolder_.get());

      processBuffer_(buffer_[bufferIndex_],bufferSize_);
      BYTE* rawBuffer;
      ThrowIfErr(audioRenderClient_->GetBuffer(bufferSize_,&rawBuffer));
      audioRenderClient_->ReleaseBuffer(bufferSize_,0);
      bufferIndex_ = (bufferIndex_ + 1) & 0x1;
      processBuffer_(buffer_[bufferIndex_],bufferSize_);

      Start();

      //     ThrowIfErr(ActivateAudioInterfaceAsync(deviceID->Data(),__uuidof(IAudioClient2),nullptr,this,&asyncOp));

    }

    ~impl(){
      audioClient_->Stop();
      audioRenderClient_.Reset();
      audioClient_->Reset();
    };

    void Render()
    {
      ::WaitForSingleObjectEx(eventHolder_.get(),INFINITE,FALSE);
      uint32_t padding;
      ThrowIfErr(audioClient_->GetCurrentPadding(&padding));
      if(!padding)
      {
        BYTE* rawBuffer;
        ThrowIfErr(audioRenderClient_->GetBuffer(bufferSize_,&rawBuffer));
        ::memcpy(rawBuffer,buffer_[bufferIndex_].get(),bufferByteCount_);
        audioRenderClient_->ReleaseBuffer(bufferSize_,0);
        bufferIndex_ = (bufferIndex_ + 1) & 0x1;
        processBuffer_(buffer_[bufferIndex_],bufferSize_);
      }
    }

    void SetProcessBufferFunc(ProcessBufferType&& v)
    {
      Stop();
      processBuffer_ = std::move(v);
      Start();
    }
    void ResetProcessBufferFunc()
    {
      Stop();
      processBuffer_ = std::move(std::bind(&SoundDriver::impl::DefaultProcessBuffer,this,_1,_2));
      Start();
    }

    void Start()
    {
      if(isStart_) return;

      ThrowIfErr(audioClient_->Start());

      isStart_ = true;
    }

    void Stop()
    {
      if(!isStart_) return;

      ThrowIfErr(audioClient_->Stop());

      isStart_ = false;
    }

    WAVEFORMATEXTENSIBLE& Format()
    {
      return format_;
    }

    void DefaultProcessBuffer(boost::shared_array<float> arr,int bufferSize)
    {
      // 今のところ出力はステレオ前提
      //for(int j = 0;j < voices_.size();++j)
      //{
      //  voices_[j].Process(buffer);
      //  *ptr +=  buffer[0]; 
      //  *(ptr + 1) += buffer[1]; 
      //}
      float *ptr = arr.get();

      ZeroMemory(ptr,bufferByteCount_);

      //for(int i = 0;i < bufferSize;++i)
      //{
      //  sequencer_->Process();
      //  synth_->Process(ptr);
      //  ptr += 2;
      //}
    }

    // IActivateAudioInterfaceCompletionHandler
    STDMETHOD(ActivateCompleted)( IActivateAudioInterfaceAsyncOperation *operation )
    {
      try {
        if(!audioClient_)
        {
          throw AudioClientNullException(L"既定のAudioClientをアクティベートできませんでした。");
        }


      } catch (...) 
      {
        return E_FAIL;
      }
      return S_OK;
    }

  private:
    ProcessBufferType processBuffer_;
    IAudioClient2Ptr audioClient_;
    uint32_t bufferSize_;
    REFERENCE_TIME latency_;
    //	IAudioClockAdjustmentPtr audioClockAdjustment_;
    IAudioRenderClientPtr audioRenderClient_;
    WAVEFORMATEXTENSIBLE format_;
    sf::handle_holder eventHolder_;
    boost::shared_array<float> buffer_[2];
    int bufferIndex_;
    int bufferByteCount_;
    bool isStart_;

  };

  SoundDriver::SoundDriver() : impl_(new impl()) {}

  SoundDriver::~SoundDriver()
  {
    impl_.reset();
  }

  void SoundDriver::Render()
  {
    impl_->Render();
  }

  void SoundDriver::SetProcessBufferFunc(ProcessBufferType&& v)
  {
    impl_->SetProcessBufferFunc(std::move(v));
  }

  void SoundDriver::ResetProcessBufferFunc()
  {
    impl_->ResetProcessBufferFunc();
  }

  void SoundDriver::Start()
  {
    impl_->Start();
  }

  void SoundDriver::Stop()
  {
    impl_->Stop();
  }

  WAVEFORMATEXTENSIBLE& SoundDriver::Format()
  {
    return impl_->Format();
  }

}
