/* cli_cmd.c */

#include "embedded_cli.h"
#include "board_init.h"
#include "ff.h"
#include "mp3_decode.h"

#define CLI_BUF_SIZE        1024
#define CLI_RX_BUF_SIZE     128
#define CLI_CMD_BUF_SIZE    128
#define CLI_HISTORY_SIZE    128
#define CLI_BINDING_CNT     8

static CLI_UINT cli_buf[BYTES_TO_CLI_UINTS(CLI_BUF_SIZE)];
static EmbeddedCli * cli_obj;

/* mp3 decode. */
extern const mp3_decode_if_t  mp3_dec_if;
             mp3_decode_obj_t mp3_dec_obj;

/* play mp3 file. */
static void cli_cmd_play(EmbeddedCli *cli, char *args, void *context);
/* change directory. */
static void cli_cmd_cd(EmbeddedCli *cli, char *args, void *context);
/* list directory contents. */
static void cli_cmd_ls(EmbeddedCli *cli, char *args, void *context);
/* print working directory */
static void cli_cmd_pwd(EmbeddedCli *cli, char *args, void *context);

/* write char. */
static void cli_write_char(EmbeddedCli *cli, char c)
{
    (void)cli;
    putchar(c);
}

void cli_cmd_init(void)
{
    EmbeddedCliConfig *cli_conf = embeddedCliDefaultConfig();
    cli_conf->cliBuffer         = cli_buf;
    cli_conf->cliBufferSize     = CLI_BUF_SIZE;
    cli_conf->rxBufferSize      = CLI_RX_BUF_SIZE;
    cli_conf->cmdBufferSize     = CLI_CMD_BUF_SIZE;
    cli_conf->historyBufferSize = CLI_HISTORY_SIZE;
    cli_conf->maxBindingCount   = CLI_BINDING_CNT;

    cli_obj = embeddedCliNew(cli_conf);
    cli_obj->writeChar = cli_write_char;

    embeddedCliAddBinding(cli_obj, (CliCommandBinding){
        "play",
        "Usage: play [FILE]...\r\n\tplay the mp3 file..",
        true,
        NULL,
        cli_cmd_play
    });

    embeddedCliAddBinding(cli_obj, (CliCommandBinding) {
        "cd",
        "Usage: cd [DIR]...\r\n\tChange the current directory to DIR.",
        true,
        NULL,
        cli_cmd_cd
    });

    embeddedCliAddBinding(cli_obj, (CliCommandBinding) {
        "ls",
        "Usage: ls [DIR]...\r\n\tList information about the FILEs (the current directory by default).",
        true,
        NULL,
        cli_cmd_ls
    });

    embeddedCliAddBinding(cli_obj, (CliCommandBinding) {
        "pwd",
        "Usage: pwd\r\n\tPrint the name of the current working directory.",
        true,
        NULL,
        cli_cmd_pwd
    });
}

void cli_task(void)
{
    char ch = board_getchar();
    if ( ch > 0 )
    {
        while( ch > 0 )
        {
            embeddedCliReceiveChar(cli_obj, ch);
            ch = board_getchar();
        }
        embeddedCliProcess(cli_obj);
    }
    fflush(stdout);
}

/* decode & play mp3 file. */

#define APP_PCM_BUF_LEN         (MP3_DECODE_FRAME_SAMPLES * 4)
#define APP_PCM_BUF_SIZE        (APP_PCM_BUF_LEN * sizeof(int16_t))
#define APP_PCM_BUF_HALF_LEN    (APP_PCM_BUF_LEN / 2)

int16_t pcm_buf[APP_PCM_BUF_LEN];

