#include <pspkernel.h>
#include <pspiofilemgr.h>
#include <pspaudio.h>
#include <pspmodulemgr.h>
#include <psploadcore.h>
#include <psppower.h>
#include <pspctrl.h>
#include <pspmscm.h>
#include <pspdisplay.h>
#include <systemctrl.h>

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include <time.h>

#include "playback.h"
#include "main.h"
#include "init.h"

//#include "conf.h"

#include "hw.h"
#include "hw_aa3.h"
#include "hw_at3.h"
#include "hw_mp3.h"

PSP_MODULE_INFO("Music_prx", 0x1000, 1, 1);

char music_root[64];
int game=0;

int in_game_mute = 0;
SceUID main_thid;
//MusicConf config;
//for on screen display
char str_buf[MAXPATH+1];
int blit_mode_timer = 0;
int blit_ready_timer = DISPLAY_TIMER_AMT;
int blit_debug_timer = 0;
int hw_init = 0;

SceUID display_thid;

Playlist kmusic;
Playlist *music;

int menuDone=0;
int showMusicMenu=0;
SceCtrlData pad;
unsigned int pad_old;

void blit(char *musicMsg)
{
	if(game)
	{
		gameBlit(musicMsg);
	}
	else
	{
		vshBlit(musicMsg);
	}
}

int module_start(SceSize args, void *argp)
{
    main_thid = sceKernelCreateThread("MAIN_Thread", main_thread, THREAD_PRIORITY+3, 0x4000, 0, NULL);
   	sceKernelStartThread(main_thid, args, argp);

    sceKernelExitDeleteThread(0);
    return 0;
}

int module_reboot_before(SceSize args, void *argp)
{
    sceKernelTerminateDeleteThread(main_thid);
    sceKernelTerminateDeleteThread(display_thid);
    sceKernelTerminateDeleteThread(music->mus_thid);

    TerminatePlaylist();

    return 0;
}

//returns number of music files in MUSIC_DIR and all subdirs 
int CountMusicFiles(char* dir)
{
	int fd;
	int music_cnt = 0;
	SceIoDirent d_dir;
	char tmp[MAXPATH];
	char ext[5];

	memset(&d_dir,0,sizeof(SceIoDirent));//prevents a crash
	fd = sceIoDopen(dir);

	if(fd >= 0)
	{
		while((sceIoDread(fd, &d_dir) > 0))
		{
			if(FIO_S_ISDIR(d_dir.d_stat.st_mode) == 0) //not a directory
			{
                		memcpy(ext, d_dir.d_name + strlen(d_dir.d_name) - 4, 5);
                		if(!stricmp(ext, ".mp3") || !stricmp(ext, ".at3")
                 		||!stricmp(ext, ".aa3") || !stricmp(ext, ".oma")
                 		||!stricmp(ext, ".omg") ) //if it is an music file
                    		music_cnt++;
				printf("music_cnt = %i\n", music_cnt);
			} 
			else if(FIO_S_ISDIR(d_dir.d_stat.st_mode)) //handle sub dirs
			{
				sprintf(tmp, "%s%s/", dir, d_dir.d_name);
				music_cnt += CountMusicFiles(tmp);
				printf("SubDir Spotted\n\n");
			}
		}
		sceIoDclose(fd);
	}
	return music_cnt;
}

