//
//  SoundManager.cpp
//  Gangster Driveby
//
//  Created by Mike Farrell on 11/14/14.
//  Copyright (c) 2014 Mike Farrell. All rights reserved.
//
#include <cstdlib>
#include <iostream>
#include "PortableSoundInfo.h"
#include "SoundManager.h"
#include "SDL_mixer.h"
static SoundManager *currentSoundManager = nullptr;
static const char *sounds[] = {
  "eng_1_mid_loop.wav",
  "type1.wav",
  "type2.wav",
  "type3.wav",
  "type4.wav",
  "type5.wav",
  "type_space.wav",
  "type_ding.wav",
  "type_feed.wav",
  "tommy.wav",
  "tommy_loop.wav",
  "glassbreak.wav",
  "bulb_break.wav",
  "bullethole_glass.wav",
  "dramatic_boom.wav",
  "paper_in.wav",
  "paper_out.wav",
  "ocean_close.wav",
  "waves_ambience.wav"
};
static const int soundSourceCounts[] = {
  1, 
  8, 8, 8, 8, 8, 8, 
  1, 1, 
  8, 1, 
  8, 8, 8, 
  1, 1, 1, 1, 1 
};
static const char *songs[] = {
/*....*/
};
static const int numSounds = sizeof(sounds)/sizeof(char *);
//static const int numSongs = sizeof(songs)/sizeof(char *);
using namespace std;
SoundManager::SoundManager()
{
  //create the engine
  if(!FAILED(XAudio2Create(&engine)))
  {
    //create the mastering voice
    if(!FAILED(engine->CreateMasteringVoice(&master)))
    {
      //create 3D sound engine
      DWORD dwChannelMask;
      if(!FAILED(engine->GetDeviceDetails(0, &deviceDetails)))
      {
        dwChannelMask = deviceDetails.OutputFormat.dwChannelMask;
        X3DAudioInitialize(dwChannelMask, X3DAUDIO_SPEED_OF_SOUND, engine3d);
        ZeroMemory(&listener, sizeof(listener));    
      }
      else
      {
        cerr << "3D sound init failed!" << endl;
      }
      //and load sound fx
      loadSoundEffects();
    }
  }
  else
  {
    cerr << "Could not initialize DirectSound for sound!" << endl;
  }
  currentSoundManager = this;
}
SoundManager::~SoundManager()
{
  if(engine)
    engine->Release();
  for(Wave *wave : buffers) if(wave)
    delete wave;
  for(MultiSource *ms : sources) if(ms)
    delete ms;
  if(currentSong)
    Mix_FreeMusic(currentSong);
}
SoundManager *SoundManager::manager()
{
  return currentSoundManager;
}
void SoundManager::loadSoundEffects()
{
  auto sz = numSounds;
  for(int i = 0; i < numSounds; i++)
  {
    Wave *buffer = new Wave();
    if(!buffer->load(sounds[i]))
    {
      cerr << "shilt" << endl;
    }
    int maxSources = soundSourceCounts[i];
    MultiSource *source = new MultiSource(engine, buffer, maxSources);
    buffers.push_back(buffer);
    sources.push_back(source);
  }
  //long music placeholder
  sources.push_back(nullptr);
  buffers.push_back(nullptr);
}
void SoundManager::update3D()
{
  for(auto s : sources) if(s)
  {
    s->update3D(this);
  }
}
void SoundManager::loadLongMusicEffect(const char *fn)
{
  if(longMusicBuffer)
    unloadLongMusicEffect();
  PortableSoundInfo sound(fn);
  longMusicBuffer = new Wave();
  if(!longMusicBuffer->load(sound))
  {
    cerr << "long music load failed!" << endl;
  }
  longMusicSource = new MultiSource(engine, longMusicBuffer, 1);
  sources[(int)LongMusic] = longMusicSource;
  buffers[(int)LongMusic] = longMusicBuffer;
}
void SoundManager::unloadLongMusicEffect()
{
  delete longMusicBuffer;
  longMusicBuffer = nullptr;
  delete longMusicSource;
  longMusicSource = nullptr;
}
void SoundManager::setEffectGain(SoundManager::Effect effect, float gain)
{
  const float vol = gain;
  auto source = sources[(int)effect];
  for(auto s : source->sources)
  {
    s->SetVolume(vol);
  }
}
void SoundManager::setEffectBasicProperties(SoundManager::Effect effect, float gain, float pitch, bool looping)
{
  setEffectGain(effect, gain);
  if(pitch != 0.0f)
  {
    setEffectPitch(effect, pitch);
  }
  if(!looping)
  {
    buffers[(int)effect]->xaBuffer()->LoopCount = 0;
  }
  else
  {
    buffers[(int)effect]->xaBuffer()->LoopCount = XAUDIO2_MAX_LOOP_COUNT;
  }
}
void SoundManager::setEffectPitch(SoundManager::Effect effect, float pitch)
{
  auto source = sources[(int)effect];
  for(auto s : source->sources)
  {
    const float ratio = pitch;
    s->SetFrequencyRatio(ratio);
  }
  //mm.. mmmm, pitch!
  source->basePitch = pitch;
}
void SoundManager::setEffectSpatialProperties(SoundManager::Effect effect, float referenceDistance, float maxDistance, float rollOffFactor,
  float3 velocity, float3 direction)
{
  //stereo tracks get a horrible sound blasting result when you try to do this
  //lets just do what openAL does, which is silently fail
  if(buffers[(int)effect]->wf()->nChannels > 1)
  {
    return;
  }
  //not reversible for now
  auto source = sources[(int)effect];
  source->is3D = true;
  source->emitter.Velocity = { velocity.x, velocity.y, velocity.z };
  if(direction != float3::zero)
  {
    source->emitter.OrientFront = { direction.x, direction.y, direction.z };
    source->emitter.OrientTop = listener.OrientTop;
  }
  //a "max distance" isn't available in x3d audio it seems..
  float distanceScalar = referenceDistance - rollOffFactor*2.5;
  source->emitter.CurveDistanceScaler = max(0.0f, distanceScalar);
}
void SoundManager::setEffectPosition(Effect effect, float3 pos)
{
  auto source = sources[(int)effect];
  source->emitter.Position = { pos.x, pos.y, pos.z };
}
void SoundManager::setListenerPosition(float3 pos, float3 orientation[])
{
  listener.Position = { pos.x, pos.y, pos.z };
  listener.OrientFront = { orientation[0].x, orientation[0].y, orientation[0].z };
  listener.OrientTop = { orientation[1].x, orientation[1].y, orientation[1].z };
}
void SoundManager::playEffect(SoundManager::Effect effect)
{
  auto ms = sources[(int)effect];
  auto source = ms->getSource();
  //ensure initial 3D calculations done if requested
  if(ms->is3D && !ms->is3DUpdated)
  {
    ms->update3D(this);
  }
  source->Start();
  source->SubmitSourceBuffer(buffers[(int)effect]->xaBuffer());
}
void SoundManager::stopEffect(SoundManager::Effect effect)
{
  auto source = sources[(int)effect];
  for(auto s : source->sources)
  {
    s->Stop();
  }
}
void SoundManager::playSong(SoundManager::Music song)
{
  if(soundAvailable)
  {
    if(currentSong)
      Mix_FreeMusic(currentSong);
    currentSong = Mix_LoadMUS(songs[(int)song]);
    Mix_PlayMusic(currentSong, -1);
  }
}
void SoundManager::stopSong()
{
  if(soundAvailable)
  {
    Mix_HaltMusic();
  }
}
void SoundManager::setSongVolume(float volume)
{
  currentSongVolume = volume;
  if(soundAvailable)
  {
    Mix_VolumeMusic((int)roundf(volume*MIX_MAX_VOLUME));
  }
}
void SoundManager::setSongPosition(double pos)
{
  if(soundAvailable)
  {
    Mix_SetMusicPosition(pos);
  }
}
float SoundManager::getSongVolume()
{
  return currentSongVolume;
}
IXAudio2SourceVoice *SoundManager::MultiSource::getSource()
{
  auto isPlaying = [](IXAudio2SourceVoice *source)->bool {
    XAUDIO2_VOICE_STATE state;
    ZeroMemory(&state, sizeof(XAUDIO2_VOICE_STATE));
    source->GetState(&state);
    return (state.BuffersQueued != 0);
  };
  int i = 0;
  while(i < sources.size() && isPlaying(sources[i]))
  {
    i++;
  }
  if(i == sources.size())
  {
    if(sources.size() > 1)
    {
#ifdef DEBUG
      cout << "Warning:  sound queue will occur (this will suck)" << endl;
#endif
    }
    return sources[0];
  }
  return sources[i];
}
void SoundManager::MultiSource::update3D(SoundManager *sm)
{
  if(is3D)
  {
    //X3DAUDIO_CALCULATE_LPF_DIRECT ?
    X3DAudioCalculate(sm->engine3d, &sm->listener, &emitter, X3DAUDIO_CALCULATE_MATRIX | X3DAUDIO_CALCULATE_DOPPLER, &dspSettings);
    for(auto s : sources)
    {
      s->SetOutputMatrix(sm->master, 1, sm->deviceDetails.OutputFormat.Format.nChannels, dspSettings.pMatrixCoefficients);
      s->SetFrequencyRatio(basePitch * dspSettings.DopplerFactor);
    }
    is3DUpdated = true;
  }
}