/*
 *   ORXONOX - the hottest 3D action shooter ever to exist
 *                    > www.orxonox.net <
 *
 *
 *   License notice:
 *
 *   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 2
 *   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, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 *   Author:
 *      Nicolas Perrenoud <nicolape_at_ee.ethz.ch>
 *   Co-authors:
 *      ...
 *
 */

#include "AudioStream.h"
#include "util/Debug.h"

namespace orxonox
{
    AudioStream::AudioStream(std::string path)
    {
        this->path = path;
        loaded = false;
    }

    void AudioStream::open()
    {
        int result;

        oggFile = fopen(path.c_str(), "rb");
        if (!oggFile)
        {
            COUT(2) << "AudioStream: Could not open Ogg file " << path << std::endl;
            return;
        }

        if ((result = ov_open(oggFile, &oggStream, NULL, 0)) < 0)
        {
            fclose(oggFile);
            COUT(2) << "AudioStream: Could not open Ogg stream. " << errorString(result) << std::endl;
            return;
        }

        loaded = true;

        vorbisInfo = ov_info(&oggStream, -1);
        vorbisComment = ov_comment(&oggStream, -1);

        if (vorbisInfo->channels == 1)
            format = AL_FORMAT_MONO16;
        else
            format = AL_FORMAT_STEREO16;


        alGenBuffers(2, buffers);
        check();
        alGenSources(1, &source);
        check();

        alSource3f(source, AL_POSITION,        0.0, 0.0, 0.0);
        alSource3f(source, AL_VELOCITY,        0.0, 0.0, 0.0);
        alSource3f(source, AL_DIRECTION,       0.0, 0.0, 0.0);
        alSourcef (source, AL_ROLLOFF_FACTOR,  0.0          );
        alSourcei (source, AL_SOURCE_RELATIVE, AL_FALSE     );
    }



    void AudioStream::release()
    {
        alSourceStop(source);
        empty();
        alDeleteSources(1, &source);
        check();
        alDeleteBuffers(1, buffers);
        check();

        ov_clear(&oggStream);
        loaded = false;
    }



    void AudioStream::display()
    {
        if (loaded)
        {
            COUT(3)
                << "version         " << vorbisInfo->version         << std::endl
                << "channels        " << vorbisInfo->channels        << std::endl
                << "rate (hz)       " << vorbisInfo->rate            << std::endl
                << "bitrate upper   " << vorbisInfo->bitrate_upper   << std::endl
                << "bitrate nominal " << vorbisInfo->bitrate_nominal << std::endl
                << "bitrate lower   " << vorbisInfo->bitrate_lower   << std::endl
                << "bitrate window  " << vorbisInfo->bitrate_window  << std::endl
                << std::endl
                << "vendor " << vorbisComment->vendor << std::endl;

            for (int i = 0; i < vorbisComment->comments; i++)
            {
                COUT(3) << "   " << vorbisComment->user_comments[i] << std::endl;
            }

            COUT(3) << std::endl;
        }
    }



    bool AudioStream::playback()
    {
        if (!loaded)
        {
            return false;
        }

        if (playing())
            return true;

        if (!stream(buffers[0]))
            return false;

        if (!stream(buffers[1]))
            return false;

        alSourceQueueBuffers(source, 2, buffers);
        alSourcePlay(source);

        return true;
    }




    bool AudioStream::playing()
    {
        if (!loaded)
        {
            return false;
        }

        ALenum state;
        alGetSourcei(source, AL_SOURCE_STATE, &state);
        return (state == AL_PLAYING);
    }




    bool AudioStream::update()
    {
        int processed;
        bool active = true;

        alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed);

        while (processed--)
        {
            ALuint buffer;

            alSourceUnqueueBuffers(source, 1, &buffer);
            check();

            active = stream(buffer);

            alSourceQueueBuffers(source, 1, &buffer);
            check();
        }

        if (active==false)
        {
            loaded = false;
        }
        return active;
    }




    bool AudioStream::stream(ALuint buffer)
    {
        char pcm[BUFFER_SIZE];
        int  size = 0;
        int  section;
        int  result;

        while (size < BUFFER_SIZE)
        {
            result = ov_read(&oggStream, pcm + size, BUFFER_SIZE - size, 0, 2, 1, &section);

            if (result > 0)
                size += result;
            else
                if (result < 0)
                    COUT(2) << "AudioStream: " << errorString(result) << std::endl;
                else
                    break;
        }

        if (size == 0)
            return false;

        alBufferData(buffer, format, pcm, size, vorbisInfo->rate);
        check();

        return true;
    }



    void AudioStream::empty()
    {
        int queued;

        alGetSourcei(source, AL_BUFFERS_QUEUED, &queued);

        while (queued--)
        {
            ALuint buffer;

            alSourceUnqueueBuffers(source, 1, &buffer);
            check();
        }
    }




    void AudioStream::check()
    {
        int error = alGetError();

        if (error != AL_NO_ERROR)
            COUT(2) << "AudioStream: OpenAL error was raised." << std::endl;
    }



    std::string AudioStream::errorString(int code)
    {
        switch (code)
        {
            case OV_EREAD:
                return std::string("Read from media.");
            case OV_ENOTVORBIS:
                return std::string("Not Vorbis data.");
            case OV_EVERSION:
                return std::string("Vorbis version mismatch.");
            case OV_EBADHEADER:
                return std::string("Invalid Vorbis header.");
            case OV_EFAULT:
                return std::string("Internal logic fault (bug or heap/stack corruption.");
            default:
                return std::string("Unknown Ogg error.");
        }
    }
}

