#include "sfl.h"
#include "sflSound.h"
#include "sflMixer.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syslimits.h>
#include <assert.h>
#include <pspkernel.h>
#include <pspthreadman.h>
#include <pspaudio.h>
#include <psputility_avmodules.h>

#define NUM_BUFFERS 2
#define NUM_SAMPLES 1024
#define SFL_MIXER_SAMPLERATE 44100

// 1.0f / 44100.0f
#define DURATION_PER_SAMPLE 2.2675736961451248e-05f

struct sflMixerSlot_t {
	union {
		struct {
			int chid;
			int thid;
			int mutex;
			int mutexOwner;
			int mutexCount;
			sflSound *sound;
			int volumeLeft;
			int volumeRight;
			int playing;
			sflMixerCallback finishCB;
			int finishCBArgs;
			void *finishCBArgp;
		};
		unsigned char dummy[64];
	};
};

static int g_initialized = 0;
static int g_terminate = 0;
static struct sflMixerSlot_t g_mixer[SFL_MIXER_NUM_SLOT] __attribute__((aligned(64)));
static short g_buffer[SFL_MIXER_NUM_SLOT][NUM_BUFFERS][NUM_SAMPLES][2] __attribute__((aligned(64)));
static char g_cwd[PATH_MAX + 1];

static int sflMixerSoundThread(unsigned int args, void *argp);

static void sflMixerLock(int slot) {
	assert(0 <= slot && slot < SFL_MIXER_NUM_SLOT);

	int thid = sceKernelGetThreadId();
	if (thid != g_mixer[slot].mutexOwner) {
		sceKernelWaitSemaCB(g_mixer[slot].mutex, 1, 0);
		g_mixer[slot].mutexOwner = thid;
	} else {
		g_mixer[slot].mutexCount++;
	}
}

static void sflMixerUnlock(int slot) {
	assert(0 <= slot && slot < SFL_MIXER_NUM_SLOT && g_mixer[slot].mutexOwner == sceKernelGetThreadId());

	if (g_mixer[slot].mutexCount > 0) {
		g_mixer[slot].mutexCount--;
	} else {
		g_mixer[slot].mutexOwner = -1;
		sceKernelSignalSema(g_mixer[slot].mutex, 1);
	}
}

int sflMixerInit(void)
{
	if (g_initialized) {
		DEBUG_PRINTF("already initialized\n");
		++g_initialized;
		return 0;
	}

	int res = sceUtilityLoadAvModule(PSP_AV_MODULE_AVCODEC);
	if (res < 0) {
		DEBUG_PRINTF("sceUtilityLoadAvModule failed: %08x\n", res);
		return 0;
	}

	g_terminate = 0;
	getcwd(g_cwd, PATH_MAX + 1);

	int i;
	for(i = 0; i < SFL_MIXER_NUM_SLOT; ++i) {
		g_mixer[i].chid = sceAudioChReserve(-1, NUM_SAMPLES, PSP_AUDIO_FORMAT_STEREO);
		if (g_mixer[i].chid < 0) {
			DEBUG_PRINTF("sceAudioChReserve failed 0x%08x\n", g_mixer[i].chid);
			goto error;
		}
		char str[32];
		snprintf(str, 32, "sflMixerSound%d", i);
		g_mixer[i].thid = sceKernelCreateThread(str, sflMixerSoundThread, 0x12, 0x10000, PSP_THREAD_ATTR_USER, 0);
		if (g_mixer[i].thid < 0) {
			DEBUG_PRINTF("sceKernelCreateThread failed 0x%08x\n", g_mixer[i].thid);
			goto error;
		}
		snprintf(str, 32, "sflMixerSound%d", i);
		g_mixer[i].mutex = sceKernelCreateSema(str, 0, 1, 1, 0);
		if (g_mixer[i].mutex < 0) {
			DEBUG_PRINTF("sceKernelCreateSema failed 0x%08x\n", g_mixer[i].mutex);
			goto error;
		}
		g_mixer[i].mutexOwner = -1;
		g_mixer[i].mutexCount = 0;
		g_mixer[i].sound = 0;
		g_mixer[i].volumeLeft = SFL_MIXER_VOLUME_MAX;
		g_mixer[i].volumeRight = SFL_MIXER_VOLUME_MAX;
		g_mixer[i].playing = 0;
		g_mixer[i].finishCB = 0;
		g_mixer[i].finishCBArgs = 0;
		g_mixer[i].finishCBArgp = 0;
		int res = sceKernelStartThread(g_mixer[i].thid, sizeof(i), &i);
		if (res < 0) {
			DEBUG_PRINTF("sceKernelStartThread failed 0x%08x\n", res);
			goto error;
		}
	}

	++g_initialized;

	return 0;

error:
	sceUtilityUnloadAvModule(PSP_AV_MODULE_AVCODEC);

	i = 0;
	for(i = 0; i < SFL_MIXER_NUM_SLOT; ++i) {
		if (g_mixer[i].chid >= 0) {
			sceAudioChRelease(g_mixer[i].chid);
			g_mixer[i].chid = -1;
		}
		if (g_mixer[i].thid >= 0) {
			sceKernelWakeupThread(g_mixer[i].thid);
			sceKernelWaitThreadEnd(g_mixer[i].thid, 0);
			sceKernelDeleteThread(g_mixer[i].thid);
			g_mixer[i].thid = -1;
		}
	}

	return -1;
}