//gets the name of an music file as a offset from the first music file in a directory and all subdirs
//filename is 262 (MAXPATH) bytes
int GetMusicFileName(char* dir,char* filename,int num,int basenum)
{
	int fd;
	int music_cnt;
	int music_dir_cnt;
	SceIoDirent d_dir;
	char tmp[MAXPATH];
	char ext[5];

	char tmpSong[128];

	music_cnt = basenum;

	memset(&d_dir,0,sizeof(SceIoDirent));//prevents a crash
	fd = sceIoDopen(dir);

	if(fd >= 0)
	{
		while((sceIoDread(fd, &d_dir) > 0))
		{
			//if((d_dir.d_stat.st_attr & FIO_SO_IFDIR) == 0)//not a directory
			if(FIO_S_ISDIR(d_dir.d_stat.st_mode) == 0)
			{

                memcpy(ext, d_dir.d_name + strlen(d_dir.d_name) - 4, 5);
                if(!stricmp(ext, ".mp3") || !stricmp(ext, ".at3")
                 ||!stricmp(ext, ".aa3") || !stricmp(ext, ".oma")
                 ||!stricmp(ext, ".omg") ) //if it is an music file
                {
                    if(music_cnt == num)
                    {
                        sprintf(filename, "%s%s", dir, d_dir.d_name);
			printf("file found : %s%s\n", dir, d_dir.d_name);
			strcpy(tmpSong, "Playing : ");
			strcat(tmpSong, d_dir.d_name);
			blit(tmpSong);
                        music_cnt = -1;
                        break;
                    }
                    music_cnt++;
                }
			} 
            //else if(d_dir.d_stat.st_attr & FIO_SO_IFDIR)//handle sub dirs
		else if(FIO_S_ISDIR(d_dir.d_stat.st_mode))
            {
                sprintf(tmp, "%s%s/", dir, d_dir.d_name);
                music_dir_cnt = GetMusicFileName(tmp, filename, num, music_cnt);
                if(music_dir_cnt == -1) //found file
                    break;
                music_cnt = music_dir_cnt;
            }
		}

		sceIoDclose(fd);
	}
    return music_cnt;
}

void InitPlaylist()
{
    music->omg_count = CountMusicFiles(OMG_AUDIO_DIR);
    music->count = CountMusicFiles(music_root) + music->omg_count;

    music->memid = sceKernelAllocPartitionMemory(1, "PLYLST_MEM", PSP_SMEM_Low, music->count*sizeof(int), NULL);
    music->random_played = sceKernelGetBlockHeadAddr(music->memid);

    music->offset = 0;
	music->resume = 0;

    music->pause = PLAYBACK_PAUSE_SONG;
	music->change_track = 0;

    music->loop = LOOP_NONE;

    ResetPlaylist();

    //create playlist thread
   	music->pl_thid = sceKernelCreateThread("Playlist_Thread", playlist_thread, THREAD_PRIORITY+2, 0x4000, 0, NULL);
   	if(music->pl_thid >= 0)
   		sceKernelStartThread(music->pl_thid, 0, NULL);
}

void TerminatePlaylist()
{
    sceKernelTerminateDeleteThread(music->pl_thid); 
    memset(music->file, 0, sizeof(music->file));
    sceKernelFreePartitionMemory(music->memid);
}

void ResetPlaylist()
{
int i;
    music->index = 0;
    music->random_index = 0;

    for(i = 0; i < music->count; i++)
        music->random_played[i] = -1; //played file not = to -1
}

