/*
 *  NXP LPC2388 MP3/AAC/WAV file player with JPEG photo frame feature
 */
#include <t_services.h>
#include <string.h>
#include "kernel_id.h"
#include "ff.h"
#include "player.h"
#include "touch.h"
#include "lcd.h"
#include "mempool.h"

#define	MAX_SONGS	50
#define	MAX_ALBUMS	50
char song_files[MAX_SONGS][15] __attribute__((section(".etherram")));
char album_dirs[MAX_ALBUMS][15] __attribute__((section(".etherram")));
UB   file_types[MAX_SONGS] __attribute__((section(".etherram")));
char slide_path[30];    /* Photo slide pathname */
char img_path[30];      /* Music album image pathname */

Music_Data music_data __attribute__((section(".etherram")));
static FILINFO finfo __attribute__((section(".etherram")));

static Player_Status pstatus;

extern FATFS	F_Object;

/*
 * Display next JPEG slide file.
 * Note that we assume all JPEG files are placed under /DCIM
 * directory. This is not compatible with normal Digital camera
 * file format. It should be fixed sometime in future.
 */
int next_slide(Player_Status *pst)
{
    FRESULT result;
    char *fn;

    if (pst->slide_no == 0) {
        result = f_opendir(&pst->slide_dir, "/DCIM");
        if (result != FR_OK) {
            syslog(LOG_NOTICE, "f_opendir --> RESULT = %d", result);
            pst->slide_no = -1;
            return -1;
        }
    }
    while ((f_readdir(&pst->slide_dir, &finfo) == FR_OK) &&
            finfo.fname[0]) {
        fn = finfo.fname;
        if (strstr(fn, ".jpg") || strstr(fn, ".JPG")) {
            str_printf(slide_path, "/DCIM/%s", fn);
syslog(LOG_NOTICE, "%d: path = %s", pst->slide_no, slide_path);
            expand_jpeg(slide_path, 1);
            pst->slide_timer = 0;
            pst->slide_no++;
            return pst->slide_no;
        }
    }
    pst->slide_no = 0;
    return 0;
}

/*
 * Count how many album (or directories) we have.
 * We assume any directories under root (except /DCIM) are
 * music album directories.
 */
int scan_album(Player_Status *pst)
{
    FRESULT result;
    char *fn, *cp;
    int nfiles = 0;
    DIR AlbumDir;

    result = f_opendir(&AlbumDir, "/");
    if (result != FR_OK) {
        syslog(LOG_NOTICE, "f_opendir --> RESULT = %d", result);
        return 0;
    }

    while ((f_readdir(&AlbumDir, &finfo) == FR_OK) &&
        	finfo.fname[0]) {
        if (finfo.fattrib & AM_DIR) {
            fn = finfo.fname;
            if (strncmp(fn, "DCIM", 4)) {
                cp = album_dirs[nfiles];
                memcpy(cp, fn, 13);
                nfiles++;
                if (nfiles == MAX_ALBUMS)
                    break;
            }
        }
    }
    return nfiles;
}

/*
 * Read TOC in current album directory. We also try to find album image.
 */
int read_toc(Player_Status *pst)
{
    FRESULT result;
    int nfiles = 0;
    int fsize = 100000;	/* about 10K large */
    char *fn, *cp;
    struct touch_event *nev;
    DIR TocDir;
    char path[30];

    /* Assume no album image */
    pst->img_flag = IMG_NONE;
    result = f_opendir(&TocDir, album_dirs[pst->cur_album -1]);
    if (result != FR_OK) {
        syslog(LOG_NOTICE, "f_opendir --> RESULT = %d", result);
        return 0;
    }

    while ((f_readdir(&TocDir, &finfo) == FR_OK) &&
        	finfo.fname[0]) {
        fn = finfo.fname;
        if ((strstr(fn, ".jpg") || strstr(fn, ".JPG"))) {
            /* This is JPEG file. It may or may not a album image file */
            if (finfo.fsize < fsize) {
                /* We'll use smallest JPEG file as album image */
                fsize = finfo.fsize;
                str_printf(img_path, "%s/%s", album_dirs[pst->cur_album -1], fn);
                pst->img_flag |= IMG_THERE; /* mark as found */
            }
            continue;
        }
        if (strstr(fn, ".mp3") || strstr(fn, ".MP3")) {
            file_types[nfiles] = TYPE_MP3;
        } else if (strstr(fn, ".m4a") || strstr(fn, ".M4A")) {
            file_types[nfiles] = TYPE_M4A;
        } else if (strstr(fn, ".wav") || strstr(fn, ".WAV")) {
            file_types[nfiles] = TYPE_WAV;
        } else {	/* Skip unknown extention */
            continue;
        }
        /* we found a music file */
        cp = song_files[nfiles];
        memcpy(cp, fn, 13);
        nfiles++;
        if (nfiles == MAX_SONGS)
            break;
    }

    return nfiles;
}

