// ---------------------------------------------------------------------------
// 
//   Lily Engine alpha
// 
//   Copyright (c) 2010 Clifford Jolly
//     http://cliffjolly.com
//     cliff@cliffjolly.com
// 
// ---------------------------------------------------------------------------
// 
//   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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
//   USA
// -------------------------- END HEADER -------------------------------------

#include <iostream>
#include <cstdio>
#include <fstream>
#include <string.h>
using namespace std;

#include "soundmanager.h"
#include "../filesystem/filesystem.h"

namespace Sound {
	
	struct OggMemBuffer {
		const void *data;
		unsigned int length;
		unsigned int readPos;
	};
	
	// FIXME: DURR. I didn't know about alGetString()
	// Simple OpenAL error function for internal use.
	void checkALError(const std::string &note, ostream &out = cout) {
	#ifdef SHOW_AL_ERRORS
		ALuint err = alGetError();
		
		if(err == AL_NO_ERROR) return;
		
		cout << "Error: " << alGetString(err) << " (" << note ")" << endl;
	#endif
	}
	
	// Callback function to read a chunk of OGG from memory.
	static size_t oggReadFromMemory(void *ptr, size_t size, size_t nmemb, void *datasource) {
		OggMemBuffer *buf = (OggMemBuffer*)datasource;

		// Figure out how many bytes to read.
		unsigned int totalBytes = nmemb * size;
		unsigned int bytesLeft = (buf->length - buf->readPos);
		unsigned int bytesToRead = totalBytes > bytesLeft ? bytesLeft : totalBytes;

		// Copy over the data.
		memcpy(ptr, (char*)buf->data + buf->readPos, bytesToRead);

		// Advance the pointer.
		buf->readPos += bytesToRead;

		return bytesToRead;
	}

	// Similar to above, but for the SoundManager's music fields.
	size_t oggReadMusicFromMemory(void *ptr, size_t size, size_t nmemb, void *datasource) {
		SoundManager *mgr = (SoundManager*)datasource;

		// Figure out how many bytes to read.
		unsigned int totalBytes = nmemb * size;
		unsigned int bytesLeft = (mgr->musicFileSize - mgr->musicFilePos);
		unsigned int bytesToRead = totalBytes > bytesLeft ? bytesLeft : totalBytes;

		// Copy over the data.
		memcpy(ptr, (char*)mgr->musicFileBuffer + mgr->musicFilePos, bytesToRead);

		// Advance the pointer.
	   	mgr->musicFilePos += bytesToRead;

		return bytesToRead;
	}

	SoundManager::SoundManager(void) {
		// Get a sound device
		alcdev = alcOpenDevice(NULL);
		// Make a context
		alccon = alcCreateContext(alcdev, NULL);
		// Make that context current
		alcMakeContextCurrent(alccon);
		
		// Generate sound sources
		alGenSources(MAX_SOUND_SOURCES, sources);
		checkALError("alGenSources - static sounds");
		
		alGenSources(1, &musicSource);
		checkALError("alGenSources - music");
		
		// Position the music source to be wherever the listener is.
		alSource3f(musicSource, AL_POSITION, 0,0,0);
		alSourcei(musicSource, AL_SOURCE_RELATIVE, AL_TRUE);
	
		ALfloat lpos[6] = { 0, 0, 0, 0, 0, 0 }; // listener position (origin initially)
		ALfloat lori[6] = { 0, 1, 0, 0, 0, 1 }; // listener orientation  up vector and lookat vector
			// (looking up the Z axis initially)
	
		// Set position and orientation
		alListenerfv(AL_POSITION, lpos);
		alListenerfv(AL_ORIENTATION, lori);
		
		// Some music setup
		musicPlaying = false;
		currentMusicBuffer = 0;
		alGenBuffers(2, musicBuffers);
		
		// Music buffer fields.
		musicFileBuffer = NULL;
		musicFileSize = 0;
		musicFilePos = 0;
		musicFileBufferMine = false;

        normalSoundVolume = 1;
	}
	
