/*
 * Play WAVE file which has linear PCM format (44.1KHz, 16bit)
 */
#include <t_services.h>
#include <string.h>
#include "kernel_id.h"
#include "ff.h"
#include "player.h"
#include "mempool.h"

/* WAV file header structure */
typedef struct {
    char  riffid[4];	/* RIFF id */
    UW	fsize;	        /* File size - 8 */
    char  waveid[4];	/* WAVE id */
} waveheader;

typedef struct {
    char chunkID[4];
    UW	chunkSize;
} chunkHeader;

typedef struct {
    UH	wFormatTag;
    UH	wChannels;
    UW	dwSamplesPerSec;
    UW	dwAvgBytesPerSec;
    UH	wBlockAlign;
    UH	wBitsPerSample;
} FormatBody;

static int wav_sample_rate;

#define	LISTID	"LIST"
#define	FORMATID	"fmt "
#define	DATAID	"data"

typedef struct {
    char istr[4];
    int  ilen;
} iheader;

static void wav_reset(Music_Data *wavd)
{
    wavd->bytes_left = 0;
    wavd->nFrames = 0;
    wavd->read_ptr = wavd->music_buffer;
    wavd->offset = 0;
}

static int wav_fill_inbuffer(FIL *wavfile, Music_Data *wavd)
{
    int bytes_to_read, bytes_read;
    int result;

    if (wavd->bytes_left > PCM_BSIZE)
        return 0;
    if (wavd->bytes_left > 0) {
        memmove(wavd->music_buffer, wavd->read_ptr, wavd->bytes_left);
    }
    bytes_to_read = MUSIC_BUFSIZE - wavd->bytes_left;
    bytes_to_read /= 512;
    bytes_to_read *= 512;
#ifdef DEBUG
    cons_printf("%d\n", bytes_to_read);
    dly_tsk(1);
#endif

    result = f_read(wavfile, wavd->music_buffer + wavd->bytes_left, bytes_to_read, &bytes_read);
    if (result != FR_OK) {
        syslog(LOG_NOTICE, "fill_in f_read: %d", result);
        return -1;
    }

#ifdef DEBUG
    cons_printf("read %d\n", bytes_read);
    dly_tsk(1);
#endif
    if (bytes_read > 0) {
        wavd->read_ptr = wavd->music_buffer;
        wavd->bytes_left += bytes_read;
        wavd->offset += bytes_read;
    } else {
#ifdef DEBUG
        cons_printf("\r\033[KMaybe EOF? (%d --> %d)\n", bytes_to_read, bytes_read);
#endif
        return -1;
    }
    return 0;
}

/*
 * WAVE format file decoder task.
 */
void wavdecoder_task()
{
    FLGPTN ptn;
    Player_Status *pst;
    pcm_data *pcm;
    FIL *wav_file;
    SYSTIM tstart;
    int samprate, nbytes;
    ER err;
    FRESULT result;
    Music_Data *wavd;

    clr_flg(FLG_STOP_REQ, 0);
    clr_flg(FLG_STOP_RES, 0);

    pst = get_player();
    wav_file = &pst->music_file;
    samprate = wav_sample_rate;
    wavd = &music_data;
    wav_reset(wavd);

    ptn = 0;
    while (wav_fill_inbuffer(wav_file, wavd) == 0) {
        err = pol_flg(FLG_STOP_REQ, 0x03, TWF_ORW, &ptn);
#ifdef DEBUG
        if (err != E_TMOUT) {
            syslog(LOG_NOTICE, "pol_flg: %d, ptn = %d", err, ptn);
        }
#endif
        if (ptn)
            break;
        get_mpf(MPF_PCM, (VP)&pcm);
        if (wavd->nFrames == 0) {
            /*
             * This is first frame. Select output device.
             */
            pst->cur_rate = samprate;
            (pst->pdevfuncs[pst->o_device]->rate_set)(samprate);
            get_tim(&tstart);
            pst->play_start = tstart;
        }
        pcm->rate = samprate;
        if (wavd->bytes_left > PCM_BSIZE) {
            nbytes = PCM_BSIZE;
        } else {
            nbytes = wavd->bytes_left;
        }
        memcpy(pcm->raw_buffer, wavd->read_ptr, nbytes);
        wavd->read_ptr += nbytes;
        wavd->bytes_left -= nbytes;
        wavd->nFrames++;
        pcm->samples = nbytes/2;
        snd_dtq(DTQ_PCM, (VP_INT)pcm);
    }
    /* Send special frame indicating EOF */
    snd_dtq(DTQ_PCM, (VP_INT)NULL);
    syslog(LOG_NOTICE, "NULL sent to DTQ_PCM");
    result = f_close(wav_file);
    if (result != FR_OK)
        syslog(LOG_NOTICE, "f_close: %d", result);
    if (ptn == 0) {
        snd_dtq(DTQ_PCOM, (VP_INT) C_NEXT);
        wai_flg(FLG_STOP_REQ, 0x03, TWF_ORW, &ptn);
    }
}

static char *wbuffer;

/*
 * Decode and dump music infomation embedded within WAV file.
 */
