/*!
  Sss - a slope soaring simulater.
  Copyright (C) 2002 Danny Chapman - flight@rowlhouse.freeserve.co.uk
  
  \file audio.cpp
*/
#include "audio.h"
#include "sss_assert.h"
#include "object.h"
#include "log_trace.h"
#include "sss.h"
#include "config.h"
#include <iostream>
using namespace std;

Audio * Audio::m_instance = 0;

#ifdef WITH_FMOD

#include <fmod.h>
#include <fmod_errors.h>

#ifdef _WIN32
#include <conio.h>
#include <windows.h>
#else
#include <wincompat.h>
#endif

Audio::Audio()
{
  TRACE_METHOD_ONLY(1);
  
  m_enable = Sss::instance()->config().use_audio;
  if (!m_enable)
    return;
  
	if (FSOUND_GetVersion() < FMOD_VERSION)
	{
		TRACE("Error : You are using the wrong FMOD DLL version!  You should be using FMOD %.02f\n", FMOD_VERSION);
    assert1(!"Error");
	}
  
// let fmod choose
//#ifdef _WIN32
//  FSOUND_SetOutput(FSOUND_OUTPUT_DSOUND);
//#else
//  FSOUND_SetOutput(FSOUND_OUTPUT_OSS);
//#endif
  
//    TRACE("Drivers for FMOD (using the first):\n");
//    //choose driver
//    int i;
//  	for (i=0; i < FSOUND_GetNumDrivers(); i++) 
//  	{
//  		TRACE("%d - %s\n", i+1, FSOUND_GetDriverName(i));	// print driver names
//  		{
//  			unsigned int caps = 0;
  
//  			FSOUND_GetDriverCaps(i, &caps);
  
//  			if (caps & FSOUND_CAPS_HARDWARE)
//  				TRACE("  * Driver supports hardware 3D sound!\n");
//  			if (caps & FSOUND_CAPS_EAX2)
//  				TRACE("  * Driver supports EAX 2 reverb!\n");
//  			if (caps & FSOUND_CAPS_EAX3)
//  				TRACE("  * Driver supports EAX 3 reverb!\n");
//  		}
//  	}
  
//  	FSOUND_SetDriver(0);					// Select sound card (0 = default)
  
  // initialise
  
//    if (!FSOUND_SetBufferSize(100))
//    {
//  		TRACE("FSOUND buffer size: %s\n", FMOD_ErrorString(FSOUND_GetError()));
//      assert1(!"Error");    
//  	}
  
	if (!FSOUND_Init(44100, 32, 0))
	{
		TRACE("Init: %s\n", FMOD_ErrorString(FSOUND_GetError()));
    TRACE("Error opening audio, so disabling it\n");
    Sss::instance()->set_config().use_audio = false;
    m_enable = false;
    return;
	}
  
  FSOUND_SetSFXMasterVolume(255);    
  
  TRACE_FILE_IF(1)
    TRACE("FMOD setup done\n");
}

void Audio::register_instance(Object * object,
                              string & file,
                              float vol_scale,
                              float pitch_scale,
                              float dist2_offset,
                              bool loop)
{
  if (!m_enable)
    return;
  TRACE_FILE_IF(1) {
    TRACE_METHOD(); TRACE("Registering %p with audio file %s\n", object, file.c_str());}
  //  cout << "Registering " << object << " with audio file " << file << "\n";
  Audio_params params;
  params.vol_scale = vol_scale;
  params.pitch_scale = pitch_scale;
  params.dist2_offset = dist2_offset;
  string real_file = "sounds/" + file;
  params.sample = FSOUND_Sample_Load(FSOUND_FREE, real_file.c_str(), FSOUND_HW3D, 0);
  if (!params.sample)
  {
    TRACE("samp1: %s\n", FMOD_ErrorString(FSOUND_GetError()));
    assert1(!"Error");    
  }
  
	// increasing mindistance makes it louder in 3d space
	FSOUND_Sample_SetMinMaxDistance(params.sample, 1.0f, 200.0f);	
	FSOUND_Sample_SetMode(params.sample, FSOUND_LOOP_NORMAL);
  
  params.channel = FSOUND_PlaySoundEx(FSOUND_FREE, params.sample, NULL, TRUE);
  
  if (loop)
    FSOUND_Sample_SetMode(params.sample, FSOUND_LOOP_NORMAL);
  else
    FSOUND_Sample_SetMode(params.sample, FSOUND_LOOP_OFF);
  
  params.paused = true;
  
  params.orig_vol = FSOUND_GetVolume(params.channel);
  params.orig_freq = FSOUND_GetFrequency(params.channel);
  
  bool ok = 
    (m_instances.insert(Instance_map::value_type(object, params))).second;
  assert1(ok);
}


