/* 
   AudioLib Sample

   Demonstrates how to get sound working with minimal effort.

   Based on sdktest sample from pspsdk
*/

#include <pspkernel.h>
#include <pspdebug.h>
#include <pspaudiolib.h>
#include <pspaudio.h>
#include <pspdisplay.h>
#include <pspctrl.h>

#include <stdio.h>

#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <limits.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>

/* 
   This part of the code is more or less identical to the sdktest sample 
*/

/* Define the module info section */
PSP_MODULE_INFO("PSPABCHR", 0, 1, 1);
/* Define the main thread's attribute value (optional) */
PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER | THREAD_ATTR_VFPU);
/* Define printf, just to make typing easier */
#define printf	pspDebugScreenPrintf

/* Exit callback */
int exitCallback(int arg1, int arg2, void *common) {
	sceKernelExitGame();
	return 0;
}

/* Callback thread */
int callbackThread(SceSize args, void *argp) {
	int cbid;

	cbid = sceKernelCreateCallback("Exit Callback", (void*) exitCallback, 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;
}

/*
  Below this point is the interesting code in this sample
*/

const int SND_SILENCE=-1;
const int SND_OK=-2;
const int SND_NG=-3;
const int SND_PERARI=-4;
const int SND_PI=-5;
const int SND_KATAKATA=-6;

#define ENC_NUM_MAX (9+1)
#define SOUNDS_MAX (30)

char testname[200];
char encoders[ENC_NUM_MAX][200];
char sounds[SOUNDS_MAX][200];

int sounds_num = 1;
int enc_num = 1+1;

int play_raw_index[2] = {-1,-1};
int play_raw_point[2] = {0,0};
int play_raw_index_x = 0;
int play_raw_index_y = 1;

int now_testing = -1;
int is_stopping = 0;

int is_training = 1;

int test_candi = 0;
int test_sample = 0;

int test_correct_count = 0;
int test_all_count = 0;
int test_correct_count_total = 0;
int test_all_count_total = 0;

int tcc[ENC_NUM_MAX];
int tac[ENC_NUM_MAX];
int tsp[ENC_NUM_MAX];
int test_align[ENC_NUM_MAX];
time_t test_abx_time[ENC_NUM_MAX][SOUNDS_MAX];
int test_abx_errata[ENC_NUM_MAX][SOUNDS_MAX];



typedef struct {
        short l, r;
} sample_t;


struct raw_common{
 double length;
 char path[250];
};

struct raw_common RBANK[10+ENC_NUM_MAX*SOUNDS_MAX];
struct raw_common *rbank[10+ENC_NUM_MAX*SOUNDS_MAX];
FILE* fp[2];
int fpi = 0;

int init_raw_common(struct raw_common *rawc, char *path){
 int i = 0;
 while(path[i]){
 	rawc->path[i] = path[i];
	i++;
 }
 fp[fpi] = fopen(rawc->path,"rb");
 if(fp[fpi]==NULL){
  printf("%s not found.\n",path);
  return 1;
 }
 else{
  fseek(fp[fpi], 0, SEEK_END);
  rawc->length = ftell(fp[fpi])>>2;
  fclose(fp[fpi]);
  return 0;
 }
 fpi = 1-fpi;
}



/* This function gets called by pspaudiolib every time the
   audio buffer needs to be filled. The sample format is
   16-bit, stereo. */
void audioCallback(void* buf, unsigned int length, void *userdata) {
	sample_t* ubuf = (sample_t*) buf;
	int i,fpt;
	int exist_length = 0;
	fpt = fpi;
	if(play_raw_index[fpt]>=0){
		fseek(fp[fpt], play_raw_point[fpt]*4,SEEK_SET);
		exist_length = fread(buf, 4, length, fp[fpt]);
		play_raw_point[fpt] += exist_length;
		if(exist_length<length){
			play_raw_index[fpt] = -1;
			play_raw_point[fpt] = 0;
		}
	}
	
	for (i = 0;i<(length-exist_length);i++) {
		ubuf[i].l = 0;
		ubuf[i].r = 0;
	}

}


void renew_fp() {
	play_raw_index[1-fpi] = 10+test_sample*ENC_NUM_MAX;
	switch(now_testing){
		case -1:play_raw_index[1-fpi] = -1;break;
		case 0:play_raw_index[1-fpi] += 0;break;
		case 1:play_raw_index[1-fpi] += test_align[test_candi];break;
		case 2:play_raw_index[1-fpi] += (play_raw_index_x?test_align[test_candi]:0);break;
		case 3:play_raw_index[1-fpi] += (play_raw_index_y?test_align[test_candi]:0);break;
		default:play_raw_index[1-fpi] = -now_testing-2;break;
	}
	if(is_stopping)play_raw_index[1-fpi] = -1;
	
	if(play_raw_index[1-fpi]>=0){
		fclose(fp[1-fpi]);
		fp[1-fpi] = fopen(rbank[play_raw_index[1-fpi]]->path,"rb");
		if(fp[1-fpi]==NULL){
			printf("%s Not Found.\n",rbank[play_raw_index[1-fpi]]->path);
		}
	}
	fpi = 1-fpi;
	
}

void init_abchr() {
	int i,j;
	test_align[0] = 0;
	for(i=1;i<enc_num;i++){
		tcc[i] = tac[i] = 0;
		tsp[i] = 50;
		int rn = rand()%(enc_num-i);
		for(j=1;j<enc_num;j++){
			if(test_align[j]==0){
				if(rn){
					rn--;
				}
				else{
					test_align[j] = i;
					break;
				}
			}
		}
	}
}

void abchr_save_and_setup() {
	time_t timer;
	time(&timer);
	FILE* fp2;
	char filename[200];
	int log_number = 0;
	struct stat st;
	do{
		log_number++;
		filename[0] = 0;
		sprintf(filename,"%s_%s_log%d.txt",testname,sounds[test_sample],log_number);
	}
	while(stat(filename,&st)==0);
	
	fp2 = fopen(filename,"w");
	fprintf(fp2,"PSP abc/hr test v2.0\r\n");
	fprintf(fp2,"Finished time: %s\r\n", ctime(&timer));
	fprintf(fp2,"tested sample: %d\r\n\r\n", test_sample);
	pspDebugScreenSetXY(0,26);
	int i,j;
	
	for(i=1;i<enc_num;i++){
		printf(" %01d ",test_align[i]);
		fprintf(fp2,"%d ",test_align[i]);
	}
	printf("\r\n");
	fprintf(fp2,"\r\n");
	fprintf(fp2,"0 = 5_ 00/00 %s\r\n",rbank[10+ENC_NUM_MAX*test_sample]->path);

	for(i=1;i<enc_num;i++){
		printf("%02d ",tsp[test_align[i]]);
		fprintf(fp2,"%01d = %02d %02d/%02d ",i,tsp[i],tcc[i],tac[i]);//score 10-50
		fprintf(fp2,"%s\r\n",rbank[10+ENC_NUM_MAX*test_sample+i]->path);
	}
	fprintf(fp2,"\r\n\r\n");
	
	for(i=1;i<enc_num;i++){
		fprintf(fp2,"original vs %s\r\n",rbank[10+ENC_NUM_MAX*test_sample+i]->path);
		fprintf(fp2,"%d/%d\r\n",tcc[i],tac[i]);
		for(j=0;j<20;j++){
			fprintf(fp2,"%02d ",test_abx_errata[i][j]);
			fprintf(fp2,"%s \r\n",ctime(&test_abx_time[i][j]));
			test_abx_errata[i][j] = -1;
		}
		test_align[i] = 0;
		fprintf(fp2,"\r\n");
	}
	fclose(fp2);
	init_abchr();
}



void abx_save_and_setup() {
	test_all_count_total += test_all_count;
	test_correct_count_total += test_correct_count;
	if(is_training){//楙廗仺杮斣
		if(test_candi && tac[test_align[test_candi]]<20){
			is_training = 0;
			now_testing = SND_PERARI;
		}
		else{
			now_testing = SND_NG;
		}
	}
	else{//杮斣仺楙廗
		now_testing = SND_PERARI;
		is_training = 1;
	}
	test_correct_count = 0;
	test_all_count = 0;

}


void check_up_key() {
    static int oldButtons = 0;

	SceCtrlData pad;
	int changedButtons;

	sceCtrlReadBufferPositive(&pad, 1);

	changedButtons = pad.Buttons & (~oldButtons);
	if (changedButtons & PSP_CTRL_SQUARE) {
				now_testing = 0;
				play_raw_point[1-fpi] = 0;//play A
				renew_fp();
	}
	if (changedButtons & PSP_CTRL_TRIANGLE) {
				now_testing = 1;
				play_raw_point[1-fpi] = 0;//play B
				renew_fp();
	}
	
	if (changedButtons & PSP_CTRL_CROSS) {
				now_testing = 2;
				play_raw_point[1-fpi] = 0;//play X
				renew_fp();
	}
	if (changedButtons & PSP_CTRL_CIRCLE) {
				now_testing = 3;
				play_raw_point[1-fpi] = 0;//play Y
				renew_fp();
	}
	
	if (changedButtons & PSP_CTRL_START) {
		if(test_candi){
				if(is_stopping){
					is_stopping = 0;
				}
				else{
					if(now_testing>=0)is_stopping = 1;
				}
		}
		else{
			abchr_save_and_setup();
			init_abchr();
			now_testing = SND_KATAKATA;
			play_raw_point[1-fpi] = 0;
		}
		renew_fp();
	}
	
	if (changedButtons & PSP_CTRL_LTRIGGER) {//楙廗杮斣曄峏
		play_raw_point[1-fpi] = 0;
		abx_save_and_setup();
		
		
		renew_fp();
	}
	if ((changedButtons & PSP_CTRL_RTRIGGER)) {
		if(now_testing>=2 && test_candi){
			if(is_training==0){
				test_abx_errata[test_align[test_candi]][tac[test_align[test_candi]]] = (now_testing-2)*2+play_raw_index_y;
				time(&test_abx_time[test_align[test_candi]][tac[test_align[test_candi]]]);
			}
			if(now_testing-2==play_raw_index_y){
				if(is_training){
					test_correct_count++;
				}
				else{
					tcc[test_align[test_candi]]++;
				}
				now_testing = SND_OK;
			}
			else{
				now_testing = SND_NG;
			}
			play_raw_point[1-fpi] = 0;
			play_raw_index_x = rand()&1;
			play_raw_index_y = 1-play_raw_index_x;
			
			
			if(is_training){
				test_all_count++;
			}
			else{
				tac[test_align[test_candi]]++;
				if(tac[test_align[test_candi]]>=20 && is_training==0){
					abx_save_and_setup();
					now_testing = SND_PI;
				}
			}
			renew_fp();
		}

	}
	if (changedButtons & PSP_CTRL_UP) {
		if(test_candi==0){
			init_abchr();
			test_sample--;
			play_raw_point[1-fpi] = 0;
			if(test_sample<0){
				test_sample = 0;
				now_testing = SND_NG;
			}
			else{
				now_testing = SND_PERARI;
			}
			test_all_count_total = 0;
			test_correct_count_total = 0;
			renew_fp();
		}
		else{
			if(tsp[test_align[test_candi]]<50)tsp[test_align[test_candi]]++;
		}
	}
	if (changedButtons & PSP_CTRL_DOWN) {
		if(test_candi==0){
			init_abchr();
			test_sample++;
			play_raw_point[1-fpi] = 0;
			if(test_sample>=sounds_num){
				test_sample = sounds_num-1;
				now_testing = SND_NG;
			}
			else{
				now_testing = SND_PERARI;
			}
			test_all_count_total = 0;
			test_correct_count_total = 0;
			renew_fp();
		}
		else{
			if(tsp[test_align[test_candi]]>10)tsp[test_align[test_candi]]--;
		}
	}
	if (changedButtons & PSP_CTRL_LEFT) {
		if(is_training){
			test_candi--;
			if(test_candi<0)test_candi = 0;
			play_raw_point[1-fpi] = 0;
			test_all_count = 0;
			test_correct_count = 0;
			now_testing = SND_SILENCE;
			renew_fp();
		}
	}
	if (changedButtons & PSP_CTRL_RIGHT) {
		if(is_training){
			test_candi++;
			if(test_candi>=enc_num)test_candi = enc_num-1;
			play_raw_point[1-fpi] = 0;
			test_all_count = 0;
			test_correct_count = 0;
			now_testing = SND_SILENCE;
			renew_fp();
		}
	}
	oldButtons = pad.Buttons;
}

int main(void) {
	pspDebugScreenInit();
	setupCallbacks();
	int i,j;
	for(i=0;i<10+ENC_NUM_MAX*SOUNDS_MAX;i++){
		rbank[i] = &RBANK[i];
	}
	init_raw_common(rbank[0],"syssound/ok.raw");
	init_raw_common(rbank[1],"syssound/ng.raw");
	init_raw_common(rbank[2],"syssound/perari.raw");
	init_raw_common(rbank[3],"syssound/pi.raw");
	init_raw_common(rbank[4],"syssound/katakata.raw");
	
	FILE* fp3;
	fp3 = fopen("config.txt","r");
	char buf[256];
	if(fp3==NULL)printf("config.txt not found.\n");
	fgets( buf, 200, fp3 );
	if(strcmp(buf,"[testname]"))printf("config.txt error %s.\n",buf);
	fgets( testname, 200, fp3 );
	fgets( buf, 200, fp3 );
	if(strcmp(buf,"[encoders]"))printf("config.txt error %s.\n",buf);
	i = 0;
	while(1){
		fgets( buf, 200, fp3 );
		if(buf[0]!='['){
			strcpy(encoders[i++],buf);
		}
		else{
			break;
		}
	}
	enc_num = i;
	if(enc_num>ENC_NUM_MAX)printf("config.txt too many encoders.\n");
	if(strcmp(buf,"[sounds]"))printf("config.txt error %s.\n",buf);
	i = 0;
	while(1){
		fgets( buf, 200, fp3);
		if(ferror(fp3)){
			break;
		}
		else{
			strcpy(sounds[i++],buf);
		}
	}
	sounds_num = i;
	
	init_abchr();
	
	for(i=0;i<sounds_num;i++){
		for(j=0;j<enc_num;j++){
			char thepath[1024] = "";
			sprintf(thepath, "%s%s", sounds[i], encoders[j]);
			init_raw_common(rbank[10+i*ENC_NUM_MAX+j],thepath);
		}
	}
	
	srand((unsigned) time(NULL));
	
	pspAudioInit();
	pspAudioSetChannelCallback(0, audioCallback, NULL);

	sceCtrlSetSamplingCycle(0);
	sceCtrlSetSamplingMode(PSP_CTRL_MODE_DIGITAL);

	printf("PSP ABC/HR TEST v1.3 http://d.hatena.ne.jp/kamedo2/\n\n");



	while(1) {
		sceDisplayWaitVblankStart();
		pspDebugScreenSetXY(0,9);
		printf("               fpi =   %1d\n", fpi);
		printf("        sounds_num = %d\n", sounds_num);
		printf("      encoders_num = %d\n", enc_num);
		printf("    play_raw_index = %03d\n", play_raw_index[fpi]);
		printf("    play_raw_point = %7d\n", play_raw_point[fpi]);
		printf("       now_testing =  %2d\n", now_testing);
		printf("    t_sample,candi =  %2d    %1d\n", test_sample+10, test_candi);
		printf("       is_training =   %1d\n\n", is_training);

		printf("       test_count  =  %2d   %2d\n", test_correct_count,test_all_count);
		printf("  test_count_total = %3d  %3d\n\n\n", test_correct_count_total,test_all_count_total);
		for(j=0;j<enc_num;j++){
			if(j==test_candi){
				printf(" *");
			}
			else{
				printf("  ");
			}
			printf("%2d/%2d", tcc[test_align[j]], tac[test_align[j]]);
		}
		printf("\n\n");
		for(j=0;j<enc_num;j++){
			if(j==test_candi){
				printf(" *");
			}
			else{
				printf("  ");
			}
			printf("%2d   ", tsp[test_align[j]]);
		}
		check_up_key();
	}
	return 0;
}
