
#include <GL/gl.h>
#include <GL/glu.h>
#include <SDL.h>
#include <SDL_sound.h>
#include <math.h>
#include <limits.h>                 /* INT_MAX */
#include <stdint.h>

#include <iostream>

#include "AV.h"

using namespace std;

#define SOUND_RATE          44100
#define CALLBACK_SAMPLES    4096
#define CHANNELS            2
#define BYTES_PER_SAMPLE    2
#if AV_SOUND_FORMAT == AUDIO_S16SYS
# define SAMPLE_MIN          SHRT_MIN
# define SAMPLE_MAX          SHRT_MAX
#elif AV_SOUND_FORMAT == AUDIO_U16SYS
# define SAMPLE_MIN          0
# define SAMPLE_MAX          USHRT_MAX
#else
# error Unknown AV_SOUND_FORMAT
#endif

void AV_sound_callback(void * userdata, Uint8 * stream, int len)
{
    ((AV *)userdata)->playCallback(stream, len);
}

AV::AV()
{
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
    {
        cerr << "SDL_Init() returned error!" << endl;
        exit(3);
    }

    const SDL_VideoInfo * vidInfo = SDL_GetVideoInfo();
    if (vidInfo != NULL)
    {
        m_defaultWidth = vidInfo->current_w;
        m_defaultHeight = vidInfo->current_h;
    }
    else
    {
        cerr << "Warning: SDL_GetVideoInfo() returned NULL!" << endl;
        m_defaultWidth = 1024;
        m_defaultHeight = 768;
    }

    m_surface = NULL;
    m_width = 0;
    m_height = 0;

    Sound_Init();
    SDL_AudioSpec desired;
    desired.freq = SOUND_RATE;
    desired.format = AUDIO_S16SYS;
    desired.channels = CHANNELS;
    desired.samples = CALLBACK_SAMPLES;
    desired.callback = AV_sound_callback;
    desired.userdata = this;
    desired.silence = 0;
    if (SDL_OpenAudio(&desired, NULL) < 0)
    {
        cerr << "SDL_OpenAudio() error." << endl;
        exit(3);
    }
    m_sound_buffer = new sample_t[CALLBACK_SAMPLES * CHANNELS];

    m_active_sounds_mutex = SDL_CreateMutex();
}

AV::~AV()
{
    SDL_PauseAudio(1);
    SDL_DestroyMutex(m_active_sounds_mutex);
    SDL_CloseAudio();
    Sound_Quit();
    SDL_Quit();
    delete[] m_sound_buffer;
}

bool AV::start(int width, int height, bool fullscreen, bool grab_input,
        int samples)
{
    if (m_surface == NULL)
    {
        if (width == 0)
            width = m_defaultWidth;
        if (height == 0)
            height = m_defaultHeight;
        int flags = SDL_HWSURFACE | SDL_OPENGL;
        if (fullscreen)
            flags |= SDL_FULLSCREEN;

        SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
        if (samples > 1)
        {
            SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
            SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, samples);
        }
        m_surface = SDL_SetVideoMode(width, height, 32, flags);
        if (m_surface == NULL)
        {
            if (samples > 1)
            {
                cerr << "Error setting video mode, trying again without multisampling" << endl;
                /* failed to set SDL video mode with multisampling, try without */
                SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
                SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
                m_surface = SDL_SetVideoMode(width, height, 32, flags);
                if (m_surface != NULL)
                {
                    cerr << "Successfully set video mode without multisampling" << endl;
                }
            }
        }
        if (m_surface == NULL)
        {
            cerr << "Error setting SDL video mode." << endl;
            cerr << "Parameters:" << endl;
            cerr << "  width = " << width << endl;
            cerr << "  height = " << height << endl;
            cerr << "  fullscreen = " << fullscreen << endl;
            cerr << "  input grab = " << grab_input << endl;
            return false;
        }
        if (grab_input)
        {
            SDL_ShowCursor(SDL_DISABLE);
            SDL_WM_GrabInput(SDL_GRAB_ON);
        }
        SDL_WM_SetCaption("Anaglym", "Anaglym");
        m_fullscreen = fullscreen;
        m_width = width;
        m_height = height;

        glEnable(GL_DEPTH_TEST);
        glEnable(GL_LIGHTING);
        glEnable(GL_LIGHT0);
        glShadeModel(GL_SMOOTH);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective(60.0, (double) width / (double) height, 0.01, 10000.0);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    }
    return true;
}

void AV::stop()
{
    if (m_surface != NULL)
    {
        SDL_FreeSurface(m_surface);
        m_surface = NULL;
    }
}

refptr<AV::Sound> AV::createSound()
{
    refptr<Sound> s = new Sound(*this);
#if 0
    m_sounds.insert(s);
#endif
    return s;
}

void AV::playSound(AV::Sound * s)
{
    SDL_mutexP(m_active_sounds_mutex);
    m_active_sounds.insert(s);
    SDL_mutexV(m_active_sounds_mutex);
    SDL_PauseAudio(0);
}

