#include <pspkernel.h>
#include <pspctrl.h>
#include <pspdebug.h>
#include <pspaudio.h>
#include <pspaudiolib.h>
#include <psppower.h>
#include <pspdisplay.h>
#include <string.h>
#include <stdio.h>
#include <sys/stat.h>
#include "cueparser.h"

//#include <tremor/ivorbiscodec.h>
//#include <tremor/ivorbisfile.h>
#include <vorbis/codec.h>
#include <vorbis/vorbisfile.h>

#include <FLAC/stream_decoder.h>

#define printf pspDebugScreenPrintf
#define OUTPUT_BUFFER 32768
#define MAX_STRING 256
PSP_MODULE_INFO("Cueplayer", 0, 1, 0);
PSP_MAIN_THREAD_ATTR(PSP_THREAD_ATTR_USER|PSP_THREAD_ATTR_VFPU);
//PSP_HEAP_SIZE_MAX();
PSP_HEAP_SIZE_KB(-256);
extern void displayDisable();
extern void displayEnable();


//Semaphores
static SceUID buffer_empty_sema;
static SceUID buffer_full_sema;

//global flags
static int running_flag = 0;
static int running_flag1 = 1;
static int eos_flag = 0;
static int seek_length = 0;
static int buffer_flip = 0;
static int backlight_enable = 1;
static int filetype_ogg = 0;
static int filetype_flac = 0;

//global parameters and data
static int audio_vol = 0x4000; // 1/2 max volume
static FLAC__uint64 total_samples = 0;
static unsigned int sample_rate = 0;
static unsigned int bps = 0;
static unsigned int channels = 0;
static char pcmout1[OUTPUT_BUFFER];
static char pcmout2[OUTPUT_BUFFER];
static long pcmlen1;
static long pcmlen2;
static int audio_channel = 0;
static int player_paused = 0;
static unsigned long frame_played = 0;

static FLAC__StreamDecoder* decoder = NULL;
static FLAC__StreamDecoderInitStatus init_status;

// This is for ogg
FILE* ogg_file;
OggVorbis_File oggvorbis_file;
int bitstream;


CueData* cuedata = NULL;
static int current_track = 0;
static char current_performer[MAX_STRING];
static char current_title[MAX_STRING];

// Extern Functions:
extern char *RequestFile (char *initialPath);

//Common Routines

/* Exit callback */
int exit_callback(int arg1, int arg2, void *common) {
   sceKernelExitGame();
   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, PSP_THREAD_ATTR_USER, 0);
   if(thid >= 0) {
      sceKernelStartThread(thid, 0, 0);
   }

   return thid;
}

//Function declarations:
int find_current_track();
int read_bookmark();
int write_bookmark();
int reset_global_var();

//some helper function

int reset_global_var()
{
    running_flag = 0;
    running_flag1 = 1;
    eos_flag = 0;
    seek_length = 0;

    //global parameters and data
    audio_vol = 0x4000; // 1/2 max volume
    total_samples = 0;
    sample_rate = 0;
    bps = 0;
    channels = 0;
    buffer_flip = 0;
    audio_channel = 0;
    player_paused = 0;
    frame_played = 0;

    decoder = NULL;

    current_track = 0;
    return 0;
}


//FLAC decoding callbacks
//required by
//FLAC__StreamDecoderInitStatus FLAC__stream_decoder_init_file(       
//      FLAC__StreamDecoder *        decoder,
//      const char *    filename,
//      FLAC__StreamDecoderWriteCallback    write_callback,
//      FLAC__StreamDecoderMetadataCallback     metadata_callback,
//      FLAC__StreamDecoderErrorCallback    error_callback,
//      void *      client_data
//      )   

FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder* decoder, const FLAC__Frame* frame, const FLAC__int32* const buffer[], void* client_data)
{
    //const FLAC__uint32 total_size = (FLAC__uint32)(total_samples * channels * (bps / 8));
    FLAC__int16* fillbuf;
    int i;

    (void)decoder, (void)client_data;

    frame_played += frame -> header.blocksize; //count the frame


    if(total_samples == 0){
        printf("Error total samples is 0??\n");
        return -1;
    }

    if(channels != 2 || bps != 16){
        printf("Error Only support 16bit and 2 channel\n");
        return -1;
    }

    if(running_flag == 1)
        sceKernelWaitSema(buffer_empty_sema, 1, 0);
    else
        return 1;
    fillbuf = buffer_flip ? (FLAC__int16*)pcmout2 : (FLAC__int16*)pcmout1;

    for(i = 0; i < frame -> header.blocksize; i++){
        // fill 2 channel into odd and even continuous fillbuf
        fillbuf[i << 1] = (FLAC__int16)buffer[0][i];
        fillbuf[(i << 1) + 1] = (FLAC__int16)buffer[1][i];
    }
    if(buffer_flip){
        pcmlen2 = frame -> header.blocksize << 2;
    }
    else{
        pcmlen1 = frame -> header.blocksize << 2;
    }
    
    sceKernelSignalSema(buffer_full_sema, 1);
    
    return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}

void metadata_callback(const FLAC__StreamDecoder* decoder, const FLAC__StreamMetadata* metadata, void* client_data)
{
    (void)decoder, (void)client_data;
    if(metadata -> type == FLAC__METADATA_TYPE_STREAMINFO){
        total_samples = metadata -> data.stream_info.total_samples;
        sample_rate = metadata -> data.stream_info.sample_rate;
        channels = metadata -> data.stream_info.channels;
        bps = metadata -> data.stream_info.bits_per_sample;
    }
}

void error_callback(const FLAC__StreamDecoder* decoder, FLAC__StreamDecoderErrorStatus status, void* client_data)
{
    (void)decoder, (void)client_data;
    printf("Error callback invoked: %s!\n", FLAC__StreamDecoderErrorStatusString[status]);
}

//Output audio thread

int audio_output(SceSize args, void* argp)
{
    int playlen;
    char* playbuf;
    while(running_flag){
        if(player_paused){
            sceKernelDelayThread(10 * 1000);
            continue;
        }
        sceKernelWaitSema(buffer_full_sema, 1, 0);
        if(eos_flag || !running_flag){
            break;
        }
        playlen = buffer_flip ? pcmlen1 : pcmlen2;
        playbuf = buffer_flip ? pcmout1 : pcmout2;
        buffer_flip ^= 1;
        sceKernelSignalSema(buffer_empty_sema, 1);
        sceAudioSetChannelDataLen(audio_channel, playlen / 4);
        sceAudioOutputBlocking(audio_channel, audio_vol, playbuf);
    }
    sceKernelExitDeleteThread(0);
    return 0;
}

int end_decoder()
{
    running_flag = 0;
    FLAC__stream_decoder_delete(decoder);
    sceKernelSignalSema(buffer_empty_sema, 1);
    sceKernelSignalSema(buffer_full_sema, 1);
    sceKernelDelayThread(1000 * 1000);
    sceAudioChRelease(audio_channel);
    sceKernelDelayThread(1000 * 1000);
    return 0;
}