int playlist_thread(SceSize args, void *argp)
{
	int i;
    char ext[5];
    char file_type;
    int ms_in;
    SceKernelThreadEntry music_thread = NULL;

    srand(time(NULL));

    while(1)
    {
        if (music->count)
        {
            if (music->random && (music->random_index >= music->count))
                ResetPlaylist(); 

            if ((!music->resume) && (music->loop != LOOP_SONG)) //not resuming music playback, load a new file
            {
                 //random file from dir
                if (music->random)
                {
                    music->index = rand()%music->count;

                    //check to see if this file was played before
                    for(i = music->random_index; i>=0; i--)
                    {
                        if (music->index == music->random_played[i])//if rand already played, pick a new one and recheck
                        {
                            music->index = rand()%music->count;
                            i = music->random_index;
                        }
                    }

                    music->random_played[music->random_index] = music->index;
                    music->random_index++;

                    if (music->index >= music->omg_count)
                        GetMusicFileName(music_root, music->file, music->index - music->omg_count, 0);
                    else
                        GetMusicFileName(OMG_AUDIO_DIR, music->file, music->index, 0);
                }
                else
                {
                    if (music->index >= music->omg_count)
                        GetMusicFileName(music_root, music->file, music->index - music->omg_count, 0);
                    else
                        GetMusicFileName(OMG_AUDIO_DIR, music->file, music->index, 0);

                    music->index++;

                    if (music->index >= music->count)
                       ResetPlaylist();
                }//if (music->random)
            }//if (!music->resume && music->loop == LOOP_NONE)

            music->title[0] = '\0'; //reset so the right title/filename is displayed

            if (music->loop == LOOP_IGNORE) //finished changing the track, reset the loop
                music->loop = LOOP_SONG;

        }//if (music->count)
        else
        {   /* if no files are in the playlist, sleep, 
            since the only way this can be fixed is by reloading */
            sceKernelSleepThreadCB();
        }

        memcpy(ext, music->file + strlen(music->file) - 4, 5);//get file extension

        //get the id3 title so the osd will have a real name to display 
        if (!stricmp(ext, ".aa3") || !stricmp(ext, ".oma") || !stricmp(ext, ".omg"))
            GetOMGTitle(music->file, music->title);

        //delay init'ing the hw decoder until play is pressed
        if (hw_init == 0) 
        {                     
            if (music->pause == PLAYBACK_PAUSE_SONG)
                music->pause = PLAYBACK_PAUSED;

            while( music->pause == PLAYBACK_PAUSED)
            {   //we must handle next/prev here too
                if ((music->change_track == PREV_SONG) || (music->change_track == NEXT_SONG)) 
                {
                    if (music->change_track == PREV_SONG)
                    { 
                        music->index -= 2;

                        if (music->index < 0)
                            music->index += music->count; //allows moving back in a loop
                    }

                    break;
                }
                sceKernelDelayThreadCB(DELAY_THREAD_AMT);
            }

            if ( music->change_track != 0)
            {
                music->change_track = 0;
                music->offset = 0;
                music->resume = 0;

                if (music->loop == LOOP_SONG)
                    music->loop = LOOP_IGNORE;

                continue;
            }
            hw_init = 1;
            HWInit();
        }
        
        //check to see if the music library is already loaded (keeps games from locking up)
        if ( FindProc("sceAvcodec_wrapper","sceAudiocodec",0x5B37EB1D)
          || FindProc("sceAudiocodec_Driver","sceAudiocodec",0x5B37EB1D) )
            music->init = 1;

        if (!stricmp(ext, ".mp3")) //if it is an mp3 file
            music_thread = HW_MP3PlayFile;
        else if (!stricmp(ext, ".at3")) //at3/at3+ file
            music_thread = HW_AT3PlayFile;
        else if (!stricmp(ext, ".aa3") || !stricmp(ext, ".oma") || !stricmp(ext, ".omg") )
        {
            file_type = GetOMGFileType(music->file);
            if (file_type == TYPE_AT3)
              music_thread = HW_AA3PlayFile;
            else if (file_type == TYPE_MP3)
                music_thread = HW_MP3PlayFile;
            else //invalid format
            {
                WaitMSReady();//wait until system is ready, this can happen due to suspend
                continue;
            }
        }

        music->flags = PLAYBACK_PLAYING;

        music->mus_thid = sceKernelCreateThread("music_thread", music_thread, THREAD_PRIORITY, 0x4000, 0, NULL);
        if (music->mus_thid >= 0)
            sceKernelStartThread(music->mus_thid, 0, NULL);

        //handle next, previous, pause resume
        while(1)//playing
        {
            if (music->pause == PLAYBACK_PAUSE_SONG)
            {
                sceKernelSuspendThread(music->mus_thid);
                music->pause = PLAYBACK_PAUSED;
            }
            else if (music->pause == PLAYBACK_RESUME_SONG)
            {
                sceKernelResumeThread(music->mus_thid);
                music->pause = PLAYBACK_PLAYING;
            }

            if ((music->change_track == PREV_SONG) || (music->change_track == NEXT_SONG)) 
            {
                if (music->change_track == PREV_SONG)
                { 
                    music->index -= 2;

                    if (music->index < 0)
                        music->index += music->count; //allows moving back in a loop
                }

                music->change_track = 0;
                music->offset = 0;
                music->resume = 0;

                if (music->loop == LOOP_SONG)
                    music->loop = LOOP_IGNORE;

                music->flags = PLAYBACK_DONE;//signal to the music thread that we are exiting

                if (music->pause == PLAYBACK_PAUSED)//resume thread if paused and then repause it 
                {
                    sceKernelResumeThread(music->mus_thid);
                    music->pause = PLAYBACK_PAUSE_SONG;
                }

                while(music->flags != PLAYBACK_CLEANED_UP)
                    sceKernelDelayThreadCB(DELAY_THREAD_AMT);//let it clean up

                sceKernelTerminateDeleteThread(music->mus_thid);
                break;
            }

            if (music->flags == PLAYBACK_CLEANED_UP)
            {
                sceKernelTerminateDeleteThread(music->mus_thid);
                do //check to see if the ms is ready (for suspend/resume)
                {
                    ms_in = MScmIsMediumInserted();
                    sceKernelDelayThreadCB(DELAY_THREAD_AMT);
                } while (ms_in <= 0);
                break;
            }

            sceKernelDelayThreadCB(DELAY_THREAD_AMT*5);
        } //while (1) //playing

    } //while (1) 

    return 0;
}