	SoundManager::~SoundManager(void) {
		
		// Delete buffers for the music.
		stopMusic();
		alDeleteBuffers(2, musicBuffers);
		
		// Clean up the sources
		alDeleteSources(MAX_SOUND_SOURCES, sources);
		alDeleteSources(1, &musicSource);
		
		// Destroy the context
		alcDestroyContext(alccon);
		
		// Close the device
		alcCloseDevice(alcdev);
		
		// Done!
	}
	
	unsigned int SoundManager::loadOgg(const std::string &fileName) {
		// Load the OGG file itself.
		int oggLength;
		char *oggFile = FileSystem::loadFile(fileName, &oggLength);

		// Decode it.
		unsigned int buffer = loadOggFromMemory(oggFile, oggLength);

		// Clean up.
	   	delete[] oggFile;

		return buffer;
	}

	unsigned int SoundManager::loadOggFromMemory(const void *data, int length) {
		ALuint buffer;
		alGenBuffers(1, &buffer);

		if(!data) return buffer;

		OggVorbis_File vf;
		char *inBuffer = new char[SOUND_MAX_SIZE];

		// Set up callbacks.
		ov_callbacks cbs;
		cbs.seek_func  = NULL;
		cbs.close_func = NULL;
		cbs.tell_func  = NULL;
		cbs.read_func  = oggReadFromMemory;

		// Buffer pointy thinger.
		OggMemBuffer memBuf;
		memBuf.readPos = 0;
		memBuf.length = length;
		memBuf.data = data;

		if(!ov_open_callbacks(&memBuf, &vf, NULL, 0, cbs)) {
		
			// Read the whole Ogg in.
			int readAmount = 0;
			int totalRead = 0;
			int bitstream = -1;
			
			do {
				readAmount = ov_read(&vf, inBuffer + totalRead, SOUND_MAX_SIZE - totalRead, 0, 2, 1, &bitstream);
				if(readAmount > 0) {
					totalRead += readAmount;
				}
			} while(readAmount > 0);
			
			// TODO: Check for errors if readAmount is < 0.
		
			// Get the format info.
			vorbis_info *info = ov_info(&vf, -1);
			ALenum format;
			
			// Set the OpenAL format from that.
			if(info->channels == 1) {
				format = AL_FORMAT_MONO16;
			} else {
				format = AL_FORMAT_STEREO16;
			}
		
			// Send the data to OpenAL.
			alBufferData(buffer, format, inBuffer, totalRead, info->rate);
			
			// Close the file.
			ov_clear(&vf);
			
		} else {
			// TODO: Error
		}
		
		delete[] inBuffer;
		
		checkALError("loadOgg");
		
		return buffer;
	}
	
	
	void SoundManager::playSound(unsigned int snd, double x, double y, double z, bool loop, float volume) {
		if(snd == 0) return;
		
		ALfloat spos[3] = {x, y, z};
	
		ALuint source = getFreeSource();
		if(source <= 0) {
			// No source is available right now.
			return;
		}
	
		// Set position
		alSourcefv(source, AL_POSITION, spos);
		checkALError("alSource - Position");
		
		// Set buffer
		alSourcei(source, AL_BUFFER, snd);
		checkALError("alSource - Buffer");
		
		// Set looping
		alSourcei(source, AL_LOOPING, loop);
		checkALError("alSource - Loop");
		
        // Set volume
        alSourcef(source, AL_GAIN, volume * normalSoundVolume);
        checkALError("alSource - Volume");

		// Start playing
		alSourcePlay(source);	
		checkALError("alSourcePlay");
		
	}
	