//FLAC playing Main function
int playflac(char* filename)
{
    //SceUID sceKernelCreateSema    (   const char *     name,
    //                                  SceUInt     attr,
    //                                  int     initVal,
    //                                  int     maxVal,
    //                                  SceKernelSemaOptParam *     option   
    //                                  )   

    SceUID audio_thid;
    FLAC__bool decoding_status = true;
    
    buffer_empty_sema = sceKernelCreateSema("buffer_empty_sema", 0, 1, 1, 0);
    buffer_full_sema = sceKernelCreateSema("buffer_full_sema", 0, 0, 1, 0);
    
    if((decoder = FLAC__stream_decoder_new()) == NULL){
        printf("cannot create flac stream decoder!\n");
        sceKernelDelayThread(2*1000*1000);
        return -1;
    }
    FLAC__stream_decoder_set_md5_checking(decoder, true);
    init_status = FLAC__stream_decoder_init_file(decoder, filename, write_callback, metadata_callback, error_callback, NULL);
    if(init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK){
        printf("Init decoder error\n");
        return -1;
    }
    memset(pcmout1, 0, OUTPUT_BUFFER);
    memset(pcmout2, 0, OUTPUT_BUFFER);
    pcmlen1 = 0;
    pcmlen2 = 0;
    eos_flag = 0;
    running_flag = 1;
    sceKernelSignalSema(buffer_empty_sema, 1);
    sceKernelSignalSema(buffer_full_sema, 0);
    audio_channel = sceAudioChReserve(audio_channel, OUTPUT_BUFFER / 4, PSP_AUDIO_FORMAT_STEREO);
    audio_thid = sceKernelCreateThread("audio_output", audio_output, 0x16, 0x1800, PSP_THREAD_ATTR_USER, NULL);
    if(audio_thid < 0){
        printf("audio thread creation error!\n");
        return -1;
    }
    sceKernelStartThread(audio_thid, 0, NULL);
    while(running_flag){
        if(player_paused == 1){
            sceKernelDelayThread(5 * 1000);

            continue;
        }

        if(seek_length){
            sceKernelSignalSema(buffer_full_sema, 0);

            if(FLAC__stream_decoder_seek_absolute(decoder, seek_length * sample_rate) == true){
                frame_played = seek_length * sample_rate;
                seek_length = 0;
                sceKernelDelayThread(50 * 1000);
            }
            else{
                printf("Seeking fail!");
            }
        }
        if(running_flag1 == 1) 
            decoding_status = FLAC__stream_decoder_process_single(decoder);
        if(decoding_status == 0){
            break;
        }

    }
    end_decoder();
    reset_global_var();
    return 0;


    /*
    if(FLAC__stream_decoder_process_until_end_of_stream(decoder)){
        printf("SUCCESS END OF STREAM.\n");
        FLAC__stream_decoder_delete(decoder);
        fclose(fileptr);
        running_flag = 0;
        sceKernelSignalSema(buffer_empty_sema, 1);
        sceKernelSignalSema(buffer_full_sema, 1);
        sceKernelDelayThread(1000 * 1000);
        sceAudioChRelease(audio_channel);
        sceKernelDelayThread(1000 * 1000);
        return 0;
    }
    else{
        printf("Failed!\n");
        return -1;
    }
    */
}    

int ogg_buffer_filling(SceSize args, void* argp)
{
    int bytes, bytesred;
    int  fillbuf;

    while(running_flag){
        sceKernelWaitSema(buffer_empty_sema, 1, 0);

        if(seek_length != 0){
            sceKernelDelayThread(200 * 1000);
            ov_time_seek(&oggvorbis_file, (double)seek_length);
            sceKernelDelayThread(200 * 1000);
            seek_length = 0;
        }

        if(eos_flag || ! running_flag){
            break;
        }
        bytesred = 0;
        fillbuf = buffer_flip ? (int)pcmout2 : (int)pcmout1;
        while(bytesred < OUTPUT_BUFFER && running_flag && !eos_flag){
            bytes = ov_read(&oggvorbis_file, (char*)(fillbuf + bytesred), OUTPUT_BUFFER - bytesred, 0, 2, 1, &bitstream);
            if(bytes == 0){
                printf("e");
                eos_flag = 1;
                running_flag = 0;
                //ov_clear(&oggvorbis_file);
            }
            else if(bytes > 0){
                bytesred += bytes;
            }
        }
        if(buffer_flip == 1){
            pcmlen2 = bytesred;
        }
        else{
            pcmlen1 = bytesred;
        }
        sceKernelSignalSema(buffer_full_sema, 1);
        if(running_flag1 == 0){
            break;
        }
    }

    sceKernelExitDeleteThread(0);
    return 0;
}


