/*=========================================================================

  Copyright (c) Kitware, Inc.
  All rights reserved.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/

#include "mvAudioHandler.h"
#include "vtkFFMPEGVideoSource.h"
#include "vtkMath.h"
#include "vtkMutexLock.h"

#include <mmdeviceapi.h>
#include <audiopolicy.h>

void mvAudioHandler::SetVolumeLevel(double volume)
{
  IMMDeviceEnumerator *pEnumerator = NULL;
  IMMDevice *pDevice = NULL;

  HRESULT _hrStatus = S_OK;
  IAudioSessionManager *_pManager = nullptr;

  // Get the enumerator for the audio endpoint devices
  // on this system.
  CoCreateInstance(__uuidof(MMDeviceEnumerator),
                  NULL, CLSCTX_INPROC_SERVER,
                    __uuidof(IMMDeviceEnumerator),
                    (void**)&pEnumerator);

  // Get the audio endpoint device with the specified data-flow
  // direction (eRender or eCapture) and device role.
  pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pDevice);
  pEnumerator->Release();
  pEnumerator = nullptr;

  // Get the session manager for the endpoint device.
  _hrStatus = pDevice->Activate(__uuidof(IAudioSessionManager),
                                CLSCTX_INPROC_SERVER, NULL,
                                (void**)&_pManager);
  pDevice->Release();
  pDevice = nullptr;

  ISimpleAudioVolume *sav;
  _pManager->GetSimpleAudioVolume(nullptr, true, &sav);
  _pManager->Release();
  _pManager = nullptr;

  sav->SetMasterVolume(volume, nullptr);
  sav->Release();
}

struct StreamingVoiceContext : public IXAudio2VoiceCallback
{
    HANDLE hBufferEndEvent;
    StreamingVoiceContext(): hBufferEndEvent( CreateEvent( NULL, FALSE, FALSE, NULL ) ){}
    ~StreamingVoiceContext(){ CloseHandle( hBufferEndEvent ); }
    void OnBufferEnd( void* ){ SetEvent( hBufferEndEvent ); }

    //Unused methods are stubs
    void OnVoiceProcessingPassEnd() { }
    void OnVoiceProcessingPassStart(UINT32) {    }
    void OnBufferStart(void *) {    }
    void OnLoopEnd(void *) {    }
    void OnVoiceError(void *, HRESULT ) { }
    void OnStreamEnd() { }
};

mvAudioHandler::mvAudioHandler()
{
  this->Context = new StreamingVoiceContext;
  this->MasterVoice = nullptr;
  this->SourceVoice = nullptr;
  this->CurrentBufferIndex = 0;

  this->MaximumBufferCount = 0;
  this->MaximumBufferSize = 0;

  HRESULT hr;
  if ( FAILED(hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED) ) )
  {
    cerr << "failed to initialize thread model\n";
  }

  if ( FAILED(hr = XAudio2Create( &this->XAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR ) ) )
  {
    cerr << "failed to create audio path\n";
  }

  if ( FAILED(hr = this->XAudio2->CreateMasteringVoice( &this->MasterVoice ) ) )
  {
    cerr << "failed to create mastering voice\n";
  }

  DWORD dwChannelMask;
  this->MasterVoice->GetChannelMask( &dwChannelMask );
  X3DAudioInitialize( dwChannelMask, X3DAUDIO_SPEED_OF_SOUND, this->X3DInstance );

  this->DSPSettings.DstChannelCount = 2;

  this->Listener.OrientFront = {1.0, 0.0, 0.0};
  this->Listener.OrientTop = {0.0, 0.0, -1.0};
  this->Listener.Position = {0.0, 0.0, 0.0};
  this->Listener.Velocity = {0.0, 0.0, 0.0};
  this->Listener.pCone = nullptr;

  this->Emitter.OrientFront = {1.0, 0.0, 0.0};
  this->Emitter.OrientTop = {0.0, 0.0, -1.0};
  this->Emitter.Position = {0.0, 0.0, 0.0};
  this->Emitter.Velocity = {343.0, 343.0, 343.0};
  this->Emitter.ChannelRadius = 2.0;

  // XAUDIO2_DEBUG_CONFIGURATION debugc;
  // debugc.TraceMask = XAUDIO2_LOG_ERRORS;
  // debugc.BreakMask = XAUDIO2_LOG_ERRORS;
  // this->XAudio2->SetDebugConfiguration(&debugc, nullptr);
}

mvAudioHandler::~mvAudioHandler()
{
  this->XAudio2->Release();
  this->SourceVoice = nullptr;
  delete this->Context;
}

namespace
{
  template <typename dtype>
  void copyRealData(
    BYTE *dest, dtype **inp,
    int numChannels, int numSamples,
    bool packed)
  {
    float *fdest = reinterpret_cast<float *>(dest);
    if (packed)
    {
      dtype *iptr = inp[0];
      for (int i = 0; i < numChannels*numSamples; ++i)
      {
        fdest[i] = iptr[i];
      }
    }
    else
    {
      for (int c = 0; c< numChannels; ++c)
      {
        float *cdest = fdest + c;
        dtype *iptr = inp[c];
        for (int i = 0; i < numSamples; ++i)
        {
          *cdest = iptr[i];
          cdest += numChannels;
        }
      }
    }
  }
}

void mvAudioHandler::SetAudioPose(double *up, double *front)
{
  this->AudioFront[0] = front[0];
  this->AudioFront[1] = front[1];
  this->AudioFront[2] = front[2];
  vtkMath::Normalize(this->AudioFront);

  this->AudioUp[0] = up[0];
  this->AudioUp[1] = up[1];
  this->AudioUp[2] = up[2];
  vtkMath::Normalize(this->AudioUp);

  vtkMath::Cross(this->AudioFront, this->AudioUp, this->AudioRight);
}

void mvAudioHandler::SetListenerPose(double *up, double *front)
{
  // convert to the audio streams coordinate system
  double mat[3][3];
  mat[0][0] = this->AudioFront[0];
  mat[0][1] = this->AudioFront[1];
  mat[0][2] = this->AudioFront[2];
  mat[1][0] = -this->AudioRight[0];
  mat[1][1] = -this->AudioRight[1];
  mat[1][2] = -this->AudioRight[2];
  mat[2][0] = this->AudioUp[0];
  mat[2][1] = this->AudioUp[1];
  mat[2][2] = this->AudioUp[2];

  double aup[3];
  double afront[3];

  vtkMath::Multiply3x3(mat, up, aup);
  vtkMath::Multiply3x3(mat, front, afront);

  this->ListenerUpdateMutex->Lock();
  this->Listener.OrientFront.x = afront[0];
  this->Listener.OrientFront.y = afront[1];
  this->Listener.OrientFront.z = -1.0*afront[2];
  this->Listener.OrientTop.x = aup[0];
  this->Listener.OrientTop.y = aup[1];
  this->Listener.OrientTop.z = -1.0*aup[2];
  this->ListenerUpdateMutex->Unlock();
}

void mvAudioHandler::UpdatePositions()
{
  XAUDIO2_FILTER_PARAMETERS FilterParameters =
    { LowPassFilter, 2.0f * sinf(X3DAUDIO_PI/6.0f * this->DSPSettings.LPFDirectCoefficient), 1.0f };

  this->ListenerUpdateMutex->Lock();

  X3DAudioCalculate(this->X3DInstance, &this->Listener, &this->Emitter,
    X3DAUDIO_CALCULATE_MATRIX | X3DAUDIO_CALCULATE_DOPPLER | X3DAUDIO_CALCULATE_LPF_DIRECT | X3DAUDIO_CALCULATE_REVERB,
    &this->DSPSettings );

  this->ListenerUpdateMutex->Unlock();

  this->SourceVoice->SetOutputMatrix( this->MasterVoice, this->Emitter.ChannelCount,
    this->NumberOfOutputChannels, this->DSPSettings.pMatrixCoefficients ) ;
  this->SourceVoice->SetFrequencyRatio(this->DSPSettings.DopplerFactor);
  this->SourceVoice->SetFilterParameters(&FilterParameters);

}

void mvAudioHandler::SetupAudioPlayback(vtkFFMPEGVideoSource *video)
{
  // clear prior voice
  if (this->SourceVoice)
  {
    this->SourceVoice->Stop( 0, 0 );
    this->SourceVoice = nullptr;
  }

  video->SetAudioCallback(
    std::bind(&mvAudioHandler::AudioCallback, this, std::placeholders::_1),
    nullptr);
}

void mvAudioHandler::AudioCallback(vtkFFMPEGVideoSourceAudioCallbackData const &acbd)
{
  HRESULT hr;

  int destBytesPerSample = 2;
  if (acbd.DataType == VTK_FLOAT || acbd.DataType == VTK_DOUBLE)
  {
    destBytesPerSample = 4;
  }

  // do we need to setup the voice?
  if (!this->SourceVoice)
  {
    // always setup for 16 bit PCM
    WAVEFORMATEXTENSIBLE wfx = {0};
    if (acbd.DataType == VTK_FLOAT || acbd.DataType == VTK_DOUBLE)
    {
      wfx.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
    }
    else
    {
      wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
    }

    // we assume 4 channel audio is ambisonic b format and create 6 emitters
    // for a hexagon decode
    this->SphericalAudio = false;
    int destChannels = acbd.NumberOfChannels;
    if (acbd.NumberOfChannels == 4)
    {
      this->SphericalAudio = true;
      destChannels = 6;
    }
    wfx.Format.nChannels = destChannels;
    wfx.Format.nSamplesPerSec = acbd.SampleRate;
    wfx.Format.wBitsPerSample = 8*destBytesPerSample;
    wfx.Format.nBlockAlign = destChannels*destBytesPerSample;
    wfx.Format.nAvgBytesPerSec = acbd.SampleRate*wfx.Format.nBlockAlign;
    wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample;
    wfx.Samples.wSamplesPerBlock = acbd.NumberOfSamples;
    if( FAILED(hr = this->XAudio2->CreateSourceVoice( &this->SourceVoice, (WAVEFORMATEX*)&wfx,
       0, XAUDIO2_DEFAULT_FREQ_RATIO, this->Context, NULL, NULL ) ) )
    {
      return;
    }
    if (this->AudioBufferSize < wfx.Format.nBlockAlign*acbd.NumberOfSamples)
    {
      cerr << "buffer too small for audio data "
        << acbd.NumberOfSamples << " samples and "
        << wfx.Format.nBlockAlign << " alignment\n";
    }

    this->MaximumBufferSize = wfx.Format.nBlockAlign*acbd.NumberOfSamples;
    // for changing audio rates make sure our max is reasonable (not too small)
    if (this->MaximumBufferSize < 32768)
    {
      this->MaximumBufferSize = 32768;
    }
    this->MaximumBufferCount = this->AudioBufferSize/this->MaximumBufferSize;

    // setup emitter struct
    this->Emitter.CurveDistanceScaler = FLT_MIN;

    this->Emitter.ChannelCount = destChannels;
    this->Emitter.pChannelAzimuths =
      new FLOAT32[this->Emitter.ChannelCount];

    // we assume 4 channel audio is ambisonic b format and create 6 emitters
    // for a hexagon decode
    if (this->SphericalAudio)
    {
      this->Emitter.pChannelAzimuths[5] =  -3.1416/3.0;
      this->Emitter.pChannelAzimuths[4] =  -2*3.1416/3.0;
      this->Emitter.pChannelAzimuths[3] =  3.1416;
      this->Emitter.pChannelAzimuths[2] =  2*3.1416/3.0;
      this->Emitter.pChannelAzimuths[1] =  3.1416/3.0;
      this->Emitter.pChannelAzimuths[0] =  0.0;
    }
    else
    {
      switch (this->Emitter.ChannelCount)
      {
        case 1:
          this->Emitter.pChannelAzimuths[0] = 0.0;
          break;
        case 8:
          this->Emitter.pChannelAzimuths[7] =  0.5*3.1416;
          this->Emitter.pChannelAzimuths[6] =  -0.5*3.1416;
          this->Emitter.pChannelAzimuths[5] =  0.8*3.1416;
          this->Emitter.pChannelAzimuths[4] =  -0.8*3.1416;
          this->Emitter.pChannelAzimuths[3] =  0.0;
          this->Emitter.pChannelAzimuths[2] =  0.0;
          this->Emitter.pChannelAzimuths[1] =  0.2*3.1416;
          this->Emitter.pChannelAzimuths[0] = -0.2*3.1416;
          break;
        case 6:
          this->Emitter.pChannelAzimuths[5] =  0.8*3.1416;
          this->Emitter.pChannelAzimuths[4] =  -0.8*3.1416;
          this->Emitter.pChannelAzimuths[3] =  0.0;
          this->Emitter.pChannelAzimuths[2] =  0.0;
          this->Emitter.pChannelAzimuths[1] =  0.4*3.1416;
          this->Emitter.pChannelAzimuths[0] = -0.4*3.1416;
          break;
        case 3:
          this->Emitter.pChannelAzimuths[2] = 0.0;
        case 2:
          this->Emitter.pChannelAzimuths[1] = 0.5*3.1416;
          this->Emitter.pChannelAzimuths[0] = -0.5*3.1416;
          break;
      }
    }

    this->DSPSettings.SrcChannelCount = this->Emitter.ChannelCount;
    this->DSPSettings.pMatrixCoefficients = new FLOAT32 [this->NumberOfOutputChannels*this->Emitter.ChannelCount];

    hr = this->SourceVoice->Start( 0, 0 );
  }

  this->UpdatePositions();

  if (this->MaximumBufferSize < static_cast<int>(
      destBytesPerSample*acbd.NumberOfSamples*this->Emitter.ChannelCount))
  {
    cerr << "buffer too small for new audio data with "
      << this->MaximumBufferSize << " buffer size and "
      << acbd.NumberOfSamples << " samples and "
      << this->Emitter.ChannelCount << " channels\n";
  }

  XAUDIO2_VOICE_STATE state;
  while( this->SourceVoice->GetState( &state ),
        static_cast<int>(state.BuffersQueued) >= this->MaximumBufferCount - 1)
  {
    cerr << "audio blocked waiting\n";
    WaitForSingleObject( this->Context->hBufferEndEvent, INFINITE );
  }

  // copy the incoming data to a buffer
  BYTE *dptr = this->AudioBuffer + this->MaximumBufferSize*this->CurrentBufferIndex;

  // if spherical do the conversion
  if (this->SphericalAudio)
  {
    // convert
    if (acbd.DataType == VTK_FLOAT)
    {
      float **inp = reinterpret_cast<float **>(acbd.Data);
      float *fdest = reinterpret_cast<float *>(dptr);
      float inValues[4];
      for (int i = 0; i < acbd.NumberOfSamples; ++i)
      {
        // get input values
        if (acbd.Packed)
        {
          float *inl = inp[0] + i*4;
          inValues[0] = *inl++;
          inValues[1] = *inl++;
          inValues[2] = *inl++;
          inValues[3] = *inl++;
        }
        else
        {
          inValues[0] = *(inp[0] + i);
          inValues[1] = *(inp[1] + i);
          inValues[2] = *(inp[2] + i);
          inValues[3] = *(inp[3] + i);
        }

        // write output values
        // the normalization factors depend on n3d versus sn3d versus maxN etc
        // I have no clue what should be used here but see
        // https://en.wikipedia.org/wiki/Ambisonic_data_exchange_formats
        // for more background
        *fdest++ = inValues[0] + inValues[3];
        *fdest++ = inValues[0] - 0.866*inValues[1] + 0.5*inValues[3];
        *fdest++ = inValues[0] - 0.866*inValues[1] - 0.5*inValues[3];
        *fdest++ = inValues[0] - inValues[3];
        *fdest++ = inValues[0] + 0.866*inValues[1] - 0.5*inValues[3];
        *fdest++ = inValues[0] + 0.866*inValues[1] + 0.5*inValues[3];
      }
    }
    else
    {
      cerr << "Error aspatial audio is data type is " << acbd.DataType << "\n";
    }
  }
  else
  {
    // no copy needed
    if (acbd.DataType == VTK_SHORT && acbd.Packed)
    {
      dptr = reinterpret_cast<BYTE *>(acbd.Data[0]);
    }
    if (acbd.DataType == VTK_FLOAT && acbd.Packed)
    {
      dptr = reinterpret_cast<BYTE *>(acbd.Data[0]);
    }

    // copy required
    if (acbd.DataType == VTK_FLOAT && !acbd.Packed)
    {
      copyRealData(dptr, reinterpret_cast<float **>(acbd.Data),
        acbd.NumberOfChannels,
        acbd.NumberOfSamples,
        acbd.Packed);
    }
    if (acbd.DataType == VTK_DOUBLE)
    {
      copyRealData(dptr, reinterpret_cast<double **>(acbd.Data),
        acbd.NumberOfChannels,
        acbd.NumberOfSamples,
        acbd.Packed);
    }
  }

  XAUDIO2_BUFFER buf = {0};
  buf.AudioBytes = acbd.NumberOfSamples*destBytesPerSample*this->Emitter.ChannelCount;
  buf.pAudioData = reinterpret_cast<BYTE *>(dptr);
  if( acbd.Caller->GetEndOfFile() )
  {
    buf.Flags = XAUDIO2_END_OF_STREAM;
  }
  this->SourceVoice->SubmitSourceBuffer( &buf );
  this->CurrentBufferIndex++;
  if (this->CurrentBufferIndex == this->MaximumBufferCount)
  {
    this->CurrentBufferIndex = 0;
  }

  // flush last buffers?
  // XAUDIO2_VOICE_STATE state;
  // while( pSourceVoice->GetState( &state ), state.BuffersQueued > 0 )
  // {
  //   WaitForSingleObjectEx( aContext.hBufferEndEvent, INFINITE, TRUE );
  // }

};