UH ntohs(UH sval)
{
    UB h, l;

    h = (sval >> 8);
    l = sval & 0xff;
    return ((l << 8) | h);
}

void show_progress_bar()
{
    Player_Status *pst = &pstatus;

    if ((pst->p_state == S_PLAYING) && (pst->play_start > 0)) {
        SYSTIM ctime, tpassed, tleft;
        int progress;

        get_tim(&ctime);
        tpassed = ctime - pst->play_start;
        tleft = pst->title_length - tpassed;
        progress = tpassed * 200 / pst->title_length;
        display_progress_bar(progress, tpassed, tleft);
    }
}

Player_Status *get_player()
{
    return &pstatus;
}

/*
 * Open music track in current album
 */
FRESULT open_track(Player_Status *pst)
{
    FRESULT result;
    struct touch_event *nev;
    UB ftype;
    char path[35];

    str_printf(path, "%s/%s", album_dirs[pst->cur_album -1],
        song_files[pst->cur_track -1]);
    pst->file_type = file_types[pst->cur_track - 1];
    /*
     * Based on the file_type, determine necesary player sub task
     * which actually function as music decoder.
     */
    switch (pst->file_type) {
    case TYPE_MP3:
        pst->decoder = MP3_DECODER_TASK;
        break;
    case TYPE_M4A:
        pst->decoder = AAC_DECODER_TASK;
        break;
    case TYPE_WAV:
        pst->decoder = WAVE_DECODER_TASK;
        break;
    }
    result = f_open(&pst->music_file, path, FA_OPEN_EXISTING|FA_READ);
syslog(LOG_NOTICE, "%d: %s", pst->cur_track, path);
    if ( result == FR_OK) {
        /* Collect meta data embeded within a music file */
        pool_init();

        switch (pst->file_type) {
        case TYPE_MP3:
            result = get_id3_info(pst);
            break;
        case TYPE_M4A:
            result = get_aac_info(pst);
            break;
        case TYPE_WAV:
            result = get_wav_info(pst);
            break;
        }
        if (result == FR_OK) {
            if (!(pst->img_flag & IMG_SHOWN)) {
                wai_sem(SEM_LCD);
                lcd_reset_window();
                lcd_fill_rect(AIMAGE_X, AIMAGE_Y, 80, 80, C_WHITE);
                sig_sem(SEM_LCD);
                if (pst->img_flag & IMG_THERE) {
                    expand_jpeg(img_path, 0);
                }
                pst->img_flag |= IMG_SHOWN;
            }
            display_song_info(pst);
            (pst->pdevfuncs[pst->o_device]->init_output)();
        }
    } else {
        syslog(LOG_NOTICE, "f_open: %s --> %d\n", path, result);
    }
    return result;
}

static void stop_decoder(Player_Status *pst)
{
    FLGPTN ptn;
    FRESULT result;

    set_flg(FLG_STOP_REQ, 0x01);
    wai_flg(FLG_STOP_RES, 0x01, TWF_ORW, &ptn);
    pst->play_start = 0;
syslog(LOG_NOTICE, "decoder_stopped");
    pst->p_state = S_READY;
}

void select_usb_output()
{
    Player_Status *pst;

    pst = &pstatus;
    pst->o_device = O_USB;
}

/*
 * Player_task() is heart of this application.
 * We receive player command through DTQ_PCOM and
 * initiates neccessary player sub-task which is responsible
 * for decoding job.
 */
