#include <vorbis/vorbisfile.h>
#include <SDL.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>



#define LENGTHOF(X) (sizeof(X)/sizeof((X)[0]))



typedef const char* stringz;



typedef struct
{
	OggVorbis_File file;
	double         loopPosition;
}GAL_Music;



typedef struct
{
	Uint8* data;
	Uint32 size;
}GAL_Sound;



typedef struct tagGAL_Source GAL_Source;



struct tagGAL_Source
{
	GAL_Source* tag;
	GAL_Sound*  sound;
	int         position;
	int         isStopRequired;
};



typedef struct
{
	Uint8 data[128*1024];
	int   size;
	int   position;
}StreamBuffer;



static const Uint16 MIXER_FORMAT        = AUDIO_S16;
static const Uint8  MIXER_CHANNEL_COUNT = 2;
static const int    MIXER_FREQUENCY     = 22050;

static GAL_Source   _sources[64];

static StreamBuffer _streamBuffers[2];
static int          _activeBuffer = 0;

static GAL_Music*   _currentMusic = NULL;
static GAL_Music*   _nextMusic    = NULL;
static int          _isStopMusicRequired = 0;
static int          _isMuteMusic         = 0;

static int _volumeBGM = SDL_MIX_MAXVOLUME;
static int _volumeSE  = SDL_MIX_MAXVOLUME;

static Uint8* convertFull(OggVorbis_File* file, Uint8* srcData, Uint32 srcSize, Uint32* dstSize)
{
	vorbis_info* vi = ov_info(file, -1);

	SDL_AudioCVT cvt;

	SDL_BuildAudioCVT(
			&cvt,
			AUDIO_S16,
			vi->channels,
			vi->rate,
			MIXER_FORMAT,
			MIXER_CHANNEL_COUNT,
			MIXER_FREQUENCY
		);

	cvt.len = srcSize;
	cvt.buf = malloc(cvt.len * cvt.len_mult);

	if(cvt.buf)
	{
		memcpy(cvt.buf, srcData, cvt.len);

		SDL_ConvertAudio(&cvt);

		*dstSize = cvt.len * cvt.len_ratio;
	}
	else
	{
		*dstSize = 0;
	}

	return cvt.buf;
}



static int readStream(OggVorbis_File* file, Uint8* buffer, size_t bufsize, Uint32* size)
{
	Uint32 sum = 0;

	*size = 0;

	while(sum + 4*1024 <= bufsize)
	{
		static const int ENDIAN = 0; // 0 for Little-Endian, 1 for Big-Endian

		int  bitStream;

		// Read up to a buffer's worth of decoded sound data
		long bytes = ov_read(file, (char*)buffer+sum, bufsize-sum, ENDIAN, 2, 1, &bitStream);
		if(bytes > 0)
		{
			sum += bytes;
		}
		else if(bytes == 0)
		{
			break;
		}
		else
		{
			return -1;
		}
	}

	*size = sum;

	return 0;
}



static Uint8* readFull(OggVorbis_File* file, Uint32* size)
{
	static const int bufsize = 32*1024;

	Uint8* data = NULL;

	Uint8* buffer = (Uint8*)malloc(bufsize);

	*size = 0;
	if(buffer == NULL)
	{
		return NULL;
	}

	// Keep reading until all is read
	while(1)
	{
		Uint32 bytes;

		// Read up to a buffer's worth of decoded sound data
		if(readStream(file, buffer, bufsize, &bytes) == 0)
		{
			if(bytes > 0)
			{
				Uint8* newBuffer = (Uint8*)malloc(*size + bytes);

				memcpy(newBuffer, data, *size);

				memcpy(newBuffer + *size, buffer, bytes);

				free(data);

				data   = newBuffer;
				*size += bytes;
			}
			else
			{
				break;
			}
		}
		else
		{
			free(data);
			data  = NULL;
			*size = 0;
			break;
		}
	}

	free(buffer);

	return data;
}



