/*
 * Copyright 2022 MindMotion Microelectronics Co., Ltd.
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "stdlib.h"

#include "embedded_cli.h"
#include "ff.h"

#include "board_init.h"
#include "mem_port.h"


static void cli_cmd_mem(EmbeddedCli *cli, char *args, void *context);

const CliCommandBinding cli_mem_cmd_bingding =
{
    .name           = "mem",
    .help           = "Usage: mem exec [BASE]\r\n\tExecute program from [BASE].\r\n\t"
                      "Usage: mem list\r\n\tList memory base & size.\r\n\t"
                      "Usage: mem erase [BASE] [SIZE]\r\n\tErase memory.\r\n\t"
                      "Usage: mem write [BASE] [DIR]\r\n\tWrite data from u disk to memory.\r\n\t"
                      "Usage: mem read  [BASE] [DIR] [SIZE]\r\n\tRead data from memory & Write to u disk.\r\n\t",
    .tokenizeArgs   = true,
    .context        = NULL,
    .binding        = cli_cmd_mem,
};

/* execute program. */
static void mem_exec(EmbeddedCli *cli, uint32_t base)
{
    char str_buf[64];
    sprintf(str_buf, "Exec on 0x%08lX\r\n", (unsigned long)base);
    embeddedCliPrint(cli, str_buf);
    mem_port_exec(base);
}

/* list memory base & size. */
static void mem_list(EmbeddedCli *cli)
{
    char str_buf[64];
    embeddedCliPrint(cli, "MEM BASE        MEM SIZE");
    for (uint32_t i = 0; i < mem_port_num; i++)
    {
        sprintf(str_buf, "0x%08lX      %u", (unsigned long)mem_port_list[i].base, (unsigned)mem_port_list[i].size);
        embeddedCliPrint(cli, str_buf);
    }
}

/* erase mem. */
static void mem_erase(EmbeddedCli *cli, uint32_t base, uint32_t size)
{
    uint32_t has_erase = mem_port_erase(base, size);
    if (has_erase == 0)
    {
        embeddedCliPrint(cli, "Cannot erase.");
    }
    else
    {
        char str_buf[64];
        sprintf(str_buf, "Erase base = 0x%08lX, size = %u", (unsigned long)base, (unsigned)has_erase);
        embeddedCliPrint(cli, str_buf);
    }
}

/* xfer data from u disk to mem. */
static void mem_write(EmbeddedCli *cli, uint32_t base, char * file_name)
{
    FIL fi;
    if (FR_OK != f_open(&fi, file_name, FA_READ))
    {
        /* cannot open file. */
        embeddedCliPrint(cli, "No such file or directory.");
        return;
    }

    uint8_t read_buf[512];
    UINT    read_cnt;

    /* read data from u disk. */
    while(FR_OK == f_read(&fi, read_buf, sizeof(read_buf), &read_cnt) && read_cnt >0)
    {
        /* write to mem. */
        if (mem_port_write(base, read_buf, read_cnt) == 0)
        {
            embeddedCliPrint(cli, "Write Failed.");
            break;
        }
        base += read_cnt;
    }

    embeddedCliPrint(cli, "Done.");
    f_close(&fi);
}

/* xfer data from mem to u disk. */
static void mem_read(EmbeddedCli *cli, uint32_t base, char * file_name, uint32_t size)
{
    FILINFO fi_info;
    if (FR_NO_FILE != f_stat(file_name, &fi_info))
    {
        /* file exist, cannot create file. */
        embeddedCliPrint(cli, "File exist.");
        return;
    }

    FIL fi;
    if (FR_OK != f_open(&fi, file_name, FA_WRITE | FA_CREATE_ALWAYS))
    {
        /* cannot create new file. */
        embeddedCliPrint(cli, "File cannot create.");
        return;
    }

    UINT has_written;
    if (FR_OK != f_write(&fi, (void*)base, size, &has_written))
    {
        /* cannot write file. */
        embeddedCliPrint(cli, "File cannot written in u disk.");
        f_close(&fi);
        return;
    }

    if (has_written != size)
    {
        /* write size not enough. */
        embeddedCliPrint(cli, "Write size err.");
        f_close(&fi);
        return;
    }

    embeddedCliPrint(cli, "Done.");
    f_close(&fi);
}

static void cli_cmd_mem(EmbeddedCli *cli, char *args, void *context)
{
    (void)context;

    uint16_t argc = embeddedCliGetTokenCount(args);

    if ( argc < 1 )
    {
        embeddedCliPrint(cli, "invalid arguments\r\n");
        return;
    }

    if (strcmp(embeddedCliGetToken(args, 1), "exec") == 0) /* exec the program form mem. */
    {
        if (argc != 2) /* token num must be 2: exec & [BASE] */
        {
            embeddedCliPrint(cli, "invalid arguments\r\n");
            return;
        }
        mem_exec(cli, strtoll(embeddedCliGetToken(args, 2), NULL, 0));
    }
    else if (strcmp(embeddedCliGetToken(args, 1), "list") == 0) /* list mem base & size. */
    {
        if (argc != 1) /* token num must be 1: list */
        {
            embeddedCliPrint(cli, "invalid arguments\r\n");
            return;
        }
        mem_list(cli);
    }
    else if (strcmp(embeddedCliGetToken(args, 1), "erase") == 0) /* erase mem. */
    {
        if (argc != 3) /* token num must be 2: exec & [BASE] */
        {
            embeddedCliPrint(cli, "invalid arguments\r\n");
            return;
        }
        mem_erase(cli, strtoll(embeddedCliGetToken(args, 2), NULL, 0), strtoll(embeddedCliGetToken(args, 3), NULL, 0));
    }
    else if (strcmp(embeddedCliGetToken(args, 1), "write") == 0) /* write data from u disk to mem. */
    {
        if (argc != 3) /* token num must be 3: download, [BASE] & [DIR] */
        {
            embeddedCliPrint(cli, "invalid arguments\r\n");
            return;
        }
        mem_write(cli, strtoll(embeddedCliGetToken(args, 2), NULL, 0), (char*)embeddedCliGetToken(args, 3) );
    }
    else if (strcmp(embeddedCliGetToken(args, 1), "read") == 0) /* write data from mem to u disk. */
    {
        if (argc != 4) /* token num must be 4: upload, [BASE], [DIR] & [SIZE] */
        {
            embeddedCliPrint(cli, "invalid arguments\r\n");
            return;
        }
        mem_read(cli, strtoll(embeddedCliGetToken(args, 2), NULL, 0), (char*)embeddedCliGetToken(args, 3), strtoll(embeddedCliGetToken(args, 4), NULL, 0));
    }
    else /* invalid cmd. */
    {
        embeddedCliPrint(cli, "invalid arguments\r\n");
    }
}