#include "Audio.h"
#include <FK/Error.h>
#include <FK/Model.h>

#include <iostream>
#include <vector>

#ifdef __BIG_ENDIAN__
const int _ENDIAN = 1;
#else
const int _ENDIAN = 0;
#endif

using namespace std;

fk_AudioOggBuffer::fk_AudioOggBuffer(void)
{
	buffer.clear();
	chunkTime.clear();
	chunkSize.clear();
	length = current = 0;
	startStatus = endStatus = surround = false;
	refDist = 1.0;
	ref_model = NULL;
	return;
}

fk_AudioOggBuffer::~fk_AudioOggBuffer()
{
	return;
}

bool fk_AudioOggBuffer::open(const std::string &argFileName)
{
	int				ovStatus;
	OggVorbis_File	vf;

	if(getInit() == false) init();

	if((ovStatus = ov_fopen((char *)&argFileName[0], &vf)) < 0) {
		switch(ovStatus) {
		  case OV_EREAD:
			fk_PutError("fk_AudioOggBuffer", "open", 1, "Read Error");
			break;

		  case OV_ENOTVORBIS:
			fk_PutError("fk_AudioOggBuffer", "open", 1, "Not Vorbis Error");
			break;

		  case OV_EVERSION:
			fk_PutError("fk_AudioOggBuffer", "open", 1, "Version Error");
			break;

		  case OV_EBADHEADER:
			fk_PutError("fk_AudioOggBuffer", "open", 1, "Header Error");
			break;

		  case OV_EFAULT:
			fk_PutError("fk_AudioOggBuffer", "open", 1, "Fault Error");
			break;

		  default:
			fk_PutError("fk_AudioOggBuffer", "open", 1, "Undefined Error");
			break;
		}

		return false;
	}

	ReadBuffer(&vf);
	ov_clear(&vf);

	startStatus = endStatus = false;
	return true;
}

void fk_AudioOggBuffer::ReadBuffer(OggVorbis_File *argVF)
{
	int				size = 1;
	unsigned int	count = 0;
	int				tmpCurrent = 0;

	buffer.clear();
	chunkTime.clear();
	chunkSize.clear();
	length = 0;

	for(count = 0;; count++) {
		buffer.resize((count+1)*FK_OV_BUFSIZE);
		chunkTime.push_back(ov_time_tell(argVF));
		size = int(ov_read(argVF, &buffer[count*FK_OV_BUFSIZE],
						   FK_OV_BUFSIZE * sizeof(char),
						   _ENDIAN, 2, 1, &tmpCurrent));

		if(size <= 0) break;
		chunkSize.push_back(size);
		length++;
		if(count == 0) MakeOVInfo(argVF);
	}
	buffer.resize(count * FK_OV_BUFSIZE);
	chunkTime.resize(count);

	return;
}

fk_AudioWavBuffer::fk_AudioWavBuffer(void) : fk_AudioOggBuffer()
{
	return;
}

fk_AudioWavBuffer::~fk_AudioWavBuffer()
{
	return;
}

bool fk_AudioWavBuffer::open(const std::string &argFileName)
{
	fk_Wave_Info	info;
	ALint			err;

	if(getInit() == false) init();

	alutLoadWAVFile((ALbyte*)argFileName.c_str(), &info.format, &info.data, &info.size, &info.freq, &info.loop);
	if((err = alGetError()) != AL_NO_ERROR) {
		fk_PutError("fk_AudioWavBuffer", "open", 1, alGetString(err));
		alutUnloadWAV(info.format, info.data, info.size, info.freq);
		return false;
	}
	ReadBuffer(&info);
	alutUnloadWAV(info.format, info.data, info.size, info.freq);

	startStatus = endStatus = false;
	return true;
}

void fk_AudioWavBuffer::ReadBuffer(fk_Wave_Info *argWave)
{
	unsigned int	count = 0, adjust = 0;
	long			bytePerSec = 0;

	// MakeOVInfo()に相当
	format = argWave->format;
	rate = argWave->freq;
	
	bytePerSec = rate;
	switch(format) {
	case AL_FORMAT_MONO8:
		bytePerSec *= 1;
		break;
	case AL_FORMAT_MONO16:
	case AL_FORMAT_STEREO8:
		bytePerSec *= 2;
		break;
	case AL_FORMAT_STEREO16:
		bytePerSec *= 4;
		break;
	}

	buffer.clear();
	chunkTime.clear();
	chunkSize.clear();
	length = 0;

	count = argWave->size / FK_OV_BUFSIZE;
	adjust = argWave->size % FK_OV_BUFSIZE;

	buffer.resize(argWave->size);
	memcpy(&buffer[0], argWave->data, argWave->size);

	for(unsigned int i = 0; i < count; i++) {
		chunkTime.push_back(double(i*FK_OV_BUFSIZE)/(double)bytePerSec);
		chunkSize.push_back(FK_OV_BUFSIZE);
		length++;
	}

	if(adjust != 0) {
		chunkTime.push_back(double(count*FK_OV_BUFSIZE)/(double)bytePerSec);
		chunkSize.push_back(adjust);
		length++;
	}

	return;
}

bool fk_AudioOggBuffer::ready(void)
{
	current = 0;
	if(startStatus == false) StartQueue(true);
	return true;
}

bool fk_AudioOggBuffer::play(void)
{
	if(startStatus == false) {
		if(ready() == false) return false;
	}

	if(PlayBuffer() == true) return true;
	else if(endStatus & loopMode) {
		seek(loopStartTime);
		return PlayBuffer();
	}

	return false;
}