static void fillBuffer(StreamBuffer* buffer, GAL_Music* music)
{
	vorbis_info* vi = ov_info(&music->file, -1);

	SDL_AudioCVT cvt;

	Uint32 readSize;
	size_t bufsize;

	buffer->size = buffer->position = 0;

	SDL_BuildAudioCVT(
			&cvt,
			AUDIO_S16,
			vi->channels,
			vi->rate,
			MIXER_FORMAT,
			MIXER_CHANNEL_COUNT,
			MIXER_FREQUENCY
		);

	bufsize = sizeof(buffer->data) / cvt.len_mult;

	cvt.buf = buffer->data;
	if(readStream(&music->file, cvt.buf, bufsize, &readSize) == 0)
	{
		if(readSize == 0)
		{
			if(ov_time_seek(&music->file, music->loopPosition) == 0)
			{
				if(readStream(&music->file, cvt.buf, bufsize, &readSize))
				{
					return;
				}
			}
			else
			{
				return;
			}
		}
		cvt.len = readSize;
		SDL_ConvertAudio(&cvt);

		buffer->size = cvt.len * cvt.len_ratio;
	}
}



static void mixAudio(void* unused, Uint8* stream, int len)
{
	size_t i;
	for(i=0; i<LENGTHOF(_sources); ++i)
	{
		if(_sources[i].tag)
		{
			if(!_sources[i].isStopRequired)
			{
				int size = _sources[i].sound->size - _sources[i].position;
				if(size > len)
				{
					size = len;
				}

				if(size > 0)
				{
					SDL_MixAudio(stream, _sources[i].sound->data+_sources[i].position, size, _volumeSE);

					_sources[i].position += size;

					if(_sources[i].position >= _sources[i].sound->size)
					{
						_sources[i].tag = NULL;
					}
				}
				else
				{
					_sources[i].tag = NULL;
				}
			}
			else
			{
				_sources[i].tag = NULL;
			}
		}
	}

	if(!_isStopMusicRequired)
	{
		if(_nextMusic)
		{
			_currentMusic = _nextMusic;

			_nextMusic    = NULL;

			ov_raw_seek(&_currentMusic->file, 0);

			for(i=0; i<LENGTHOF(_streamBuffers); ++i)
			{
				_streamBuffers[i].position = _streamBuffers[i].size = 0;
			}
			_activeBuffer = 0;
		}

		if(_currentMusic)
		{
			for(i=0; i<LENGTHOF(_streamBuffers); ++i)
			{
				if(_streamBuffers[i].position >= _streamBuffers[i].size)
				{
					fillBuffer(&_streamBuffers[i], _currentMusic);
				}
			}
			{
				StreamBuffer* buffer = &_streamBuffers[_activeBuffer];

				int size = buffer->size - buffer->position;

				if(size > len)
				{
					size = len;
				}

				int vol = (_isMuteMusic)?0:_volumeBGM;

				SDL_MixAudio(stream, buffer->data+buffer->position, size, vol);

				buffer->position += size;
				if(buffer->position >= buffer->size)
				{
					_activeBuffer ^= 1;
					if(size < len)
					{
						stream += size;
						len    -= size;

						buffer = &_streamBuffers[_activeBuffer];

						size = buffer->size - buffer->position;
						if(size > len)
						{
							size = len;
						}

						SDL_MixAudio(stream, buffer->data+buffer->position, size, vol);

						buffer->position += size;
					}
					fillBuffer(&_streamBuffers[_activeBuffer^1], _currentMusic);
				}
			}
		}
	}
	else
	{
		_currentMusic = NULL;
	}
}



void GAL_initialize(int argc, stringz* argv)
{
	SDL_AudioSpec fmt;

	memset(_sources, 0, sizeof(_sources));
	memset(_streamBuffers, 0, sizeof(_streamBuffers));

	/* 22khz の 16-bit ステレオ に設定 */
	fmt.freq     = MIXER_FREQUENCY;
	fmt.format   = MIXER_FORMAT;
	fmt.channels = MIXER_CHANNEL_COUNT;
	fmt.samples  = 512;
	fmt.callback = &mixAudio;
	fmt.userdata = NULL;

	/* オーディオデバイスをオープンしてサウンドの再生を開始! */
	if(SDL_OpenAudio(&fmt, NULL) >= 0)
	{
		SDL_PauseAudio(0);
	}
	else
	{
		fprintf(stderr, "Unable to open audio: %s\n", SDL_GetError());
	}
}



void GAL_quit(void)
{
}



