/*
  Copyright (C) 2010-2011  Charles Pence <charles@charlespence.net>
  Copyright (C) 1996-1997  Id Software, Inc.

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "../pch.h"
#include "../quakedef.h"
#include <physfs.h>
#include "../filesystem/filesystem.h"
#include "../filesystem/physfsrwops.h"
#include "service.h"
#include "mixer.h"


namespace sound
{

static const float SOUND_CLIP_DISTANCE = 1000.0f;

void Service::Initialize()
{
    Con_Printf("\nSound Initialization\n");

    Cvar_RegisterVariable("nosound", "0");
    Cvar_RegisterVariable("volume", "0.7", true);
    Cvar_RegisterVariable("bgmvolume", "0.3", true);
    Cvar_RegisterVariable("ambient_level", "0.3");
    Cvar_RegisterVariable("ambient_fade", "100");
    
    music_ = NULL;
    mixer_ = new Mixer;
    
    initialized_ = true;
    thread_run_ = true;
    
    thread_ = boost::thread(boost::bind(&Service::ThreadProc, this));
    
    service::PlaySound.connect(boost::bind(&Service::PlaySound, this, _1, _2, _3, _4, _5, _6));
    service::StopSound.connect(boost::bind(&Mixer::Stop, mixer_, _1, _2));
    service::PlayStaticSound.connect(boost::bind(&Service::PlayStaticSound, this, _1, _2, _3, _4));
    
    service::PlayMusic.connect(boost::bind(&Service::PlayMusic, this, _1));
    service::PauseMusic.connect(boost::bind(&Service::PauseMusic, this, _1));
    service::StopMusic.connect(Mix_HaltMusic);
    
    service::FramePreRender.connect(boost::bind(&Mixer::SetListenerParams, mixer_, _1));
    
    service::ConsoleCommand.connect(boost::bind(&Service::Play_f, this, _1));
    service::ConsoleCommand.connect(boost::bind(&Service::PlayVol_f, this, _1));
    service::ConsoleCommand.connect(boost::bind(&Service::StopSound_f, this, _1));
}

void Service::Shutdown()
{
    if (!initialized_)
        return;
    
    initialized_ = false;
    
    // Signal and wait for the thread to terminate
    {
        boost::mutex::scoped_lock lock(thread_lock_);
        thread_run_ = false;
    }
    thread_.join();
    
    if (music_)
    {
        Mix_FreeMusic(music_);
        music_ = NULL;
    }
    delete mixer_;
}


void Service::ThreadProc()
{
    while (1)
    {
        {
            boost::mutex::scoped_lock lock(thread_lock_);
            if (!thread_run_)
                return;
        }
        
        mixer_->Update();
        boost::this_thread::sleep(boost::posix_time::milliseconds(50));
    }
}


void Service::PlaySound(int entity_num, int entity_channel, unsigned soundindex, 
    vec3_t origin, float vol, float attenuation)
{
    if (!initialized_)
        return;
    if (Cvar_VariableValue("nosound"))
        return;
    Sample::Pointer s = Sample::Get(soundindex);
    if (!s.get())
        return;

    Channel *target = mixer_->GetNewChannel(entity_num, entity_channel);
    if (!target)
        return;
    
    target->sample_ = s;
    target->looping_ = s->looping();
    target->master_vol_ = vol;
    target->entity_num_ = entity_num;
    target->entity_channel_ = entity_channel;
    VectorCopy(origin, target->origin_);
    target->dist_mult_ = attenuation / SOUND_CLIP_DISTANCE;
    target->Spatialize(mixer_->listener_origin(), mixer_->listener_right());
    
    if (!target->left_vol_ && !target->right_vol_)
    {
        target->stop();
        return;
    }
    
    target->Play();
}

void Service::PlayStaticSound(unsigned soundindex, vec3_t origin, 
    float vol, float attenuation)
{
    if (!initialized_)
        return;
    
    Sample::Pointer s = Sample::Get(soundindex);
    if (!s.get())
        return;

    Channel *ch = mixer_->GetStaticChannel();
    if (!ch)
        return;

    ch->sample_ = s;
    ch->looping_ = true;
    VectorCopy (origin, ch->origin_);
    ch->master_vol_ = vol;
    ch->dist_mult_ = (attenuation/64) / SOUND_CLIP_DISTANCE;
    ch->Spatialize(mixer_->listener_origin(), mixer_->listener_right());

    ch->Play();
}


void Service::PlayMusic(int track)
{
    std::string filename;
    filename = boost::str(boost::format("music/track%1$02d.ogg") % track);
 
    // If we've already loaded, reset
    if (music_)
    {
        Mix_HaltMusic();
        Mix_FreeMusic(music_);
    }
    
    // Open up the file
    SDL_RWops *rwops = PHYSFSRWOPS_openRead(filename.c_str());
    if (!rwops)
    {
        Con_Printf("PlayMusic: Cannot open %s\n", filename.c_str());
        return;
    }
    
    music_ = Mix_LoadMUS_RW(rwops);
    if (!music_)
    {
        Con_Printf("PlayMusic: Cannot read %s\n", filename.c_str());
        return;
    }

    Mix_PlayMusic(music_, -1);
}

void Service::PauseMusic(bool pause)
{
    if (Mix_PausedMusic() && !pause)
        Mix_ResumeMusic();
    else if (!Mix_PausedMusic() && pause)
        Mix_PauseMusic();
}


bool Service::Play_f(const std::string &cmd)
{
    if (cmd != "play")
        return false;
    
    static int hash = 345;    
    size_t i = 1;
    while (i < Cmd_Argc())
    {
        std::string name = filesystem::DefaultPathExtension(Cmd_Argv(i), ".wav");
        
        unsigned sfx = Sample::Load(name);
        if (sfx == (unsigned)-1)
            continue;
        
        PlaySound(hash++, 0, sfx, mixer_->listener_origin(), 1.0, 1.0);
        i++;
    }
    
    return true;
}

bool Service::PlayVol_f(const std::string &cmd)
{
    if (cmd != "playvol")
        return false;
    
    static int hash = 543;
    size_t i = 1;
    while (i < Cmd_Argc())
    {
        std::string name = filesystem::DefaultPathExtension(Cmd_Argv(i), ".wav");
        
        unsigned sfx = Sample::Load(name);
        if (sfx == (unsigned)-1)
            continue;
        
        float vol = Util::lexical_cast<float>(Cmd_Argv(i+1));
        PlaySound(hash++, 0, sfx, mixer_->listener_origin(), vol, 1.0);
        i += 2;
    }
    
    return true;
}

bool Service::StopSound_f(const std::string &cmd)
{
    if (cmd != "stopsound")
        return false;
    
    mixer_->StopAll();
    return true;
}

}