int playogg(char* filename)
{
    vorbis_info* vi;
    int oggbuffer_thid = -1;
    SceUID audio_thid;
    buffer_empty_sema = sceKernelCreateSema("buffer_empty_sema", 0, 1, 1, 0);
    buffer_full_sema = sceKernelCreateSema("buffer_full_sema", 0, 0, 1, 0);


    ogg_file = fopen(filename, "r");
    if(ogg_file == NULL){
        return -1;
    }
    ov_open(ogg_file, &oggvorbis_file, NULL, 0);
    vi = ov_info(&oggvorbis_file, -1);
    sample_rate = vi -> rate;
    bps = vi -> bitrate_nominal / 1000;
    total_samples = (unsigned long)ov_time_total(&oggvorbis_file, -1) * sample_rate;

    memset(pcmout1, 0, OUTPUT_BUFFER);
    memset(pcmout2, 0, OUTPUT_BUFFER);
    pcmlen1 = 0;
    pcmlen2 = 0;
    
    eos_flag = 0;
    running_flag = 1;
    sceKernelSignalSema(buffer_empty_sema, 1);
    sceKernelSignalSema(buffer_full_sema, 0);
    audio_channel = sceAudioChReserve(audio_channel, OUTPUT_BUFFER / 4, PSP_AUDIO_FORMAT_STEREO);

    oggbuffer_thid = sceKernelCreateThread("ogg_buffer_filling", ogg_buffer_filling, 0x12, 0x1800, PSP_THREAD_ATTR_USER, NULL);
    if(oggbuffer_thid < 0){
        return -1;
    }
    sceKernelStartThread(oggbuffer_thid, 0, NULL);
    audio_thid = sceKernelCreateThread("audio_output", audio_output, 0x16, 0x1800, PSP_THREAD_ATTR_USER, NULL);
    if(audio_thid < 0){
        return -1;
    }
    sceKernelStartThread(audio_thid, 0, NULL);
    
    while(running_flag && ! eos_flag){
        frame_played = (unsigned long) (ov_time_tell(&oggvorbis_file) * sample_rate );

        sceKernelDelayThread(100 * 1000);
    }
    running_flag = 0;
    sceKernelSignalSema(buffer_empty_sema, 1);
    sceKernelSignalSema(buffer_full_sema, 1);
    sceKernelDelayThread(1 * 1000 * 1000);
    sceAudioChRelease(audio_channel);
    reset_global_var();
    return 0;
}






    




int seeking(int length)
{
    if(length == 0){
        length = 1;
    }
    seek_length = length;
    //frame_played =  length * sample_rate;
    sceKernelDelayThread(1000*100);

    return 0;
}

int find_current_track()
{
    int current_time = frame_played / sample_rate;
    int tmp_track = current_track;
    for(cuedata -> trackdata = cuedata -> track_head -> next; cuedata -> trackdata != NULL; cuedata -> trackdata = cuedata -> trackdata -> next){
        if(current_time > cuedata -> trackdata -> start_time){
            current_track = cuedata -> trackdata -> track_number;
            strcpy(current_title, cuedata -> trackdata -> title);
            strcpy(current_performer, cuedata -> trackdata -> performer);
        }
    }
    if(tmp_track != current_track){
        return 1;
    }
    else{
        return 0;
    }
}
/*
int seek_prev_track()
{
    int current_time = frame_played / sample_rate;
    for(cuedata -> trackdata = cuedata -> track_head -> next; cuedata -> trackdata != NULL; cuedata -> trackdata = cuedata -> trackdata -> next){
        if(current_time > cuedata -> trackdata -> start_time){
            if(cuedata -> trackdata -> prev != NULL){
                seeking(cuedata -> trackdata -> prev -> start_time);
                break;
            }
        }
    }
    return 0;
}
*/