GAL_Sound* GAL_createSound(stringz fileName)
{
	GAL_Sound* sound = (GAL_Sound*)calloc(1, sizeof(*sound));

	if(sound)
	{
		OggVorbis_File oggFile;

		sound->data = NULL;

		// Try opening the given file
		if(ov_fopen((char*)fileName, &oggFile) == 0)
		{
			Uint8* srcData;
			Uint32 srcSize;

			srcData = readFull(&oggFile, &srcSize);
			if(srcData)
			{
				sound->data = convertFull(&oggFile, srcData, srcSize, &sound->size);

				free(srcData);
				srcData = NULL;
			}

			// Clean up!
			ov_clear(&oggFile);
		}
		else
		{
			fprintf(stderr, "%s: error opening for decoding...\n", fileName);
		}

		if(!sound->data)
		{
			free(sound);
			sound = NULL;
		}
	}

	return sound;
}



void GAL_releaseSound(GAL_Sound* sound)
{
	if(sound)
	{
		size_t i;

		SDL_LockAudio();

		for(i=0; i<LENGTHOF(_sources); ++i)
		{
			if(_sources[i].tag)
			{
				if(_sources[i].sound == sound)
				{
					_sources[i].tag = NULL;
				}
			}
		}

		SDL_UnlockAudio();

		free(sound->data);
		free(sound);
	}
}



Uint32 GAL_soundSize(GAL_Sound* sound)
{
	return sound->size;
}



GAL_Source* GAL_createSource()
{
	return (GAL_Source*)calloc(1, sizeof(GAL_Source));
}



void GAL_releaseSource(GAL_Source* source)
{
	free(source);
}



void GAL_setSource(GAL_Source* source, GAL_Sound* sound)
{
	source->sound = sound;
}



void GAL_playSource(GAL_Source* source)
{
	size_t i;

	source->isStopRequired = 0;
	source->position       = 0;
	source->tag            = NULL;

	for(i=0; i<LENGTHOF(_sources); ++i)
	{
		if(!_sources[i].tag)
		{
			_sources[i] = *source;

			_sources[i].tag = source;

			source->tag = &_sources[i];

			return;
		}
	}
}



void GAL_stopSource(GAL_Source* source)
{
	if(source->tag)
	{
		if(source->tag->tag == source)
		{
			source->tag->isStopRequired = 1;
		}
	}
}



GAL_Music* GAL_createMusic(stringz fileName, double loopPosition)
{
	GAL_Music* music = (GAL_Music*)calloc(1, sizeof(*music));

	if(music)
	{
		// Try opening the given file
		if(ov_fopen((char*)fileName, &music->file) == 0)
		{
			music->loopPosition = loopPosition;

			return music;
		}
		else
		{
			fprintf(stderr, "%s: error opening for decoding...\n", fileName);
		}

		free(music);
		music = NULL;
	}
	else
	{
		fprintf(stderr, "createMusic: no memory...\n");
	}

	return NULL;
}



void GAL_stopMusic(void)
{
	_isStopMusicRequired = 1;
}



void GAL_muteMusic(int mute)
{
	_isMuteMusic = mute;
}


void GAL_releaseMusic(GAL_Music* music)
{
	if(music)
	{
		SDL_LockAudio();

		if(_currentMusic == music)
		{
			_currentMusic = NULL;
		}
		if(_nextMusic == music)
		{
			_nextMusic = NULL;
		}

		SDL_UnlockAudio();

		ov_clear(&music->file);

		free(music);
	}
}



void GAL_playMusic(GAL_Music* music)
{
	_isStopMusicRequired = 0;
	GAL_muteMusic(0);

	_nextMusic = music;
}



void GAL_setVolume(float bgm, float se)
{
	if(bgm < 1.0)
	{
		_volumeBGM = bgm * SDL_MIX_MAXVOLUME;
	}
	else
	{
		_volumeBGM = SDL_MIX_MAXVOLUME;
	}

	if(se < 1.0)
	{
		_volumeSE  = se * SDL_MIX_MAXVOLUME;
	}
	else
	{
		_volumeSE  = SDL_MIX_MAXVOLUME;
	}
}



void GAL_getVolume(float* bgm, float* se)
{
	*bgm = _volumeBGM * (1.0/SDL_MIX_MAXVOLUME);
	*se  = _volumeSE  * (1.0/SDL_MIX_MAXVOLUME);
}
