/*
  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 <SDL/sdl.h>
#include "mixer.h"

namespace sound
{

Mixer::Mixer()
{
    if (!(Mix_Init(MIX_INIT_OGG) & MIX_INIT_OGG))
    {
        Con_SafePrintf("Couldn't load SDL mixer with OGG support\n");
        return;
    }
    
    if (Mix_OpenAudio(44100, AUDIO_S16SYS, 2, 1024) == -1)
    {
        Con_SafePrintf("Couldn't open SDL mixer\n");
        return;
    }
    
    ambient_indexes_[AMBIENT_WATER] = Sample::Load("ambience/water1.wav");
    ambient_indexes_[AMBIENT_SKY] = Sample::Load("ambience/wind2.wav"); 
    ambient_indexes_[AMBIENT_SLIME] = (unsigned)-1;
    ambient_indexes_[AMBIENT_LAVA] = (unsigned)-1;   

    // Pre-allocate the dynamic channels
    boost::mutex::scoped_lock lock(channel_lock_);
    
    for (int i = 0 ; i < MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS ; i++)
        dynamic_channels_[i].Allocate();
}

Mixer::~Mixer()
{
    Mix_Quit();
}


Channel *Mixer::GetNewChannel(int entity_num, int entity_channel)
{
    // Find the best channel to replace (or an already stopped channel)
    boost::mutex::scoped_lock lock(channel_lock_);
    int first_to_die = -1;
    int oldest_started = 99999999;
    Channel *ch;
    
    for (int ch_idx = NUM_AMBIENTS ; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS ; ch_idx++)
    {
        ch = &dynamic_channels_[ch_idx];
        
        // Always override a sound from the same entity
        if (entity_num > 0 && ch->entity_num_ == entity_num &&
            (ch->entity_channel_ == entity_channel || entity_channel == -1))
        {
            first_to_die = ch_idx;
            break;
        }
        
        // Always take a stopped channel
        if (!ch->playing())
        {
            first_to_die = ch_idx;
            break;
        }
        
        // Don't let monster sounds override player sounds
        if (ch->entity_num_ == cl.viewentity && entity_num != cl.viewentity)
            continue;

        // Take the channel that's been playing for the longest time
        if (ch->started_ < oldest_started)
        {
            oldest_started = ch->started_;
            first_to_die = ch_idx;
        }
    }

    if (first_to_die == -1)
        return NULL;

    ch = &dynamic_channels_[first_to_die];
    if (ch->playing())
        ch->stop();
        
    return ch;
}

Channel *Mixer::GetStaticChannel()
{
    // Note: This cannot create a race condition, as the static channels are
    // stored in an std::list, and anything added to that list is not
    // removed (at least, not removed at a time that this function might be
    // called).
    boost::mutex::scoped_lock lock(channel_lock_);
    
    std::list<Channel>::iterator iter = 
        static_channels_.insert(static_channels_.end(), Channel());
    (*iter).Allocate();
    return &(*iter);
}


void Mixer::Stop(int entity_num, int entity_channel)
{
    boost::mutex::scoped_lock lock(channel_lock_);
    
    for (int i = NUM_AMBIENTS ; i < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS ; i++)
    {
        if (dynamic_channels_[i].entity_num_ == entity_num &&
            dynamic_channels_[i].entity_channel_ == entity_channel)
        {
            dynamic_channels_[i].stop();
            return;
        }
    }
}

void Mixer::StopAll()
{
    boost::mutex::scoped_lock lock(channel_lock_);
    
    foreach(Channel &ch, dynamic_channels_)
        ch.stop();
    foreach(Channel &ch, static_channels_)
        ch.stop();
}


void Mixer::SetListenerParams(const refdef_t &refdef)
{
    boost::mutex::scoped_lock lock(channel_lock_);

    VectorCopy(refdef.origin, listener_origin_);
    VectorCopy(refdef.right, listener_right_);
}

float *Mixer::listener_origin()
{
    boost::mutex::scoped_lock lock(channel_lock_);
    return listener_origin_;
}

float *Mixer::listener_right()
{
    boost::mutex::scoped_lock lock(channel_lock_);
    return listener_right_;
}



void Mixer::Update()
{
    Mix_Volume(-1, Cvar_VariableValue("volume") * MIX_MAX_VOLUME);
    Mix_VolumeMusic(Cvar_VariableValue("bgmvolume") * MIX_MAX_VOLUME);
    
    boost::mutex::scoped_lock lock(channel_lock_);
    
    foreach(Channel &ch, dynamic_channels_)
        UpdateChannel(ch);
    foreach(Channel &ch, static_channels_)
        UpdateChannel(ch);
    UpdateAmbients();
}

void Mixer::UpdateChannel(Channel &channel)
{
    if (!channel.playing())
        return;
    
    channel.Spatialize(listener_origin_, listener_right_);
    Mix_SetPanning(channel.mix_channel_, channel.left_vol_, channel.right_vol_);
}

void Mixer::UpdateAmbients()
{
    // Calculate ambient sound levels
    if (!cl.worldmodel)
        return;

    float level = Cvar_VariableValue("ambient_level") / 255.0f;
    float fade = Cvar_VariableValue("ambient_fade") / 255.0f;
    mleaf_t *l = Mod_PointInLeaf(listener_origin_, cl.worldmodel);
    
    if (!l || !level)
    {
        for (int i = 0 ; i < NUM_AMBIENTS ; i++)
            dynamic_channels_[i].stop();
        return;
    }

    for (int i = 0 ; i < NUM_AMBIENTS ; i++)
    {
        if (ambient_indexes_[i] == (unsigned)-1)
            continue;
        
        Channel *chan = &dynamic_channels_[i];
        chan->sample_ = Sample::Get(ambient_indexes_[i]);
        chan->looping_ = true;

        float vol = level * l->ambient_sound_level[i];
        if (vol < 0.03f)
            vol = 0;
        
        // Smooth out the volume adjustments over time
        if (chan->master_vol_ < vol)
        {
            chan->master_vol_ += host_frametime * fade;
            if (chan->master_vol_ > vol)
                chan->master_vol_ = vol;
        }
        else if (chan->master_vol_ > vol)
        {
            chan->master_vol_ -= host_frametime * fade;
            if (chan->master_vol_ < vol)
                chan->master_vol_ = vol;
        }

        // We don't spatialize these, just set the left and right volumes
        chan->left_vol_ = chan->right_vol_ = (byte)(chan->master_vol_ * 255.0f);
        Mix_SetPanning(chan->mix_channel_, chan->left_vol_, chan->right_vol_);
        
        if (!chan->playing())
            chan->Play();
    }
}

}