void player_task()
{
    int pcom;
    Player_Status *pst;
    FRESULT result;
    SYSTIM ctime;

    pst = &pstatus;
    pst->p_state = S_NODISC;
    pst->o_device = O_CODEC;
    pst->o_volume = 5;		/* Default volume level */
    pst->slide_no = 0;

    for (;;) {
        syscall(rcv_dtq(DTQ_PCOM, (VP_INT *)&pcom));
syslog(LOG_NOTICE, "command %d @ status %d", pcom, pst->p_state);

        switch (pst->p_state) {
        case S_NODISC:
            if (pcom == C_INSERT) { /* Disk has inserted */
                dly_tsk(300);
                result = f_mount(0, &F_Object);
                if (result != FR_OK) {
                    syslog(LOG_NOTICE, "f_mount: %d", result);
                    break;
                }
                /* Scan album names */
                pst->p_state = S_LOADING;
                if (pst->max_album = scan_album(pst)) {
                    pst->cur_album = 1;
                    snd_dtq(DTQ_PCOM, (VP_INT) C_READTOC);
                }
                /* display first slide file */
                (void)next_slide(pst);
            }
            break;
        case S_LOADING:
            switch (pcom) {
            case C_REMOVE:
                pst->p_state = S_NODISC;
                pst->cur_track = pst->max_track = 0;
                break;
            case C_READTOC:
                if (pst->max_track = read_toc(pst)) {
                    pst->cur_track = 1;
syslog(LOG_NOTICE, "%d songs found in disc.", pst->max_track);
                    pst->p_state = S_READY;
                    if (pst->tmode == TMODE_MP3) {
                        /* Start playing first music file */
                        snd_dtq(DTQ_PCOM, (VP_INT) C_PLAY);
                    }
                }
                break;
            }
            break;
        case S_READY:
            switch (pcom) {
            case C_REMOVE:
                pst->p_state = S_NODISC;
                pst->cur_track = pst->max_track = 0;
                break;
            case C_PLAY:
                if (open_track(pst) == FR_OK) {
                    pst->p_state = S_PLAYING;
                    /* Activate music file decoder task. */
                    act_tsk(pst->decoder);
                } else {
                    syslog(LOG_NOTICE, "can't open MP3 file.");
                }
                break;
            case C_NEXT:
                if (pst->cur_track < pst->max_track) {
                    pst->cur_track++;
                }
                break;
            case C_PREV:
                if (pst->cur_track > 1) {
                    pst->cur_track--;
                }
                break;
            case C_SLIDE:
                if (next_slide(pst) <= 0) {
                    (void)next_slide(pst);
                }
                break;
            }
            break;
        case S_PLAYING:
            switch (pcom) {
            case C_NEXT:
                if (pst->cur_track < pst->max_track) {
                    stop_decoder(pst);
                    pst->cur_track++;
                    if (open_track(pst) == FR_OK) {
                        pst->p_state = S_PLAYING;
                        act_tsk(pst->decoder);
                    } else {
                        syslog(LOG_NOTICE, "can't open MP3 file.");
                    }
                } else {
                    snd_dtq(DTQ_PCOM, (VP_INT)C_NEXT_ALBUM);
                }
                break;
            case C_PREV:
                stop_decoder(pst);
                if (pst->cur_track > 1) {
                    pst->cur_track--;
                    if (open_track(pst) == FR_OK) {
                        pst->p_state = S_PLAYING;
                        act_tsk(pst->decoder);
                    } else {
                        syslog(LOG_NOTICE, "can't open MP3 file.");
                    }
                } else {
                    pst->p_state = S_READY;
                }
                break;
            case C_REWIND:
                stop_decoder(pst);
                if (open_track(pst) == FR_OK) {
                    act_tsk(pst->decoder);
                    pst->p_state = S_PLAYING;
                }
                break;
            case C_PAUSE:
                sus_tsk(pst->decoder);
syslog(LOG_NOTICE, "SUSPEND");
                get_tim(&ctime);
                pst->play_suspend = ctime;
                pst->p_state = S_PAUSING;
                break;
            case C_STOP:
                stop_decoder(pst);
                pst->p_state = S_READY;
                break;
            case C_NEXT_ALBUM:
                stop_decoder(pst);
                if (pst->cur_album < pst->max_album) {
                    pst->cur_album++;
                } else {
                    pst->cur_album = 1;
                }
                pst->p_state = S_LOADING;
                snd_dtq(DTQ_PCOM, (VP_INT) C_READTOC);
                break;
            case C_PREV_ALBUM:
                stop_decoder(pst);
                if (pst->cur_album > 1) {
                    pst->cur_album--;
                } else {
                    pst->cur_album = pst->max_album;
                }
                pst->p_state = S_LOADING;
                snd_dtq(DTQ_PCOM, (VP_INT) C_READTOC);
                break;
            }
            break;
        case S_PAUSING:
            switch (pcom) {
            case C_PLAY:
syslog(LOG_NOTICE, "RESUME");
                (pst->pdevfuncs[pst->o_device]->restart_output)();
                get_tim(&ctime);
                pst->play_start += ctime - pst->play_suspend;
                rsm_tsk(pst->decoder);
                pst->p_state = S_PLAYING;
                break;
            case C_STOP:
                (pst->pdevfuncs[pst->o_device]->restart_output)();
                rsm_tsk(pst->decoder);
                stop_decoder(pst);
                pst->cur_track = 1;
                pst->p_state = S_READY;
                break;
            case C_REWIND:
                (pst->pdevfuncs[pst->o_device]->restart_output)();
                rsm_tsk(pst->decoder);
                pst->p_state = S_PLAYING;
syslog(LOG_NOTICE, "REWIND");
                stop_decoder(pst);
                if (open_track(pst) == FR_OK)
                    act_tsk(pst->decoder);
                pst->p_state = S_PLAYING;
                break;
            case C_NEXT:
            case C_PREV:
                (pst->pdevfuncs[pst->o_device]->restart_output)();
                rsm_tsk(pst->decoder);
                pst->p_state = S_PLAYING;
                dly_tsk(20);
                snd_dtq(DTQ_PCOM, (VP_INT) pcom);
                break;
            }
            break;
        }
    }
}