	void SoundManager::freeSound(unsigned int snd) {
		ALuint buffer = snd;
		
		// Go through and stop all the sources playing this.
		// Then clear the buffer from them.
		for(int i = 0; i < MAX_SOUND_SOURCES; i++) {
			ALint sourceBuffer;
			alGetSourcei(sources[i], AL_BUFFER, &sourceBuffer);
			if((ALuint)sourceBuffer == buffer) {
				alSourceStop(sources[i]);
				alSourcei(sources[i], AL_BUFFER, AL_NONE);
			}
		}
		
		alDeleteBuffers(1, &buffer);
		
		checkALError("freeSound()");
	}
	
	void SoundManager::stopAllSounds(void) {
		
		// Loop through all sources...
		for(int i = 0; i < MAX_SOUND_SOURCES; i++) {
			
			// And stop each one.
			alSourceStop(sources[i]);
		}
	}
	
	
	ALuint SoundManager::getFreeSource(void) {
		
		int i;
		int state;
		
		// Loop through all our sources.
		for(i = 0; i < MAX_SOUND_SOURCES; i++) {
			
			// See if each one is playing.
			alGetSourcei(sources[i], AL_SOURCE_STATE, &state);
			
			if(state != AL_PLAYING) {
				
				// If it's not playing, then it's free.
				return sources[i];
			}
		}
		
		// If this point was reached, then everything is playing something.
		return 0;
		
	}
	
	void SoundManager::fillMusicBuffer(unsigned int buf) {
		ALshort musicDataBuffer[MUSIC_BUF_SIZE];
		int amountRead = 0;
	
		// Read in chunks of however large OGG Vorbis will let us go,
		// up to the maximum buffer size.
		while(amountRead < MUSIC_BUF_SIZE) {
			int bitstream = -1;
			int r = ov_read(&musicFile, ((char*)musicDataBuffer) + amountRead, MUSIC_BUF_SIZE - amountRead, 0, 2, 1, &bitstream);

			// FIXME: Handle the rare (and stupid) case of a zero-length Ogg.
			//   It'll loop forever here otherwise.			
			if(!r) {
				// Buffer ran out.
				// Loop back to start!

				ov_clear(&musicFile);

				ov_callbacks cbs;
				cbs.seek_func  = NULL;
				cbs.close_func = NULL;
				cbs.tell_func  = NULL;
				cbs.read_func  = oggReadMusicFromMemory;

				// Don't have to reload the file. Just seek back to start.
				musicFilePos = 0;

				// Re-open file.
				ov_open_callbacks(this, &musicFile, NULL, 0, cbs);		
			}
			
			amountRead += r;
			
		}
		
		// We've filled up the software buffer, now to dump it into an OpenAL buffer
		
		// Get the format info.
		vorbis_info *info = ov_info(&musicFile, -1);
		ALenum format;
		
		// Set the OpenAL format from that.
		if(info->channels == 1) {
			format = AL_FORMAT_MONO16;
		} else {
			format = AL_FORMAT_STEREO16;
		}
	
		// Send the data to OpenAL.
		alBufferData(musicBuffers[buf], format, musicDataBuffer, MUSIC_BUF_SIZE, info->rate);
		
		// Note: Once it goes to OpenAL, it's been copied to it.
		// musicDataBuffer safely goes out of scope here.
	}
	
	void SoundManager::startMusic(const std::string &fileName) {
		
		int len;
		char *newBuffer = FileSystem::loadFile(fileName, &len);

		startMusicFromBuffer(newBuffer, len, true);
	}