int dump_list(Player_Status *pst, FIL *fp, int size)
{
    char istr[5];
    iheader ih;
    int len, nb;
    FRESULT result;

    bzero(istr, 5);
    f_read(fp, istr, 4, &nb);
    if (strncmp(istr, "INFO", 4)) {
        syslog(LOG_NOTICE, "no INFO");
        return -1;
    }
    size -= 4;
    while (size > 0) {
        result = f_read(fp, &ih, sizeof(ih), &nb);
        if (result != FR_OK || nb != sizeof(ih)) {
            syslog(LOG_NOTICE, "fread failed 1 (%d, %d)", result, nb);
            return -1;
        }
        size -= sizeof(ih);
        memcpy(istr, ih.istr, 4);
        len = ih.ilen;
        if (len & 1) len++;
        result = f_read(fp, wbuffer, len, &nb);
        if (result != FR_OK || nb != len) {
#ifdef VERBOSE
            syslog(LOG_NOTICE, "fread failed 2 (%d, %d)", len, nb);
#endif
            return -1;
        }
#ifdef VERBOSE
        syslog(LOG_NOTICE, "  %s: %s", istr, wbuffer);
#endif
        if (strncmp(istr, "IART", 4) == 0) {
            strncpy(pst->performer_name, wbuffer, MAX_PERFORMER_NAME);
        } else if (strncmp(istr, "INAM", 4) == 0) {
            strncpy(pst->song_name, wbuffer, MAX_SONG_NAME);
        } else if (strncmp(istr, "IPRD", 4) == 0) {
            strncpy(pst->album_name, wbuffer, MAX_ALBUM_NAME);
        }
        size -= len;
#ifdef VERBOSE
        dly_tsk(10);
#endif
    }
    return 0;
}

/*
 * Verify WAV format. We accept 44.1/48.0 KHz stereo only.
 */
int dump_format(FIL *fp)
{
    FormatBody fb;
    int nb;

    f_read(fp, &fb, sizeof(fb), &nb);
#ifdef VERBOSE
    syslog(LOG_NOTICE, "  wFormatTag: %d", fb.wFormatTag);
    syslog(LOG_NOTICE, "  wChannels: %d", fb.wChannels);
    syslog(LOG_NOTICE, "  dwSamplesPerSec: %d", fb.dwSamplesPerSec);
    syslog(LOG_NOTICE, "  dwAvgBytesPerSec: %d", fb.dwAvgBytesPerSec);
    syslog(LOG_NOTICE, "  wBlockAligh: %d", fb.wBlockAlign);
    syslog(LOG_NOTICE, "  wBitsPerSample: %d", fb.wBitsPerSample);
    dly_tsk(1);
#endif
    if (fb.wFormatTag != 1 || fb.wChannels != 2 || fb.wBitsPerSample != 16) {
        syslog(LOG_NOTICE, "Not acceptable WAV file.");
        return -1;
    }
    if ((fb.dwSamplesPerSec != 44100) && (fb.dwSamplesPerSec != 48000)) {
        syslog(LOG_NOTICE, "Not acceptable Sample rate %d.", fb.dwSamplesPerSec);
        return -1;
    }
    wav_sample_rate = fb.dwSamplesPerSec;
    return 0;   /* acceptable */
}

/*
 * Collect WAV file information into out Player_Status struct.
 * When return, file is seeked upto beggining of music data.
 */
FRESULT get_wav_info(Player_Status *pst)
{
    FIL *fp = &pst->music_file;
    waveheader wh;
    chunkHeader ch;
    char chunk_str[5];
    long offset;
    int result, nb;

    wbuffer = jsp_malloc(4096);

    result = f_read(fp, &wh, sizeof(wh), &nb);
    if (result != FR_OK || nb != sizeof(wh)) {
        syslog(LOG_NOTICE, "failed to read header");
        return -1;
    }
    if (strncmp(wh.riffid, "RIFF", 4)) {
        syslog(LOG_NOTICE, "not a RIFF file.");
        return -1;
    }
    if (strncmp(wh.waveid, "WAVE", 4)) {
        syslog(LOG_NOTICE, "not a WAVE file.");
        return -1;
    }
    while (f_read(fp, &ch, sizeof(ch), &nb) == FR_OK) {
        if (nb != sizeof(ch))
            break;
        memcpy(chunk_str, ch.chunkID, 4);
        chunk_str[4] = 0;
#ifdef DEBUG
        syslog(LOG_NOTICE, "%s", chunk_str);
#endif
        offset = fp->fptr;
        if (strncmp(chunk_str, FORMATID, 4) == 0) {
            if (dump_format(fp))
                break;
        } else if (strncmp(chunk_str, LISTID, 4) == 0) {
            if (dump_list(pst, fp, ch.chunkSize))
                break;
        } else if (strncmp(chunk_str, DATAID, 4) == 0) {
            syslog(LOG_NOTICE, "offset = %d (0x%x)\n", offset, offset);
            f_lseek(fp, offset);
            /* file size / 2 channel / 2byte --> samples */
            pst->title_length = (wh.fsize - offset) / 2 / 2;
            /* wav_sample_rate is either 44100 or 48000.
             * So, div by 44.1 or 48.0 gives number of milli seconds.
             */
            pst->title_length /= (wav_sample_rate / 100);
            pst->title_length *= 10;
            return FR_OK;
        }
        f_lseek(fp, offset + ch.chunkSize);
    }
    return -1;
}

#ifdef DEBUG_COMMAND
void wplay_command(int argc, char **argv)
{
    char *fname;
    Player_Status *pst;

    if (argc > 1) {
        fname = argv[1];
    }

    pst = get_player();
    pst->o_device = O_CODEC;
    do_mount();
    fname = "test.wav";
    if (argc > 1) {
        fname = argv[1];
    }
    f_open(&pst->music_file, fname, FA_OPEN_EXISTING|FA_READ);
    get_wav_info(pst);
dly_tsk(20);
    wavdecoder_task();
}
#else
void wplay_command(int argc, char **argv)
{
}
#endif