void musicMenu() 
{
	int ctrlWait = 0;
	menuDone=0;
	
	//showMusicMenu=1;
	sceKernelDelayThread(10*100000);
	
	while(!menuDone)
	{
		sceDisplayWaitVblankStart();
		
		sceCtrlPeekBufferPositive(&pad, 1);
		
		if(ctrlWait>0) ctrlWait--;
		
		if(!ctrlWait) 
		{

			if (pad.Buttons != pad_old)
			{	
		
				if(pad.Buttons & PSP_CTRL_START) 
				{
					if (music->pause == PLAYBACK_PAUSED)
					{
						music->pause = PLAYBACK_RESUME_SONG;
						blit("Resuming song...");
					}
					else if (music->pause == PLAYBACK_PLAYING)
					{
						music->pause = PLAYBACK_PAUSE_SONG;
						blit("Pausing song...");	
					}
					ctrlWait=15;
				}

				if(pad.Buttons & PSP_CTRL_UP) 
				{
                			if(music->volume<100)
						music->volume++;
						blit("Volume Up...");
				}

				if(pad.Buttons & PSP_CTRL_DOWN) 
				{
                			if(music->volume>0)
                    				music->volume--;
						blit("Volume Down...");
				}

				if(pad.Buttons & PSP_CTRL_LTRIGGER) 
				{
					if(!music->random)
					{
						music->change_track = PREV_SONG;
						//blit("Skipping To Previous Song...");
					}
					ctrlWait=15;
				}

				if(pad.Buttons & PSP_CTRL_RTRIGGER) 
				{
					music->change_track = NEXT_SONG;
					//blit("Skipping To Next Song...");
					ctrlWait=15;
				}
				
				if(pad.Buttons & PSP_CTRL_SELECT)
				{
                    			if (in_game_mute)
					{
						in_game_mute = 0;
						blit("In-Game Mute Disabled...");
                    			}
					else
					{ 
						in_game_mute = 1;
						blit("In-Game Mute Enabled...");
					}
					ctrlWait=15;
				}

				if(pad.Buttons & PSP_CTRL_LEFT) 
				{
					music->random = !music->random;
					ResetPlaylist();
					music->change_track = NEXT_SONG;
					blit("Random Mode enabled...");
					ctrlWait=15;
				}

				if(pad.Buttons & PSP_CTRL_RIGHT) 
				{
					if (music->loop == LOOP_NONE)
					{
						music->loop = LOOP_SONG;
						blit("Loop Mode Enabled...");
					}
					else if((music->loop == LOOP_SONG) || (music->loop == LOOP_IGNORE))
					{
						music->loop = LOOP_NONE;
						blit("Loop Mode Disabled...");
					}
					ctrlWait=15;
				}

				if(pad.Buttons & PSP_CTRL_CIRCLE) 
				{
					//nkThreadResume(sceKernelGetThreadId());
					//showMusicMenu=0;
					menuDone=1;
					blit("Exiting music menu...");
				}
			}
		}
	}
	pad_old = pad.Buttons;
	sceKernelDelayThread(1000);
}