void player_command(int argc, char **argv)
{
    Player_Status *pst = &pstatus;

    if (argc > 1) {
        char *cp = argv[1];
        if (strncmp(cp, "insert", 6) == 0) {
            snd_dtq(DTQ_PCOM, (VP_INT) C_INSERT);
        }
        else if (strncmp(cp, "remove", 6) == 0) {
            snd_dtq(DTQ_PCOM, (VP_INT) C_REMOVE);
        }
        else if (strncmp(cp, "play", 4) == 0) {
            snd_dtq(DTQ_PCOM, (VP_INT) C_PLAY);
        }
        else if (strncmp(cp, "prev", 4) == 0) {
            snd_dtq(DTQ_PCOM, (VP_INT) C_PREV);
        }
        else if (strncmp(cp, "next", 4) == 0) {
            snd_dtq(DTQ_PCOM, (VP_INT) C_NEXT);
        }
        else if (strncmp(cp, "rewind", 6) == 0) {
            snd_dtq(DTQ_PCOM, (VP_INT) C_REWIND);
        }
        else if (strncmp(cp, "pause", 5) == 0) {
            snd_dtq(DTQ_PCOM, (VP_INT) C_PAUSE);
        }
        else if (strncmp(cp, "stop", 4) == 0) {
            snd_dtq(DTQ_PCOM, (VP_INT) C_STOP);
        }
        else if (strncmp(cp, "codec", 5) == 0) {
            pst->o_device = O_CODEC;
        }
        else if (strncmp(cp, "usb", 3) == 0) {
            pst->o_device = O_USB;
        }
    } else {
        cons_printf("Status: %d, Track: %d/%d\n",
                pst->p_state, pst->cur_track, pst->max_track);
#ifdef VDEBUG
        cons_printf("output = %d\n", pst->o_device);
#endif
        if (pst->p_state == S_PLAYING) {
            SYSTIM ctime, tpassed;

            get_tim(&ctime);
            tpassed = ctime - pst->play_start;
            cons_printf("%d/%d (%d%%)\n",
                tpassed, pst->title_length, tpassed * 100/ pst->title_length);
        }
    }
}
