#include "sfl.h"
#include "sflSound.h"
#include "sflMixer.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/syslimits.h>
#include <pspkernel.h>
#include <pspdisplay.h>
#include <pspdebug.h>
#include <pspctrl.h>
#include <pspthreadman.h>

#include <string>
#include <vector>
#include <algorithm>

#define VER 0
#define REV 4

PSP_MODULE_INFO("sound player sample", PSP_MODULE_USER, VER, REV);
PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER | PSP_THREAD_ATTR_VFPU);
PSP_HEAP_SIZE_KB(-1024);

#define MUSIC_DIR "ms0:/MUSIC/"

struct playlist_t {
	std::vector<std::string> filename;
	sflSound *current;
	sflSound *next;
	unsigned int index;
	int thid;
	playlist_t(): filename(), current(0), next(0), index(0), thid(-1) {}
};

int SetupCallbacks(void);

static int g_running = 1;
static char g_cwd[PATH_MAX + 1];

int loadSound(SceSize args, void *argp)
{
	sceIoChdir(g_cwd);
	struct playlist_t *playlist = *(struct playlist_t**)argp;

	sflSoundClose(playlist->current);
	playlist->current = playlist->next;
	
	unsigned int start = playlist->index;
	playlist->index = (playlist->index + 1) % playlist->filename.size();
	while(playlist->index != start) {
		DEBUG_PRINTF("loading %s start\n", playlist->filename[playlist->index].c_str());
		playlist->next = sflSoundOpen(playlist->filename[playlist->index].c_str());
		if (playlist->next) {
			DEBUG_PRINTF("loading %s end\n", playlist->filename[playlist->index].c_str());
			break;
		}
		DEBUG_PRINTF("can't open %s\n", playlist->filename[playlist->index].c_str());
		playlist->index = (playlist->index + 1) % playlist->filename.size();
	}

	sceKernelExitThread(0);
	return 0;
}

void playplaylist(int slot, int args, void *argp)
{
	struct playlist_t *playlist = (struct playlist_t*)argp;

	if (playlist->filename.size() > 1) {
		if (playlist->thid >= 0) {
			sceKernelWaitThreadEnd(playlist->thid, 0);
			sceKernelDeleteThread(playlist->thid);
		}

		if (!playlist->next) {
			DEBUG_PRINTF("playlist->next = null\n");
			return;
		}

		sflMixerPlaySound(slot, playlist->next);

		// すぐに戻らないとギャップレス再生できないので
		// 次のファイルの読み込みは別のスレッドに投げる
		playlist->thid = sceKernelCreateThread("loadSound", loadSound, 0x12, 0x10000, PSP_THREAD_ATTR_USER, 0);
		if (playlist->thid < 0) {
			DEBUG_PRINTF("sceKernelCreateThread failed 0x%08x\n", playlist->thid);
			return;
		}
	
		int res = sceKernelStartThread(playlist->thid, sizeof(&playlist), &playlist);
		if (res < 0) {
			DEBUG_PRINTF("sceKernelStartThread failed 0x%08x\n", res);
			return;
		}
	} else {
		sflMixerPlaySound(slot, playlist->current);
	}
	
	sflMixerSetCallback(slot, playplaylist, 0, playlist);
}