int seek_prev_track()
{
    int i;
    if(current_track == 1){
        seeking(1);
    }
    for(i = 0, cuedata -> trackdata = cuedata -> track_head -> next; i < current_track -1 && cuedata -> trackdata != NULL; i++, cuedata -> trackdata = cuedata -> trackdata -> next);
    seeking(cuedata -> trackdata -> start_time);
    return 0;

}

int seek_next_track()
{
    int i;
    for(i = 0, cuedata -> trackdata = cuedata -> track_head -> next; i < current_track && cuedata -> trackdata -> next!= NULL; i++, cuedata -> trackdata = cuedata -> trackdata -> next);
    seeking(cuedata -> trackdata -> start_time);
    return 0;
}
/*
int seek_next_track()
{
    int current_time = frame_played / sample_rate;
    for(cuedata -> trackdata = cuedata -> track_head -> next; cuedata -> trackdata != NULL; cuedata -> trackdata = cuedata -> trackdata -> next){
        if(current_time > cuedata -> trackdata -> start_time){
            if(cuedata -> trackdata -> next != NULL){
                seeking(cuedata -> trackdata -> next -> start_time);
                break;
            }
        }
    }
    return 0;
}
*/

int player_control(SceSize args, void* argp)
{
    SceCtrlData pad;
    while(1){
        sceKernelDelayThread(1000*500);
        if(eos_flag == 1){
            break;
        }

        printf("FF: %lu / %lu \r", frame_played / sample_rate, total_samples / sample_rate);
        sceCtrlReadBufferPositive(&pad, 1);
        if(find_current_track() == 1){
            printf("\nCURRENT TRACK %d / %d\n", current_track, cuedata -> total_tracks);
            printf("TITLE: %s\n", current_title);
            printf("PERFORMER %s\n", current_performer);
        }
        if(pad.Buttons & PSP_CTRL_CROSS){
            printf("Return to start\n");
            //player_paused = 1;
            //sceKernelDelayThread(1000*20);
            //FLAC__stream_decoder_seek_absolute(decoder, 20 * 44100);
            seeking(1);
            //player_paused = 0;
        }
        if(pad.Buttons & PSP_CTRL_CIRCLE){
            if(player_paused == 0){
                player_paused = 1;
                printf("paused\n");
            }
            else{
                player_paused = 0;
                printf("unpaused\n");
            }
        }
        if(pad.Buttons & PSP_CTRL_TRIANGLE){
            write_bookmark();
            running_flag1 = 0;
            sceKernelDelayThread(500 * 1000);
            running_flag = 0;
            //sceKernelSignalSema(buffer_empty_sema, 1);
            sceKernelDelayThread(500 * 1000);
            sceKernelExitDeleteThread(0);
            //end_decoder();
            break; 
        }
        if(pad.Buttons & PSP_CTRL_LTRIGGER){
            seek_prev_track();
        }
        if(pad.Buttons & PSP_CTRL_RTRIGGER){
            seek_next_track();

        }
        if(pad.Buttons & PSP_CTRL_SQUARE){
            read_bookmark();
        }
        if(pad.Buttons & PSP_CTRL_START){
            if(backlight_enable == 1){
                displayDisable();
                backlight_enable = 0;
            }
            else{
                displayEnable();
                backlight_enable = 1;
            }
        }
    }
    sceKernelExitDeleteThread(0);

    return 0;
}

int write_bookmark()
{
    FILE* bmfile = fopen("bookmark", "w");
    if(bmfile == NULL){
        return -1;
    }
    fprintf(bmfile, "%lu\n", frame_played);
    fclose(bmfile);
    return 0;
}

