/*
  MP3 Player support code

  This part of code is based on the Andreas Schewarz's
  "ARM MP3/AAC player" project.

    Ref: http://embdev.net/articles/ARM_MP3/AAC_Player

  The project's source code is distributed with following notice.

  ----------
  Copyright (C) 2006 Andreas Schwarz <andreas@andreas-s.net>

  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages
  arising from the use of this software.

  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it
  freely, subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not
     claim that you wrote the original software. If you use this software
     in a product, an acknowledgment in the product documentation would be
     appreciated but is not required.
  2. Altered source versions must be plainly marked as such, and must not be
     misrepresented as being the original software.
  3. This notice may not be removed or altered from any source distribution.
*/

#include <t_services.h>
#include "nxpmp3.h"
#include <coder.h>
#include <string.h>
#include "kernel_id.h"
#include "ff.h"
#include "player.h"
#include "mempool.h"

static MP3FrameInfo frame_info;

static MP3DecInfo *mp3dec;

static void mp3_reset(Music_Data *mp3d)
{
    pool_init();
    mp3d->bytes_left = 0;
    mp3d->nFrames = 0;
    mp3d->read_ptr = mp3d->music_buffer;
    mp3d->offset = 0;
}

static int mp3_fill_inbuffer(FIL *mp3file, Music_Data *mp3d)
{
    int bytes_to_read, bytes_read;
    int result;

    if (mp3d->bytes_left > 0) {
        memmove(mp3d->music_buffer, mp3d->read_ptr, mp3d->bytes_left);
    }
    bytes_to_read = MUSIC_BUFSIZE - mp3d->bytes_left;

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

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


void mp3decoder_task()
{
    int rbytes, ec, try;
    int samprate;
    pcm_data *pcm;
    Player_Status *pst;
    FIL *music_file;
    Music_Data *mp3d;
    FLGPTN ptn;
    ER err;
    FRESULT result;
    SYSTIM tstart;

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

    mp3d = &music_data;
    mp3_reset(mp3d);
    mp3dec = MP3InitDecoder();
    try = 0;
    pst = get_player();
    music_file = &pst->music_file;

    ptn = 0;
    while (mp3_fill_inbuffer(music_file, mp3d) == 0) {
        err = pol_flg(FLG_STOP_REQ, 0x03, TWF_ORW, &ptn);
#ifdef VDEBUG
        if (err != E_TMOUT) {
           syslog(LOG_NOTICE, "pol_flg: %d, ptn = %d", err, ptn);
        }
#endif
        if (ptn) { /* STOP has requested */
            break;
	}
        rbytes = MP3FindSyncWord(mp3d->read_ptr, mp3d->bytes_left);
        if (rbytes < 0) {
            if (try++ < 5) {
                syslog(LOG_NOTICE, "SKIP %d bytes (0).", mp3d->bytes_left);
                mp3d->read_ptr = mp3d->music_buffer;
                mp3d->bytes_left = 0;
                continue;
            }
            syslog(LOG_NOTICE, "SYNC error.");
            break;
        }
        if (rbytes > 0) {
            syslog(LOG_NOTICE, "SKIP %d bytes (1).", rbytes);
            mp3d->read_ptr += rbytes;
            mp3d->bytes_left -= rbytes;
            continue;
        }
        /* We are in sync. Decode next mp3 frame */
#ifdef GOOD
        get_mpf(MPF_PCM, (VP)&pcm);
#else
        if (tget_mpf(MPF_PCM, (VP)&pcm, 500) != E_OK) {
            syslog(LOG_NOTICE, "get_mpf (MPF_PCM) failed");
            break;
        }
#endif
        ec = MP3Decode(mp3dec, &mp3d->read_ptr, &mp3d->bytes_left, pcm->raw_buffer, 0);
        if (ec) {
            syslog(LOG_NOTICE, "ERROR");
            rel_mpf(MPF_PCM, (VP)pcm);
            break;
        }

        if (mp3d->nFrames == 0) {
            MP3GetLastFrameInfo(mp3dec, &frame_info);
            syslog(LOG_NOTICE, "Bitrate: %d", frame_info.bitrate);
            if (frame_info.nChans == 2) {
                samprate = frame_info.samprate;
            } else {
                samprate = 0;
                ec = -1;
            }
            if (ec) {
                rel_mpf(MPF_PCM, (VP)pcm);
                break;
            }
            syslog(LOG_NOTICE, "chan: %d, samprate: %d", frame_info.nChans, samprate);
            syslog(LOG_NOTICE, "Bits/Sample: %d", frame_info.bitsPerSample);
            syslog(LOG_NOTICE, "Samples: %d", frame_info.outputSamps);
            syslog(LOG_NOTICE, "Layer: %d, Version: %d", frame_info.layer, frame_info.version);
            pst->cur_rate = samprate;
            (pst->pdevfuncs[pst->o_device]->rate_set)(samprate);

            get_tim(&tstart);
            pst->play_start = tstart;
        }
        if (ec == 0) {
            pcm->rate = samprate;
            pcm->samples = PCM_BSIZE;
            mp3d->nFrames++;
            if (tsnd_dtq(DTQ_PCM, (VP_INT)pcm, 500) != E_OK) {
                syslog(LOG_NOTICE, "tnsd_dtq failed.");
            }
#ifdef VDEBUG
cons_printf("\r\033[K%d (%d)\r", mp3d->nFrames, mp3d->offset);
#endif
        }
    }
    /* Send special frame indicating End of stream */
    snd_dtq(DTQ_PCM, (VP_INT)NULL);
syslog(LOG_NOTICE, "NULL sent to DTQ_PCM");
    result = f_close(music_file);
    if (result != FR_OK) {
        syslog(LOG_NOTICE, "f_close: %d", result);
    }
    if (ptn == 0) {
        /* We have reached end of music data.
         * Queue C_NEXT command, then player will start playing next song.
         */
        snd_dtq(DTQ_PCOM, (VP_INT) C_NEXT);
        wai_flg(FLG_STOP_REQ, 0x03, TWF_ORW, &ptn);
    }
}

#ifdef DEBUG_COMMAND
static char *mp3fname;

void dummyplayer_task()
{
    int rbytes, ec, try;
    int samprate;
    SYSTIM time1, time2;
    pcm_data *pcm;
    Music_Data *mp3d;
    Player_Status *pst;

    pst = get_player();
    get_tim(&time1);

    if (mp3dec) {
        MP3FreeDecoder(mp3dec);
    }
    mp3dec = MP3InitDecoder();

    if (f_open(&pst->music_file, mp3fname, FA_OPEN_EXISTING|FA_READ) != FR_OK) {
        cons_printf("can't open MP3 file.\n");
        return;
    }

    mp3d = &music_data;
    mp3_reset(mp3d);
    try = 0;
    while (mp3_fill_inbuffer(&pst->music_file, mp3d) == 0) {
        rbytes = MP3FindSyncWord(mp3d->read_ptr, mp3d->bytes_left);
        if (rbytes < 0) {
            if (try++ < 3) {
                cons_printf("SKIP %d bytes.\n", mp3d->bytes_left);
                mp3d->read_ptr = mp3d->music_buffer;
                mp3d->bytes_left = 0;
                continue;
            }
            cons_printf("SYNC error.\n");
            break;
        }
        if (rbytes > 0) {
            cons_printf("SKIP %d bytes.\n", rbytes);
            mp3d->read_ptr += rbytes;
            mp3d->bytes_left -= rbytes;
            continue;
        }
        /* We are in sync. Decode next mp3 frame */
        get_mpf(MPF_PCM, (VP)&pcm);
        ec = MP3Decode(mp3dec, &mp3d->read_ptr, &mp3d->bytes_left, pcm->raw_buffer, 0);
        if (ec) {
            cons_printf("ERROR\n");
            rel_mpf(MPF_PCM, (VP)pcm);
            break;
        }

        if (mp3d->nFrames == 0) {
            MP3GetLastFrameInfo(mp3dec, &frame_info);
            cons_printf("Bitrate: %d\n", frame_info.bitrate);
            if (frame_info.nChans == 2) {
                samprate = frame_info.samprate;
            } else {
                samprate = 0;
                ec = -1;
            }
            if (ec) {
                rel_mpf(MPF_PCM, (VP)pcm);
                break;
            }
            cons_printf("chan: %d, samprate: %d\n", frame_info.nChans, samprate);
            cons_printf("Bits/Sample: %d\n", frame_info.bitsPerSample);
            cons_printf("Samples: %d\n", frame_info.outputSamps);
            cons_printf("Layer: %d, Version: %d\n", frame_info.layer, frame_info.version);
        }
        if (ec == 0) {
            pcm->rate = samprate;
            pcm->samples = PCM_BSIZE;
            mp3d->nFrames++;
            rel_mpf(MPF_PCM, (VP)pcm);
        }
#ifdef DO_DELAY
        dly_tsk(1);
#endif
    }

    get_tim(&time2);
    time2 -= time1;
    cons_printf("\nTotal %d frames, %d.%d sec elapsed.\n", mp3d->nFrames, time2/1000, time2%1000);

    f_close(&pst->music_file);
}
#endif

void pcmsender_task(VP_INT taskno)
{
    pcm_data *pcm;
    Player_Status *pst;

    pst = get_player();

    for (;;) {
        syscall(rcv_dtq(DTQ_PCM, (VP_INT *)&pcm));
        if (pcm) {
            (pst->pdevfuncs[pst->o_device]->pcm_output)(pcm);
            rel_mpf(MPF_PCM, (VP)pcm);
        } else {
syslog(LOG_NOTICE, "Got NULL pcm");
            /* End of tune. Stop isochronous transfer */
            (pst->pdevfuncs[pst->o_device]->stop_output)();
            set_flg(FLG_STOP_RES, 0x01);
        }
    }
}

#ifdef DEBUG_COMMAND
extern char *find_mp3file(char *cp);

void mplay_command(int argc, char **argv)
{
    char *fname;
    Player_Status *pst;

    pst = get_player();
    pst->o_device = O_CODEC;
    do_mount();
    fname = "test.mp3";
    if (argc > 1) {
        fname = find_mp3file(argv[1]);
        if (fname == NULL) {
            cons_printf("no such file.\n");
            return;
        }
    }
    mp3fname = fname;
#ifdef USE_TASK
    act_tsk(DPLAYER_TASK);
#else
    dummyplayer_task();
#endif
}
#else
void mplay_command(int argc, char **argv)
{
}
#endif