void Audio::deregister_instance(Object * object)
{
  if (!m_enable)
    return;
  TRACE_FILE_IF(1) {
    TRACE_METHOD(); TRACE("Deregistering %p\n", object);}
  Audio_params params = m_instances[object];
	FSOUND_Sample_Free(params.sample);	
  m_instances.erase(object);
}


void Audio::set_vol_scale(Object * object,
                          float vol_scale)
{
  if (!m_enable)
    return;
  m_instances[object].vol_scale = vol_scale * 0.5;
}

void Audio::set_pitch_scale(Object * object,
                            float pitch_scale)
{
  if (!m_enable)
    return;
  m_instances[object].pitch_scale = pitch_scale;
}

void Audio::set_dist2_offset(Object * object,
                             float dist2_offset)
{
  if (!m_enable)
    return;
  m_instances[object].dist2_offset = dist2_offset;
}

void Audio::do_audio(const Object * ear)
{
  if (!m_enable)
    return;
  // FMOD treats +X as right, +Y as up, and +Z as forwards.
  float fmod_vel[3];
  float fmod_pos[3];
  
  const Velocity & ear_vel = ear->get_vel();
  const Position & ear_pos = ear->get_eye();
  const Vector & ear_fwd = (ear->get_eye_target() - ear->get_eye()).normalise();
  const Vector & ear_up = ear->get_eye_up();
  
  fmod_vel[0] = -ear_vel[1];
  fmod_vel[1] = ear_vel[2];
  fmod_vel[2] = ear_vel[0];
  fmod_pos[0] = -ear_pos[1];
  fmod_pos[1] = ear_pos[2];
  fmod_pos[2] = ear_pos[0];
  
  FSOUND_3D_Listener_SetAttributes(fmod_pos, 
                                   fmod_vel,
                                   -ear_fwd[1], 
                                   ear_fwd[2], 
                                   ear_fwd[0],
                                   -ear_up[1],   
                                   ear_up[2],   
                                   ear_up[0]);
  
  Instance_map::iterator it;
  for (it = m_instances.begin() ;
       it != m_instances.end() ; 
       ++it)
  {
    Object * object = it->first;
    assert1(object);
    
    int freq = (int) (it->second.pitch_scale * it->second.orig_freq);
    int vol = (int) (Sss::instance()->config().global_audio_scale * 
                     it->second.vol_scale * it->second.orig_vol);
    
    if (vol < 0) vol = 0;
    else if (vol > 255) vol = 255;
    
    const Velocity & vel = object->get_vel();
    const Position & pos = object->get_pos();
    
    fmod_vel[0] = -vel[1];
    fmod_vel[1] = vel[2];
    fmod_vel[2] = vel[0];
    fmod_pos[0] = -pos[1];
    fmod_pos[1] = pos[2];
    fmod_pos[2] = pos[0];
    
		FSOUND_3D_SetAttributes(it->second.channel,
                            fmod_pos,
                            fmod_vel);
    
    FSOUND_SetFrequency(it->second.channel,
                        freq);
    FSOUND_SetVolume(it->second.channel,
                     vol);
    if (it->second.paused)
    {
      FSOUND_SetPaused(it->second.channel, FALSE);
      it->second.paused = false;
    }
  }
  
#ifdef USE_FMOD_3_6
  FSOUND_3D_Update();
#else
  FSOUND_Update();
#endif
}

#else // not fmod
#ifdef WITH_PLIB
#include <plib/sl.h>
#include <plib/sm.h>

Audio::Audio()
{
  TRACE_METHOD_ONLY(1);
  m_enable = Sss::instance()->config().use_audio;
  if (!m_enable)
    return;
  m_scheduler = new slScheduler(8000);
  m_mixer = new smMixer("/dev/mixer");
  m_mixer->setMasterVolume ( 100 ) ;
  // Windows seems to cope with a safety margin < 0.128, but linux
  // needs .128?
  m_scheduler->setSafetyMargin ( 0.128f ) ;
  
}