int read_bookmark()
{
    unsigned long fpl;
    FILE* bmfile = fopen("bookmark", "r");
    if(bmfile == NULL){
        return -1;
    }

    fscanf(bmfile, "%lu\n", &fpl);
    frame_played = fpl;
    fclose(bmfile);
    seeking(fpl / sample_rate);
    return 0;
}

char* read_init_path()
{
    char* initpath = (char*)malloc(sizeof(char) * MAX_STRING);
    memset(initpath, 0, MAX_STRING);
    FILE* initpath_file = fopen("initpath", "r");
    SceUID dfd;
    if(initpath_file == NULL){
        strcpy(initpath, "ms0:/");
        fclose(initpath_file);
        initpath_file = fopen("initpath", "w");
        if(initpath_file == NULL){
            return NULL;
        }
        fprintf(initpath_file, "%s\n", initpath);
        fclose(initpath_file);
        return initpath;
    }
        
    else{
        fscanf(initpath_file, "%[^\n]\n", initpath);
        fclose(initpath_file);
        if(strlen(initpath) <= 3 || strlen(initpath) >= MAX_STRING){
            return NULL;
        }
        //here check the availablity of the initpath
        dfd = sceIoDopen(initpath);
        if(dfd >= 0){
            sceIoDclose(dfd);
        }
        else{
            strcpy(initpath, "ms0:/");
        }
        return initpath;
    }
}


int main()
{

    char filename[MAX_STRING];
    //char* cuefilename = "ms0:/psp/music/CDImage.cue";
    char * cuefilename;
    pspDebugScreenInit();
    SceUID pc_thid;
    SetupCallbacks();
    FILE* fp;
    SceUID modid;
    char* initpath;

    modid = pspSdkLoadStartModule("myLib.prx", PSP_MEMORY_PARTITION_KERNEL);
    if(modid < 0){
        printf("Cannot load myLib.prx\n");
        sceKernelDelayThread(3 * 1000 * 1000);
    }


   //start here 
    while(1){
        while(1){
            initpath = read_init_path();
            if(initpath == NULL){
                cuefilename = RequestFile("ms0:/");
            }
            else{
                cuefilename = RequestFile(initpath);
                free(initpath);
            }

            if(cuefilename == NULL){
                continue;
            }
            if(strstr(cuefilename, ".cue") != NULL){
                printf("GET: %s", cuefilename);
                break;
            }
        }
        pspDebugScreenClear();
        printf("############# Cueplayer SVN for PSP #############\n");
         

        filename[0] = 0;
        strcpy(filename, cuefilename);
        *(strstr(filename, ".cue")) = 0;
        strcat(filename, ".flac");
        
        fp = fopen(filename, "r");
        if(fp){
            fclose(fp);
            filetype_flac = 1;
            filetype_ogg = 0;
        }
        else{
            strcpy(filename, cuefilename);
            *(strstr(filename, ".cue")) = 0;
            strcat(filename, ".ogg");
            fp = fopen(filename, "r");

            if(fp){
                fclose(fp);
                filetype_flac = 0;
                filetype_ogg = 1;
            }
            else{
                printf("Cannot find corresponding flac file!\n");
                sceKernelDelayThread(1000 * 1000);
                continue;
            }
        }
        printf("Audio File name: %s\n", filename);

        
        cuedata = read_cuefile(cuefilename);
        printf("Title: %s\n", cuedata -> title);
        printf("Performer: %s\n", cuedata -> performer);
        

        pc_thid = sceKernelCreateThread("player_control", player_control, 0x11, 0x1800, PSP_THREAD_ATTR_USER, NULL);
        if(pc_thid > 0){
            sceKernelStartThread(pc_thid, 0, 0);
        }
        else{
            printf("player_control thread error\n");
        }
        printf("playing!........\n");
        if(filetype_flac){
            playflac(filename);
        }
        if(filetype_ogg){
            playogg(filename);
        }
        freeup_cuedata(cuedata);
        reset_global_var();

        //sceKernelExitGame();
    }
    return 0;
}