static bool app_fs_play_mp3_file(char *filepath)
{
    mp3_decode_init(&mp3_dec_obj, (mp3_decode_if_t*)&mp3_dec_if);
    /* open a mp3 file. */
    if (!mp3_decode_open(&mp3_dec_obj, filepath))
    {
        printf("cannot open file.\r\n");
        return false;
    }

    /* fill the pcm buf. */
    mp3_decode(&mp3_dec_obj, pcm_buf, APP_PCM_BUF_LEN);

    /* init audio. */
    Audio_ClearStatus(AUDIO_STATUS_XFER_HALF | AUDIO_STATUS_XFER_DONE);
    Audio_Setup(mp3_decode_get_sample_rate(&mp3_dec_obj), (uint16_t*)pcm_buf, APP_PCM_BUF_LEN);

    /* play. */
    Audio_Enable(true);
    
    /* ping-pang buffer. */
    while (1)
    {
        uint32_t audio_flags = Audio_GetStatus();
        if (0 != (audio_flags & AUDIO_STATUS_XFER_HALF) )
        {
            /* fill the first half pcm buf. */
            if (0 == mp3_decode(&mp3_dec_obj, &pcm_buf[0], APP_PCM_BUF_HALF_LEN) )
            {
                /* mp3 play done. */
                break;
            }
            Audio_ClearStatus(AUDIO_STATUS_XFER_HALF);
        }
        else if (0 != (audio_flags & AUDIO_STATUS_XFER_DONE) )
        {
            /* fill the sencond half pcm buf. */
            if (0 == mp3_decode(&mp3_dec_obj, &pcm_buf[APP_PCM_BUF_HALF_LEN], APP_PCM_BUF_HALF_LEN) )
            {
                /* mp3 play done. */
                break;
            }
            Audio_ClearStatus(AUDIO_STATUS_XFER_DONE);
        }
        else
        {
            /* dummy loop. */
        }
    }
    /* close the audio. */
    Audio_Enable(false);
    return true;
}

/* play mp3 file. */
static void cli_cmd_play(EmbeddedCli *cli, char *args, void *context)
{
    (void) cli;
    (void) context;

    /* check args. */
    uint16_t argc = embeddedCliGetTokenCount(args);
    if (argc != 1)
    {
        printf("invalid arguments\r\n");
        return;
    }

    const char* fpath = embeddedCliGetToken(args, 1); /* get file path. */
    
    FIL fi;
    /* check file path. */
    if (FR_OK != f_open(&fi, fpath, FA_READ))
    {
        printf("%s: No such file or directory\r\n", fpath);
        f_close(&fi);
        return;
    }
    f_close(&fi);  

    printf("play %s\r\n", fpath);
    app_fs_play_mp3_file((char*)fpath);
    printf("done.\r\n");

}

/* change directory. */
void cli_cmd_cd(EmbeddedCli *cli, char *args, void *context)
{
    (void) cli;
    (void) context;

    uint16_t argc = embeddedCliGetTokenCount(args);

    /* only support 1 argument. */
    if ( argc != 1 )
    {
        printf("invalid arguments\r\n");
        return;
    }

    /* default is current directory. */
    const char* dpath = args;

    if ( FR_OK != f_chdir(dpath) )
    {
        printf("%s: No such file or directory\r\n", dpath);
        return;
    }
}

/* list directory contents. */
static void cli_cmd_ls(EmbeddedCli *cli, char *args, void *context)
{
    (void) cli;
    (void) context;

    uint16_t argc = embeddedCliGetTokenCount(args);

    /* only support 1 argument. */
    if ( argc > 1 )
    {
        printf("invalid arguments\r\n");
        return;
    }

    /* default is current directory. */
    const char* dpath = ".";
    if (argc) dpath = args;

    DIR dir;
    if ( FR_OK != f_opendir(&dir, dpath) )
    {
        printf("cannot access '%s': No such file or directory\r\n", dpath);
        return;
    }

    FILINFO fno;
    while( (f_readdir(&dir, &fno) == FR_OK) && (fno.fname[0] != 0) )
    {
        if ( fno.fname[0] != '.' ) /* ignore . and .. entry. */
        {
            if ( fno.fattrib & AM_DIR )
            {
                /* directory. */
                printf("/%s\r\n", fno.fname);
            }
            else
            {
                printf("%-40s", fno.fname);
                if (fno.fsize < 1024)
                {
                    printf("%lu B\r\n", (unsigned long)fno.fsize);
                }
                else
                {
                    printf("%lu KB\r\n", (unsigned long)(fno.fsize / 1024));
                }
            }
        }
    }

    f_closedir(&dir);
}

/* print working directory */
static void cli_cmd_pwd(EmbeddedCli *cli, char *args, void *context)
{
    (void) cli;
    (void) context;
    uint16_t argc = embeddedCliGetTokenCount(args);

    if (argc != 0)
    {
        printf("invalid arguments\r\n");
        return;
    }

    char path[256];
    if (FR_OK != f_getcwd(path, sizeof(path)))
    {
        printf("cannot get current working directory\r\n");
    }

    puts(path);
}

/* EOF. */