void Audio::register_instance(Object * object,
                              string & file,
                              float vol_scale,
                              float pitch_scale,
                              float dist2_offset,
                              bool loop)
{
  if (!m_enable)
    return;
  TRACE_FILE_IF(1) {
    TRACE_METHOD(); TRACE("Registering %p with audio file %s\n", object, file.c_str());}
  //  cout << "Registering " << object << " with audio file " << file << "\n";
  Audio_params params;
  params.vol_scale = vol_scale;
  params.pitch_scale = pitch_scale;
  params.dist2_offset = dist2_offset;
  params.rate_envelope = new slEnvelope( 1, SL_SAMPLE_ONE_SHOT );
  params.vol_envelope = new slEnvelope( 1, SL_SAMPLE_ONE_SHOT );
  string real_file = "sounds/" + file;
  params.sample = new slSample(real_file.c_str(), m_scheduler);
//  cout << "sample = " << params.sample << endl;
  if (loop)
    m_scheduler->loopSample(params.sample);
  else
    m_scheduler->playSample(params.sample);
  
  bool ok = 
    (m_instances.insert(Instance_map::value_type(object, params))).second;
  assert1(ok);
  
//  cout << "done\n";
}


void Audio::deregister_instance(Object * object)
{
  if (!m_enable)
    return;
  TRACE_FILE_IF(1) {
    TRACE_METHOD(); TRACE("Deregistering %p\n", object);}
  Audio_params params = m_instances[object];
  m_scheduler->stopSample(params.sample);
  m_scheduler->addSampleEnvelope(
    params.sample, 0, 1, 0, SL_PITCH_ENVELOPE);
  m_scheduler->addSampleEnvelope(
    params.sample, 0, 0, 0, SL_VOLUME_ENVELOPE);
  m_scheduler->update();
// plib is broken so that deleting the sample makes it crash. For now just leak it...
//  delete params.rate_envelope;
//  delete params.vol_envelope;
//  delete params.sample;
  m_instances.erase(object);
}


void Audio::set_vol_scale(Object * object,
                          float vol_scale)
{
  if (!m_enable)
    return;
  m_instances[object].vol_scale = vol_scale;
}

void Audio::set_pitch_scale(Object * object,
                            float pitch_scale)
{
  if (!m_enable)
    return;
  m_instances[object].pitch_scale = pitch_scale;
}

void Audio::set_dist2_offset(Object * object,
                             float dist2_offset)
{
  if (!m_enable)
    return;
  m_instances[object].dist2_offset = dist2_offset;
}

void Audio::do_audio(const Object * ear)
{
  if (!m_enable)
    return;
  Instance_map::iterator it;
//  int count = 0;
  for (it = m_instances.begin() ;
       it != m_instances.end() ; 
       ++it)
  {
//    TRACE("%d\n", count++);
    Object * object = it->first;
    assert1(object);
    
    float rate = it->second.pitch_scale;
    float vol = Sss::instance()->config().global_audio_scale * 
      it->second.vol_scale;
    float dist2_offset = it->second.dist2_offset;
    
    const Velocity & vel = object->get_vel();
    const Position & pos = object->get_pos();
    
    float dist2 = (pos - ear->get_eye()).mag2();
    float rel_vel = 0.0f;
    // Doppler
    if (dist2 > 0.001)
    {
      Vector dir = (pos - ear->get_eye()).normalise();
      rel_vel = dot((vel - ear->get_vel()), dir);
      rate *= (1 - rel_vel / 330.0); // speed of sound
    }
    
//    cout << "sample = " << it->second.sample << endl;
    
    assert1(it->second.sample);
    assert1(it->second.rate_envelope);
    it->second.rate_envelope->setStep(0, 0.0, rate);
    m_scheduler->addSampleEnvelope(
      it->second.sample, 0, 1, it->second.rate_envelope, SL_PITCH_ENVELOPE);
    
    // volume
    vol *= dist2_offset/(dist2_offset + dist2);
    
    // get compression of waves when the source comes towards you -
    // I guessed this next equation...
//    vol *= (1 - rel_vel/330 ) * (1 - rel_vel/330);
    
    assert1(it->second.vol_envelope);
    it->second.vol_envelope->setStep(0, 0.0, vol);
    m_scheduler->addSampleEnvelope(
      it->second.sample, 0, 0, it->second.vol_envelope, SL_VOLUME_ENVELOPE);
  }
  m_scheduler->update();
}

#else // no plib - just stub out

Audio::Audio()
{
  TRACE_METHOD_ONLY(1);
}

void Audio::register_instance(Object * object,
                              std::string & file,
                              float vol_scale,
                              float pitch_scale,
                              float dist2_offset,
                              bool loop)
{
}


void Audio::deregister_instance(Object * object)
{
}


void Audio::set_vol_scale(Object * object,
                          float vol_scale)
{
}

void Audio::set_pitch_scale(Object * object,
                            float pitch_scale)
{
}

void Audio::set_dist2_offset(Object * object,
                             float dist2_offset)
{
}

void Audio::do_audio(const Object * ear)
{
}


#endif // with plib
#endif // with FMOD