void sflMixerQuit(void)
{
	if (g_initialized <= 0) {
		DEBUG_PRINTF("not initialized\n");
		return;
	}

	g_initialized--;
	if (g_initialized > 0) {
		return;
	}

	g_terminate = 1;
	int i = 0;
	for(i = 0; i < SFL_MIXER_NUM_SLOT; ++i) {
		sceKernelWakeupThread(g_mixer[i].thid);
		sceKernelWaitThreadEnd(g_mixer[i].thid, 0);
		sceKernelDeleteThread(g_mixer[i].thid);
		sceAudioChRelease(g_mixer[i].chid);
		sceKernelDeleteSema(g_mixer[i].mutex);
		g_mixer[i].thid = -1;
		g_mixer[i].chid = -1;
		g_mixer[i].mutex = -1;
		if (g_mixer[i].sound) {
			sflSoundClose(g_mixer[i].sound);
			g_mixer[i].sound = 0;
		}
	}
	sceUtilityUnloadAvModule(PSP_AV_MODULE_AVCODEC);

	DEBUG_PRINTF("end\n");
}

int sflMixerPlaySound(int slot, sflSound *sound)
{
	if (slot != SFL_MIXER_NEXT_SLOT) {
		sflMixerLock(slot);
		if (g_mixer[slot].sound) {
			DEBUG_PRINTF("slot%d full\n", slot);
			sflMixerUnlock(slot);
			return -1;
		}
	} else {
		int i;
		for(i = 0; i < SFL_MIXER_NUM_SLOT; ++i) {
			sflMixerLock(i);
			//DEBUG_PRINTF("lock %d\n", i);
			if (g_mixer[i].sound == 0) {
				break;
			}
		}

		slot = i;
		for(i = 0; i < slot; ++i) {
			//DEBUG_PRINTF("unlock %d\n", i);
			sflMixerUnlock(i);
		}

		if (slot >= SFL_MIXER_NUM_SLOT) {
			DEBUG_PRINTF("all slot full\n");
			return -1;
		}
	}

	assert(0 <= slot && slot < SFL_MIXER_NUM_SLOT && g_initialized);

	// 複数スロットで同じsoundを再生するときはcloneしないと
	// 正しく再生できない
	g_mixer[slot].sound = sflSoundClone(sound);
	if (!g_mixer[slot].sound) {
		DEBUG_PRINTF("sflSoundClone failed\n");
		sflMixerUnlock(slot);
		return -1;
	}
	//g_mixer[slot].sound = sound;

	g_mixer[slot].playing = 1;
	sflMixerUnlock(slot);

	sceKernelWakeupThread(g_mixer[slot].thid);

	return slot;
}