u32 vshComboButton_i, vshMusicButton_i;
char tmpVshComboChar[10];
char tmpVshMusicChar[10];
u32 gameComboButton_i, gameMusicButton_i;
char tmpGameComboChar[10];
char tmpGameMusicChar[10];

int main_thread(SceSize args, void *argp)
{
    SceCtrlData pad;
    int power_cbid;

	SceModule *pMod;
	SceModule *qMod;

    //wait until memory stick is readable
    WaitMSReady();

    music = &kmusic;

    pMod = sceKernelFindModuleByName("Music_prx");//TODO Test in 1.5,
    music_text_addr = pMod->text_size;//pMod definition is wrong...
    music_text_end = pMod->text_size+pMod->data_size;

	qMod = sceKernelFindModuleByName("vsh_module");
	if(!qMod) {
		game=1;
		printf("game mode spotted");
	}
 
	char *path;

	if(game)
	{
		const char* tmpdir = gameMusicDir();
		strcpy(music_root, tmpdir);
		printf("music_dir = %s\n", music_root);

		path = strrchr(music_root, '/');
		if(path == NULL)
		{
			strcat(music_root, "/");
		}

		const char* gameComboChar = gameComboButton(); 
		strcpy(tmpGameComboChar, gameComboChar);
		gameComboButton_i=gameTranslateButtonsByName(tmpGameComboChar);
		const char* gameMusicChar = gameMusicButton(); 
		strcpy(tmpGameMusicChar, gameMusicChar);
		gameMusicButton_i=gameTranslateButtonsByName(tmpGameMusicChar);
	}
	else
	{
		const char* tmpdir = vshMusicDir();
		strcpy(music_root, tmpdir);
		printf("music_dir = %s\n", music_root);

		path = strrchr(music_root, '/');
		if(path == NULL)
		{
			strcat(music_root, "/");
		}

		const char* vshComboChar = vshComboButton(); 
		strcpy(tmpVshComboChar, vshComboChar);
		vshComboButton_i=vshTranslateButtonsByName(tmpVshComboChar);
		const char* vshMusicChar = vshMusicButton(); 
		strcpy(tmpVshMusicChar, vshMusicChar);
		vshMusicButton_i=vshTranslateButtonsByName(tmpVshMusicChar);
	 }

    power_cbid = sceKernelCreateCallback("powercb", (SceKernelCallbackFunction)PowerCallback, NULL);
    scePowerRegisterCallback(15, power_cbid);

    music->is_vsh = 0;
    music->mus_thid = -1;
    music->init = 0;

    InitPatches();
    InitPlaylist();

	music->volume=100;

	musicMenu();

    while(1)
    {
        sceKernelDelayThreadCB(5*DELAY_THREAD_AMT);//needs less running time

        sceCtrlPeekBufferPositive(&pad, 1); 

	if (pad.Buttons != pad_old)
	{
		if(game)
		{
			if((pad.Buttons & (gameMusicButton_i | gameComboButton_i)) == (gameMusicButton_i | gameComboButton_i))		
		        {
				blit("Entering music menu...");
				musicMenu();
		
		        }
		}
		else
		{			
			if((pad.Buttons & (vshMusicButton_i | vshComboButton_i)) == (vshMusicButton_i | vshComboButton_i))		
		        {
				blit("Entering music menu...");
				musicMenu();
		
		        }
		}

        }
	pad_old = pad.Buttons;
    }
	return 0;
}

void WaitMSReady()//wait until ms is ready
{
int ret;
    ret = MScmIsMediumInserted();
    while(ret <= 0)
    {
        sceKernelDelayThreadCB(DELAY_THREAD_AMT);
        ret = MScmIsMediumInserted();
    }
    sceKernelDelayThreadCB(DELAY_THREAD_SEC);
}