void AV::stopSound(AV::Sound * s)
{
    SDL_mutexP(m_active_sounds_mutex);
    m_active_sounds.erase(s);
    int sz = m_active_sounds.size();
    SDL_mutexV(m_active_sounds_mutex);
    if (sz == 0)
    {
        SDL_PauseAudio(1);
    }
}

void AV::playCallback(Uint8 * stream, int len)
{
    sample_t * stream16 = (sample_t *) stream;
    memset(stream, 0, len);
    SDL_mutexP(m_active_sounds_mutex);
#ifdef DEBUG
    if (len > CALLBACK_SAMPLES * CHANNELS * BYTES_PER_SAMPLE)
    {
        cerr << "anaglym bug: callback buffer length = " << len
            << ", sound buffer size = "
            << CALLBACK_SAMPLES * CHANNELS * BYTES_PER_SAMPLE << endl;
        exit(38);
    }
#endif
    for (std::set<Sound *>::iterator it = m_active_sounds.begin();
         it != m_active_sounds.end();
         it++)
    {
        int bytes_decoded = (*it)->decode((Uint8 *)m_sound_buffer, len);
        if (bytes_decoded == 0)
        {
            m_active_sounds.erase(it);
        }
        float volume = (*it)->getVolume();
        for (int i = 0, samples_decoded = bytes_decoded / BYTES_PER_SAMPLE;
             i < samples_decoded;
             i++)
        {
            int sample_val = stream16[i] + (int)(volume * m_sound_buffer[i]);
            stream16[i] = min(SAMPLE_MAX, max(SAMPLE_MIN, sample_val));
        }
    }
    int sz = m_active_sounds.size();
    SDL_mutexV(m_active_sounds_mutex);
    if (sz == 0)
    {
        SDL_PauseAudio(1);
    }
}

/******************** AV::Sound ********************/

AV::Sound::Sound(AV & av)
    : m_av(av)
{
      m_rwops = NULL;
      m_ss = NULL;
      m_buff_bytes_left = 0;
      m_volume = 1.0f;
      m_data = NULL;
}

AV::Sound::~Sound()
{
    if (m_ss != NULL)
    {
        Sound_FreeSample(m_ss);
    }
    if (m_data != NULL)
    {
        delete[] m_data;
    }
}

bool AV::Sound::load(const unsigned char *data, unsigned int size)
{
    m_data = new unsigned char[size];
    memcpy(m_data, data, size);
    m_rwops = SDL_RWFromMem(m_data, size);
    Sound_AudioInfo desired;
    desired.channels = CHANNELS;
    desired.format = AUDIO_S16SYS;
    desired.rate = SOUND_RATE;
    m_ss = Sound_NewSample(m_rwops, NULL, &desired,
            CALLBACK_SAMPLES * BYTES_PER_SAMPLE);
    if (m_ss != NULL)
    {
        return true;
    }
    m_rwops = NULL;
    return false;
}

void AV::Sound::play()
{
    loop(1);
}

void AV::Sound::resume()
{
    if (m_ss != NULL)
    {
        m_av.playSound(this);
    }
}

void AV::Sound::stop()
{
    if (m_ss != NULL)
    {
        m_av.stopSound(this);
    }
}

void AV::Sound::loop(int times)
{
    if (m_ss != NULL)
    {
        m_loop_count = times;
        rewind();
        m_av.playSound(this);
    }
}

void AV::Sound::rewind()
{
    if (m_ss != NULL)
    {
        Sound_Rewind(m_ss);
    }
}

int AV::Sound::decode(Uint8 * stream, int len)
{
    int len_decoded = 0;
    int bytes_decoded_this_pass;
    if (m_buff_bytes_left > 0)
    {
        int len_to_copy = min(len, m_buff_bytes_left);
        int buff_offset = m_ss->buffer_size - m_buff_bytes_left;
        memcpy(stream, ((int8_t *)m_ss->buffer) + buff_offset, len_to_copy);
        m_buff_bytes_left -= len_to_copy;
        len_decoded += len_to_copy;
    }
    while (len_decoded < len)
    {
        bytes_decoded_this_pass = Sound_Decode(m_ss);
        if (bytes_decoded_this_pass == 0)
        {
            if (m_loop_count < INT_MAX)
            {
                m_loop_count--;
            }
            if (m_loop_count > 0)
            {
                Sound_Rewind(m_ss);
                bytes_decoded_this_pass = Sound_Decode(m_ss);
            }
        }
        if (bytes_decoded_this_pass <= 0)
        {
            return len_decoded;
        }
        int len_to_copy = min(bytes_decoded_this_pass, len - len_decoded);
        memcpy(stream + len_decoded, m_ss->buffer, len_to_copy);
        len_decoded += len_to_copy;
        m_buff_bytes_left = bytes_decoded_this_pass - len_to_copy;
    }
    return len_decoded;
}

void AV::Sound::setVolume(float v)
{
    m_volume = v;
    if (m_volume > 1.0f)
        m_volume = 1.0f;
    else if (m_volume < 0.0f)
        m_volume = 0.0f;
}