void sflMixerPauseSound(int slot)
{
	if (slot == SFL_MIXER_ALL_SLOT) {
		int i;
		for(i = 0; i < SFL_MIXER_NUM_SLOT; ++i)
			sflMixerPauseSound(i);

		return;
	}

	assert(0 <= slot && slot < SFL_MIXER_NUM_SLOT && g_initialized);

	sflMixerLock(slot);
	if (!g_mixer[slot].sound) {
		sflMixerUnlock(slot);
		return;
	}

	g_mixer[slot].playing = 0;

	sflMixerUnlock(slot);
}

void sflMixerResumeSound(int slot)
{
	if (slot == SFL_MIXER_ALL_SLOT) {
		int i;
		for(i = 0; i < SFL_MIXER_NUM_SLOT; ++i)
			sflMixerResumeSound(i);

		return;
	}

	assert(0 <= slot && slot < SFL_MIXER_NUM_SLOT && g_initialized);

	sflMixerLock(slot);
	if (!g_mixer[slot].sound) {
		sflMixerUnlock(slot);
		return;
	}

	g_mixer[slot].playing = 1;

	sflMixerUnlock(slot);

	sceKernelWakeupThread(g_mixer[slot].thid);
}

void sflMixerStopSound(int slot)
{
	if (slot == SFL_MIXER_ALL_SLOT) {
		int i;
		for(i = 0; i < SFL_MIXER_NUM_SLOT; ++i)
			sflMixerStopSound(i);

		return;
	}

	assert(0 <= slot && slot < SFL_MIXER_NUM_SLOT && g_initialized);

	sflMixerLock(slot);
	if (!g_mixer[slot].sound) {
		sflMixerUnlock(slot);
		return;
	}

	sflSoundClose(g_mixer[slot].sound);
	g_mixer[slot].playing = 0;
	g_mixer[slot].sound = 0;
	if (g_mixer[slot].finishCB) {
		sflMixerCallback tmp = g_mixer[slot].finishCB;
		g_mixer[slot].finishCB = 0;
		tmp(slot, g_mixer[slot].finishCBArgs, g_mixer[slot].finishCBArgp);
	}

	sflMixerUnlock(slot);
}

int sflMixerIsPlaying(int slot)
{
	assert(0 <= slot && slot < SFL_MIXER_NUM_SLOT && g_initialized);

	sflMixerLock(slot);
	int res = g_mixer[slot].playing;
	sflMixerUnlock(slot);

	return res;
}

int sflMixerIsPaused(int slot)
{
	assert(0 <= slot && slot < SFL_MIXER_NUM_SLOT && g_initialized);
	return sflMixerIsPlaying(slot);
}

int sflMixerIsStopped(int slot)
{
	assert(0 <= slot && slot < SFL_MIXER_NUM_SLOT && g_initialized);

	sflMixerLock(slot);
	int res = (g_mixer[slot].sound == 0);
	sflMixerUnlock(slot);

	return res;
}

void sflMixerSetVolume(int slot, int left, int right)
{
	if (slot == SFL_MIXER_ALL_SLOT) {
		int i;
		for(i = 0; i < SFL_MIXER_NUM_SLOT; ++i)
			sflMixerSetVolume(i, left, right);

		return;
	}

	assert(0 <= slot && slot < SFL_MIXER_NUM_SLOT && g_initialized);

	if (left < -SFL_MIXER_VOLUME_MAX) {
		left = -SFL_MIXER_VOLUME_MAX;
	} else if (SFL_MIXER_VOLUME_MAX < left) {
		left = SFL_MIXER_VOLUME_MAX;
	}
	if (right < -SFL_MIXER_VOLUME_MAX) {
		right = -SFL_MIXER_VOLUME_MAX;
	} else if (SFL_MIXER_VOLUME_MAX < right) {
		right = SFL_MIXER_VOLUME_MAX;
	}
	g_mixer[slot].volumeLeft = left;
	g_mixer[slot].volumeRight = right;
}