int main(int argc, char *argv[])
{
	pspDebugScreenInit();
	SetupCallbacks();
	atexit(sceKernelExitGame);

	getcwd(g_cwd, PATH_MAX + 1);

	if (sflMixerInit() < 0) {
		DEBUG_PRINTF("sflMixerInit failed\n");
		exit(0);
	}

	// MUSIC_DIRの直下にある*.mp3を探す
	SceIoDirent dir;
	memset(&dir, 0, sizeof(SceIoDirent));
	SceUID fd = sceIoDopen(MUSIC_DIR);
	if (fd < 0) {
		DEBUG_PRINTF("can't open directory '%s'", MUSIC_DIR);
		exit(0);
	}
	struct playlist_t playlist; 
	while(sceIoDread(fd, &dir) > 0) {
		if (!(dir.d_stat.st_attr & FIO_SO_IFREG)) {
			continue;
		}

		int len = strlen(dir.d_name);
		if (len <= 4 || string_compare(&dir.d_name[len-3], "MP3") != 0) {
			continue;
		}

		playlist.filename.push_back(std::string(MUSIC_DIR) + dir.d_name);
	}
	sceIoDclose(fd);

	// ファイル名でソート
	std::sort(playlist.filename.begin(), playlist.filename.end());

	playlist.current = sflSoundOpen(playlist.filename[0].c_str());
	if (playlist.filename.size() > 1) {
		playlist.next = sflSoundOpen(playlist.filename[1 % playlist.filename.size()].c_str());
		++playlist.index;
	} else {
		playlist.next = 0;
	}

	int slot = sflMixerPlaySound(SFL_MIXER_NEXT_SLOT, playlist.current);
	sflMixerSetCallback(slot, playplaylist, 0, &playlist);

	int volume = SFL_MIXER_VOLUME_MAX;
	
	unsigned int lastButtons = 0;
	SceCtrlData pad;
	sceCtrlSetSamplingCycle(0);
	sceCtrlSetSamplingMode(0);

	while(g_running) {
		sceCtrlPeekBufferPositive(&pad, 1);
		if (pad.Buttons != lastButtons) {
			if (pad.Buttons & PSP_CTRL_CIRCLE) {
				// callbackが呼び出されるので次の曲の再生が開始される
				sflMixerStopSound(slot);
			}

			if (pad.Buttons & PSP_CTRL_CROSS) {
				if (sflMixerIsPlaying(slot)) {
					sflMixerPauseSound(slot);
				} else {
					sflMixerResumeSound(slot);
				}
			}
			
			if (pad.Buttons & PSP_CTRL_UP) {
				volume += 1024;
				if (volume > SFL_MIXER_VOLUME_MAX) {
					volume = SFL_MIXER_VOLUME_MAX;
				}
				sflMixerSetVolume(slot, volume, volume);
			}

			if (pad.Buttons & PSP_CTRL_DOWN) {
				volume -= 1024;
				if (volume < 0) {
					volume = 0;
				}
				sflMixerSetVolume(slot, volume, volume);
			}

			if (pad.Buttons & PSP_CTRL_LEFT) {
				sflMixerSetPositionBySeconds(slot, -10.0f, SEEK_CUR);
			}

			if (pad.Buttons & PSP_CTRL_RIGHT) {
				sflMixerSetPositionBySeconds(slot, 10.0f, SEEK_CUR);
			}

			if (pad.Buttons & PSP_CTRL_START) {
				g_running = 0;
			}

			lastButtons = pad.Buttons;
		}

		pspDebugScreenSetXY(0, 0);
		pspDebugScreenPrintf("position: %d\n", sflMixerGetPositionBySamples(slot));		
		pspDebugScreenPrintf("volume: %d\n", volume);
		pspDebugScreenPrintf("Press CIRCLE to play next sound\n");
		pspDebugScreenPrintf("Press CROSS to %s sound\n", sflMixerIsPlaying(slot) ? "pause" : "resume");
		pspDebugScreenPrintf("Press UP/DOWN to up/down sound volume\n");		
		pspDebugScreenPrintf("Press LEFT/RIGHT to back/forward 10sec\n");
		pspDebugScreenPrintf("Press START to exit\n");

		sceDisplayWaitVblankStart();
	}

	sflMixerSetCallback(SFL_MIXER_ALL_SLOT, 0, 0, 0);
	sflMixerStopSound(SFL_MIXER_ALL_SLOT);
	if (playlist.current)
		sflSoundClose(playlist.current);

	if (playlist.next)
		sflSoundClose(playlist.next);

	sflMixerQuit();

	return 0;
}

// Exit callback
int exit_callback(int arg1, int arg2, void *common)
{
	g_running = 0;
	return 0;
}

// Callback thread
int CallbackThread(SceSize args, void *argp)
{
	int cbid;
	cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL);
	sceKernelRegisterExitCallback(cbid);
	sceKernelSleepThreadCB();
	return 0;
}

// Sets up the callback thread and returns its thread id
int SetupCallbacks(void)
{
	int thid = 0;
	thid = sceKernelCreateThread("update_thread", CallbackThread, 0x11, 0xFA0, 0, 0);
	if(thid >= 0) {
		sceKernelStartThread(thid, 0, 0);
	}
	return thid;
}