void fk_AudioOggBuffer::StartQueue(bool argInitFlg)
{
	ALuint			bufferID;
	ALint			queueNum;

	startStatus = true;

	if(argInitFlg == true) {
		queueNum = -1;
		CreateID();
	}

	UnQueue(true);

	for(unsigned int i = 0; i < queueSize && current < length; i++) {
		alGenBuffers(1, &bufferID);
		alBufferData(bufferID, format,
					 &buffer[current*FK_OV_BUFSIZE],
					 chunkSize[current], rate);
		alSourceQueueBuffers(source, 1, &bufferID);
		current++;
	}

	return;
}

void fk_AudioOggBuffer::UnQueue(bool argBufferFlg)
{
	ALint		queueNum;
	ALuint		bufferID;
	int			i;

	alGetSourcei(source, AL_BUFFERS_QUEUED, &queueNum);
	for(i = 0; i < queueNum; i++) {
		alSourceUnqueueBuffers(source, 1, &bufferID);
		if(argBufferFlg == true) {
			alDeleteBuffers(1, &bufferID);
		}
	}
	return;
}

bool fk_AudioOggBuffer::PlayBuffer(void)
{
	ALint			status, i, procNum, bufNum;
	ALuint			bufferID;

	bufferID = 0;
	procNum = 0;
	bufNum = 0;

	if(surround == true) {
		alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE);
		if(ref_model != NULL) {
			sourcePos = ref_model->getInhPosition();
		}
		alSource3f(source, AL_POSITION, (float)sourcePos.x, (float)sourcePos.y, (float)sourcePos.z);
		alSourcef(source, AL_REFERENCE_DISTANCE, (float)refDist);
		UpdateListener();
	} else {
		alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
	}

	alGetSourcei(source, AL_SOURCE_STATE, &status);
	
	// 再生していなければ、再生命令を出す
	if(status != AL_PLAYING) {
		alSourcePlay(source);
		sleep(0.001);
	}

	// 再生済みバッファの個数を取得
	alGetSourcei(source, AL_BUFFERS_PROCESSED, &procNum);

	for(i = 0; i < procNum; i++) {
		alSourceUnqueueBuffers(source, 1, &bufferID);

		// 既にファイルを読み終えてる場合、バッファを消去
		if(endStatus == true) {
			alDeleteBuffers(1, &bufferID);
			continue;
		}

		if(current >= length) {
			// 終端
			endStatus = true;
			alDeleteBuffers(1, &bufferID);
			continue;
		}

		alBufferData(bufferID, format,
					 &buffer[current*FK_OV_BUFSIZE],
					 chunkSize[current], rate);
		alSourceQueueBuffers(source, 1, &bufferID);
		current++;
	}

	// 終了判定
	if(status != AL_PLAYING && endStatus == true) return false;
	return true;
}

void fk_AudioOggBuffer::stop(void)
{
	ALint		status, procNum, i;
	ALuint		bufferID;

	if(startStatus == false) return;
	alGetSourcei(source, AL_SOURCE_STATE, &status);
	if(status != AL_STOPPED) {
		alSourceStop(source);
	}
	alGetSourcei(source, AL_BUFFERS_PROCESSED, &procNum);
	for(i = 0; i < procNum; i++) {
		alSourceUnqueueBuffers(source, 1, &bufferID);
		alDeleteBuffers(1, &bufferID);
	}
	return;
}

void fk_AudioOggBuffer::end(void)
{
	stop();
	if(startStatus == true) alDeleteSources(1, &source);
	startStatus = endStatus = false;
	EraseID();
	return;
}	

double fk_AudioOggBuffer::tell(void)
{
	ALfloat	bufTell;

	if(startStatus == false) return -1.0;

	alGetSourcef(source, AL_SEC_OFFSET, &bufTell);
	return chunkTime[current] + (double)bufTell;
}

void fk_AudioOggBuffer::seek(double argTime)
{
	unsigned int i;

	stop();
	for(i = 0; i < length; i++) {
		if(chunkTime[i] >= argTime - 0.00001) break;
	}
	current = i;
	endStatus = false;
	StartQueue(!startStatus);

	return;
}

void fk_AudioOggBuffer::setPosition(const fk_Vector &argPos)
{
	sourcePos = argPos;
	surround = true;

	return;
}
void fk_AudioOggBuffer::setModel(fk_Model *argModel)
{
	ref_model = argModel;
	if(ref_model != NULL) {
		sourcePos = ref_model->getInhPosition();
		surround = true;
	}

	return;
}
void fk_AudioOggBuffer::setModel(fk_Model &argModel)
{
	ref_model = &argModel;
	if(ref_model != NULL) {
		sourcePos = ref_model->getInhPosition();
		surround = true;
	}

	return;
}

void fk_AudioOggBuffer::setReferenceDist(double argDist)
{
	refDist = argDist;
}

void fk_AudioOggBuffer::disableSurround(void)
{
	ref_model = NULL;
	sourcePos.init();
	refDist = 1.0;
	surround = false;

	return;
}

fk_Model * fk_AudioOggBuffer::getModel(void)
{
	return ref_model;
}

fk_Vector fk_AudioOggBuffer::getPosition(void)
{
	return sourcePos;
}

double fk_AudioOggBuffer::getReferenceDist(void)
{
	return refDist;
}

bool fk_AudioOggBuffer::getSurroundMode(void)
{
	return surround;
}