int sflMixerSetPositionBySamples(int slot, int offset, int whence)
{
	assert(0 <= slot && slot < SFL_MIXER_NUM_SLOT && g_initialized);
	sflMixerLock(slot);

	if (!g_mixer[slot].sound) {
		DEBUG_PRINTF("no sound set on slot%d\n", slot);
		sflMixerUnlock(slot);
		return -1;
	}

	int res = sflSoundSeek(g_mixer[slot].sound, offset, whence);

	sflMixerUnlock(slot);

	return res;
}

int sflMixerSetPositionBySeconds(int slot, float offset, int whence)
{
	return sflMixerSetPositionBySamples(slot, offset * 44100.0f, whence);
}

int sflMixerGetPositionBySamples(int slot)
{
	assert(0 <= slot && slot < SFL_MIXER_NUM_SLOT && g_initialized);
	sflMixerLock(slot);

	if (!g_mixer[slot].sound) {
		DEBUG_PRINTF("no sound set on slot%d\n", slot);
		sflMixerUnlock(slot);
		return -1;
	}

	int pos = sflSoundTell(g_mixer[slot].sound);
	sflMixerUnlock(slot);
	return pos;
}

float sflMixerGetPositionBySeconds(int slot)
{
	return (float)sflMixerGetPositionBySamples(slot) * DURATION_PER_SAMPLE;
}

void sflMixerSetCallback(int slot, sflMixerCallback callback, int args, void *argp)
{
	if (slot == SFL_MIXER_ALL_SLOT) {
		int i;
		for(i = 0; i < SFL_MIXER_NUM_SLOT; ++i)
			sflMixerSetCallback(i, callback, args, argp);

		return;
	}

	assert(0 <= slot && slot < SFL_MIXER_NUM_SLOT && g_initialized);

	sflMixerLock(slot);

	g_mixer[slot].finishCB = callback;
	g_mixer[slot].finishCBArgs = args;
	g_mixer[slot].finishCBArgp = argp;

	sflMixerUnlock(slot);
}

int sflMixerSoundThread(unsigned int args, void *argp)
{
	DEBUG_PRINTF("start\n");
	const int slot = *(int*)argp;
	int index = 0;

	if (sceIoChdir(g_cwd) < 0) {
		DEBUG_PRINTF("sceIoChdir failed, cwd=%s\n", g_cwd);
	}

	while(g_terminate == 0) {
		sflMixerLock(slot);

		if (g_mixer[slot].playing == 0 || g_mixer[slot].sound == 0) {
			sflMixerUnlock(slot);
			sceKernelSleepThread();
			continue;
		}

		int fill = 0;
		while(fill < NUM_SAMPLES) {
			assert(g_mixer[slot].playing && g_mixer[slot].sound);
			int request = NUM_SAMPLES - fill;
			size_t res = sflSoundRead(g_mixer[slot].sound, g_buffer[slot][index][fill], request);
			if (res == request)
				break;

			fill += res;

			sflMixerStopSound(slot);

			if (!g_mixer[slot].playing || g_mixer[slot].sound == 0) {
				memset(&g_buffer[slot][index][fill], 0, (NUM_SAMPLES - fill) * sizeof(short) * 2);
				break;
			}
		}

		sflMixerUnlock(slot);
		sceAudioOutputPannedBlocking(g_mixer[slot].chid, g_mixer[slot].volumeLeft, g_mixer[slot].volumeRight, g_buffer[slot][index]);
		index = (index + 1) % NUM_BUFFERS;
	}

	DEBUG_PRINTF("end\n");
	sceKernelExitThread(0);
	return 0;
}