	void SoundManager::startMusicFromBuffer(char *buffer, int length, bool freeBufferWhenDone) {
		
		// If music is playing, stop it.
		if(musicPlaying) stopMusic();

		// Clean up last buffer, if we need to.	
		if(musicFileBuffer && musicFileBufferMine) {
			delete[] musicFileBuffer;
		}

		musicFileBuffer = buffer;
		musicFilePos = 0;
		musicFileSize = length;
		musicFileBufferMine = freeBufferWhenDone;

		if(!musicFileBuffer) return;

		// Set up callbacks.
		ov_callbacks cbs;
		cbs.seek_func  = NULL;
		cbs.close_func = NULL;
		cbs.tell_func  = NULL;
		cbs.read_func  = oggReadMusicFromMemory;

		if(ov_open_callbacks(this, &musicFile, NULL, 0, cbs) < 0) {
			// Music file can't be opened.
			return;
		}
		
		// Fill up the two buffers.
		fillMusicBuffer(0);
		fillMusicBuffer(1);
		
		// Queue them up.
		currentMusicBuffer = 0;
		
		alSourceQueueBuffers(musicSource, 1, (&musicBuffers[0]));
		checkALError("alSourceQueueBuffers - first OGG chunk");
	
		alSourceQueueBuffers(musicSource, 1, (&musicBuffers[1]));
		checkALError("alSourceQueueBuffers - second OGG chunk");
		
		// Start them playing.
		alSourcePlay(musicSource);
		checkALError("alSourcePlay - OGG");
		
		// Music is now playing.
		musicPlaying = true;
		
	}
	
	void SoundManager::stopMusic(void) {
		if(!musicPlaying) return;
		
		// Stop the source.
		alSourceStop(musicSource);
		alSourcei(musicSource, AL_BUFFER, NULL);
		
		// Close the OGG file.
		ov_clear(&musicFile);
		
		// Music is now stopped.
		musicPlaying = false;
		
		// Clean up the music file.
		if(musicFileBuffer) {
			if(musicFileBufferMine) {
				delete[] musicFileBuffer;
			}
			musicFileBuffer = NULL;
			musicFileSize = 0;
			musicFileBufferMine = false;
		}
	}
	
	bool SoundManager::getMusicPlaying(void) {
		return musicPlaying;
	}
	
    float SoundManager::getMusicVolume(void) {
        float r = 0;
        alGetSourcef(musicSource, AL_GAIN, &r);
        return r;
    }

    void SoundManager::setMusicVolume(float v) {
        alSourcef(musicSource, AL_GAIN, v);
    }

    float SoundManager::getSoundVolume(void) {
        return normalSoundVolume;
    }

    void SoundManager::setSoundVolume(float v) {

        if(normalSoundVolume != 0) {

            // Readjust the volume of everything that's already
            // playing.
            float gainMultiplier = v / normalSoundVolume;

            for(int i = 0; i < MAX_SOUND_SOURCES; i++) {

                float oldGain = 0;
                alGetSourcef(sources[i], AL_GAIN, &oldGain);

                alSourcef(sources[i], AL_GAIN, oldGain * gainMultiplier);
            }
        }

        normalSoundVolume = v;
    }

	void SoundManager::updateMusic(void) {
		if(!musicPlaying) return;
		
		// Check to see if the music reached the end of the buffers
		// (Long time since updateMusic() was called?)
		int musicState;
		alGetSourcei(musicSource, AL_SOURCE_STATE, &musicState);
		if(musicState == AL_STOPPED) {
			alSourcePlay(musicSource);
		}
	
		// Get the current buffer playing
		int currentBuffer;
		alGetSourcei(musicSource, AL_BUFFER, &currentBuffer);
		
		// If it's changed since the last time we did anything, we need to fill the other buffer.
		if((ALuint)currentBuffer != musicBuffers[currentMusicBuffer]) {
			
			// Take the used up buffer off the source
			alSourceUnqueueBuffers(musicSource, 1, &(musicBuffers[currentMusicBuffer]));
			
			// Fill it up with new music data
			fillMusicBuffer(currentMusicBuffer);
			
			// Put it back onto the queue
			alSourceQueueBuffers(musicSource, 1, &(musicBuffers[currentMusicBuffer]));
			
			// Remember which one is playing for now.
			currentMusicBuffer = !currentMusicBuffer;
		}
		
	}

};







