#define _CRT_SECURE_NO_WARNINGS

#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;
}

int fk_AudioWavBuffer::ReadHeaderWav(FILE *fp, int *channel, int *bit, int *size, int *freq)
{
	short	res16;
	int		res32;
	int		dataSize, chunkSize;
	short	channelCnt, bitParSample, blockSize;
	int		samplingRate, byteParSec;
	
	int		dataPos;
	int		flag = 0;
	
	fread(&res32, 4, 1, fp);
	if(res32 != 0x46464952) {	//"RIFF"
		return 1;	//error 1
	}

	//データサイズ = ファイルサイズ - 8 byte の取得
	fread(&dataSize, 4, 1, fp);
	
	//WAVEヘッダーの読み
	fread(&res32, 4, 1, fp);
	if(res32 != 0x45564157) {	//"WAVE"
		return 2;	//error 2
	}

	while(flag != 3) {
		//チャンクの読み
		fread(&res32, 4, 1, fp);
		fread(&chunkSize, 4, 1, fp);
		
		switch(res32) {
		case 0x20746d66:	//"fmt "
			//format 読み込み
			//PCM種類の取得
			fread(&res16, 2, 1, fp);
			if(res16 != 1) {
				//非対応フォーマット
				return 4;
			}
			//モノラル(1)orステレオ(2)
			fread(&channelCnt, 2, 1, fp);
			if(res16 > 2) {
				//チャンネル数間違い
				return 5;
			}
			//サンプリングレート
			fread(&samplingRate, 4, 1, fp);
			//データ速度(byte/sec)=サンプリングレート*ブロックサイズ
			fread(&byteParSec, 4, 1, fp);
			//ブロックサイズ(byte/sample)=チャンネル数*サンプルあたりのバイト数
			fread(&blockSize, 2, 1, fp);
			//サンプルあたりのbit数(bit/sample)：8 or 16
			fread(&bitParSample, 2, 1, fp);
			
			*channel = (int)channelCnt;
			*bit = (int)bitParSample;
			*freq = samplingRate;

			flag += 1;
				
			break;
		case 0x61746164:	//"data"

			*size = chunkSize;
			
			dataPos = ftell(fp);

			flag += 2;
			break;
		}

	}
	
	//頭出し("fmt "が"data"より後にあった場合のみ動く)
	if(dataPos != ftell(fp)) {
		fseek(fp,dataPos,SEEK_SET);
	}

	return 0;
}

bool fk_AudioWavBuffer::open(const std::string &argFileName)
{
	bool			res;
	int				bit, channel, size, freq;
	FILE			*fp;

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

	fp = fopen(argFileName.c_str(), "rb");
	if(fp == NULL) return false;

	if(ReadHeaderWav(fp, &channel, &bit, &size, &freq) != 0) {
		fclose(fp);
		return false;
	}

	res = ReadBuffer(fp, channel, bit, size, freq);
	fclose(fp);

	startStatus = endStatus = false;
	return res;
}

bool fk_AudioWavBuffer::ReadBuffer(FILE *argFP, int argCh, int argBit, int argSize, int argFreq)
{
	unsigned int	count = 0, adjust = 0;
	long			bytePerSec = 0;
	size_t			res = 0;

	if(argCh == 2) {
		if(argBit == 16) {
			format = AL_FORMAT_STEREO16;
		} else {
			format = AL_FORMAT_STEREO8;
		}
	} else {
		if(argBit == 16) {
			format = AL_FORMAT_MONO16;
		} else {
			format = AL_FORMAT_MONO8;
		}
	}
	rate = argFreq;
	
	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 = argSize / FK_OV_BUFSIZE;
	adjust = argSize % FK_OV_BUFSIZE;

	buffer.resize(argSize);
	res = fread(&buffer[0], sizeof(char), argSize, argFP);
	if(res != argSize) {
		fk_PutError("fk_AudioWavBuffer", "ReadBuffer", 1, "Read Error");
		buffer.clear();
		rate = 0;
		return false;
	}

	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 true;
}

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;
}