SceUID LoadStartModule(char *modname, int partition)
{
    SceKernelLMOption option;
    SceUID modid;

    memset(&option, 0, sizeof(option));
    option.size = sizeof(option);
    option.mpidtext = partition;
    option.mpiddata = partition;
    option.position = 0;
    option.access = 1;

    modid = sceKernelLoadModule(modname, 0, &option);
    if (modid < 0)
        return modid;

    return sceKernelStartModule(modid, 0, NULL, NULL, NULL);
}

void PowerCallback(int unknown, int powerInfo)
{
    if ( (powerInfo & PSP_POWER_CB_POWER_SWITCH)
       ||(powerInfo & PSP_POWER_CB_SUSPENDING)
       ||(powerInfo & PSP_POWER_CB_STANDBY) )
    {
        music->flags = PLAYBACK_RESET;
    }
}

int GetID3TagSize(char *fname)
{
    SceUID fd;
    char header[10];
    int size = 0;
    fd = sceIoOpen(fname, PSP_O_RDONLY, 0777);
    if (fd < 0)
        return 0;

    sceIoRead(fd, header, sizeof(header));
    sceIoClose(fd);

    if (!strncmp((char*)header, "ea3", 3) || !strncmp((char*)header, "EA3", 3)
      ||!strncmp((char*)header, "ID3", 3))
    {
        //get the real size from the syncsafe int
        size = header[6];
        size = (size<<7) | header[7];
        size = (size<<7) | header[8];
        size = (size<<7) | header[9];

        size += 10;

        if (header[5] & 0x10) //has footer
            size += 10;
         return size;
    }
    return 0;
}

// uses ID3(ea3) TIT2 to get the song's name (this is only for sonicstage copied
// audio, so we don't display 1XXXXXXX.OMA as the name)
void GetOMGTitle(char *fname, char *title)
{
    SceUID memid;
    unsigned char *pBuffer;
    int i,x;
    int current_char = 0;
    int size = 0;
    int maxsize = 0;
    unsigned char character;
    SceUID fd;

    maxsize = GetID3TagSize(fname);

    fd = sceIoOpen(fname, PSP_O_RDONLY, 0777);
    if (fd < 0)
        return;

    memid = sceKernelAllocPartitionMemory(1, "id3", PSP_SMEM_Low, maxsize, NULL);
    pBuffer = (u8*)sceKernelGetBlockHeadAddr(memid);

    size = sceIoRead(fd, pBuffer, maxsize);
    sceIoClose(fd);
    if (size <= 0)
    {
        sceKernelFreePartitionMemory(memid);
        return;
    }
    
    for (i = 0; i < size; i++)
    {
        //if this is a TIT2 frame
        if ( !strncmp((char*)pBuffer+i, "TIT2", 4))
        {
            size = pBuffer[i+4]; //size of title
            size = (size<<7) | pBuffer[i+5];
            size = (size<<7) | pBuffer[i+6];
            size = (size<<7) | pBuffer[i+7];

            if (size > MAXPATH)
                size = MAXPATH;

            memset(title, 0, MAXPATH);

            for (x = 1; x < size; x++) //this allows us to parse latin-encoded 
            {                          //unicode strings, though it could be better
                character = pBuffer[i+x+10]; //buf offset + str offset + frame header size
                if ((character >= 0x20) && (character <= 0x7f))
                {  
                    title[current_char] = character;
                    current_char++;
                }
            }
            break;
        }
    }

    sceKernelFreePartitionMemory(memid);
}

char GetOMGFileType(char *fname)
{
    SceUID fd;
    int size;
    char ea3_header[0x60];

    size = GetID3TagSize(fname);

    fd = sceIoOpen(fname, PSP_O_RDONLY, 0777);
    if (fd < 0)
        return TYPE_UNK;

    sceIoLseek32(fd, size, PSP_SEEK_SET);

    if (sceIoRead(fd, ea3_header, 0x60) != 0x60)
        return TYPE_UNK;

    sceIoClose(fd);

    if (strncmp(ea3_header, "EA3", 3) != 0)
        return TYPE_UNK;

    switch (ea3_header[3])
    {
        case 1:
        case 3:
            return TYPE_AT3;
        case 2:
            return TYPE_MP3;
        default:
            return TYPE_UNK;
    }
}

