/**
 * Copyright (c) 2017 - 2020, Nordic Semiconductor ASA
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form, except as embedded into a Nordic
 *    Semiconductor ASA integrated circuit in a product or a software update for
 *    such product, must reproduce the above copyright notice, this list of
 *    conditions and the following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * 4. This software, with or without modification, must only be used with a
 *    Nordic Semiconductor ASA integrated circuit.
 *
 * 5. Any software provided in binary form under this license must not be reverse
 *    engineered, decompiled, modified and/or disassembled.
 *
 * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <string.h>

#include "app_error.h"
#include "boards.h"
#include "nrf_cli.h"
#include "nrf_cli_uart.h"
#include "nrf_drv_uart.h"
#include "nrf_fstorage.h"
#include "nrf_soc.h"
#include "nrf_strerror.h"
#include "sdk_config.h"


#define READ_HELP   "read bytes from flash\n"                                                     \
                    "usage: read hex|str addr len"

#define READ_HEX_HELP   "read bytes from flash in HEX format\n"                                   \
                        "usage: read hex addr len\n"                                              \
                        "- addr: the address to read from, in HEX\n"                              \
                        "- len: number of bytes to read"

#define READ_STR_HELP   "read bytes from flash in ASCII format\n"                                 \
                        "usage: read str addr len\n"                                              \
                        "- addr: the address to read from, in HEX\n"                              \
                        "- len: number of bytes to read"

#define WRITE_HELP  "write ASCII bytes to flash\n"                                                \
                    "usage: write addr data\n"                                                    \
                    "- addr: address to write to, in HEX\n"                                       \
                    "- data: data to be written; use quotes for strings"

#define ERASE_HELP  "erase flash pages\n"                                                         \
                    "usage: erase addr pages\n"                                                   \
                    "- addr: address of the page to begin erasing from, in HEX\n"                 \
                    "- pages: number of pages to erase"

#define FLASHAREA_HELP  "print or set the boundaries of the flash\n"                              \
                        "usage: flasharea print\n"                                                \
                        "usage: flasharea set begin end"

#define FLASHAREA_PRINT_HELP    "print the boundaries of the flash\n"                             \
                                "usage: flasharea print"

#define FLASHAREA_SET_HELP  "set the boundaries of the flash\n"                                   \
                            "usage: flasharea set begin end\n"                                    \
                            "- begin: address of the beginning of the flash area, in HEX\n"       \
                            "- end: address of end of the flash area, in HEX"


typedef enum
{
    DATA_FMT_HEX = 'h',
    DATA_FMT_STR = 's'
} data_fmt_t;


extern nrf_fstorage_t fstorage;                                                                     /**< The fstorage instance, defined in main.c. */
extern void wait_for_flash_ready(nrf_fstorage_t const *);                                           /**< Wait for flash operations to complete. Defined in main.c */


NRF_CLI_UART_DEF(cli_uart, 0, 64, 128);
NRF_CLI_DEF(m_cli_uart, "fstorage example:~$ ", &cli_uart.transport, '\r', 4);


void cli_init(void)
{
    nrf_drv_uart_config_t uart_config = NRF_DRV_UART_DEFAULT_CONFIG;
    uart_config.pseltxd               = TX_PIN_NUMBER;
    uart_config.pselrxd               = RX_PIN_NUMBER;
    uart_config.hwfc                  = NRF_UART_HWFC_DISABLED;

    ret_code_t rc = nrf_cli_init(&m_cli_uart, &uart_config, true, true, NRF_LOG_SEVERITY_INFO);
    APP_ERROR_CHECK(rc);
}


void cli_start(void)
{
    ret_code_t rc = nrf_cli_start(&m_cli_uart);
    APP_ERROR_CHECK(rc);
}


void cli_process(void)
{
    nrf_cli_process(&m_cli_uart);
}


static void cli_unknown_param_help(nrf_cli_t const * p_cli,
                                   char const * p_param,
                                   char const * p_cmd)
{
    nrf_cli_fprintf(p_cli,
                    NRF_CLI_ERROR,
                    "%s: unknown parameter '%s'\n"
                    "Try '%s -h' for help.\n",
                    p_cmd,
                    p_param,
                    p_cmd);
}


static void cli_missing_param_help(nrf_cli_t const * p_cli, char const * p_cmd)
{
    nrf_cli_fprintf(p_cli,
                    NRF_CLI_ERROR,
                    "%s: missing parameter.\n"
                    "Try '%s -h' for help.\n",
                    p_cmd,
                    p_cmd);
}


static void fstorage_read(nrf_cli_t const * p_cli, uint32_t addr, uint32_t len, data_fmt_t fmt)
{
    ret_code_t rc;
    uint8_t    data[256] = {0};

    if (len > sizeof(data))
    {
        len = sizeof(data);
    }

    /* Read data. */
    rc = nrf_fstorage_read(&fstorage, addr, data, len);
    if (rc != NRF_SUCCESS)
    {
        nrf_cli_fprintf(p_cli, NRF_CLI_ERROR, "nrf_fstorage_read() returned: %s\n",
                        nrf_strerror_get(rc));
        return;
    }

    switch (fmt)
    {
        case DATA_FMT_HEX:
        {
            /* Print bytes. */
            for (uint32_t i = 0; i < len; i++)
            {
                nrf_cli_fprintf(p_cli, NRF_CLI_NORMAL, "0x%x ", data[i]);
            }
            nrf_cli_fprintf(p_cli, NRF_CLI_NORMAL, "\n");
        } break;

        case DATA_FMT_STR:
        {
            nrf_cli_fprintf(p_cli, NRF_CLI_NORMAL, "%s\n", data);
        } break;

        default:
            break;
    }
}


static uint32_t round_up_u32(uint32_t len)
{
    if (len % sizeof(uint32_t))
    {
        return (len + sizeof(uint32_t) - (len % sizeof(uint32_t)));
    }

    return len;
}


static void fstorage_write(nrf_cli_t const * p_cli, uint32_t addr, void const * p_data)
{
    /* The following code snippet make sure that the length of the data we are writing to flash
     * is a multiple of the program unit of the flash peripheral (4 bytes).
     *
     * In case of non-string piece of data, use the sizeof operator instead of strlen.
     */
    uint32_t len = round_up_u32(strlen(p_data));

    ret_code_t rc = nrf_fstorage_write(&fstorage, addr, p_data, len, NULL);
    if (rc != NRF_SUCCESS)
    {
        nrf_cli_fprintf(p_cli, NRF_CLI_ERROR, "nrf_fstorage_write() returned: %s\n",
                        nrf_strerror_get(rc));
    }
}


static void fstorage_erase(nrf_cli_t const * p_cli, uint32_t addr, uint32_t pages_cnt)
{
    ret_code_t rc = nrf_fstorage_erase(&fstorage, addr, pages_cnt, NULL);
    if (rc != NRF_SUCCESS)
    {
        nrf_cli_fprintf(p_cli, NRF_CLI_ERROR, "nrf_fstorage_erase() returned: %s\n",
                        nrf_strerror_get(rc));
    }
}


static void read_cmd(nrf_cli_t const * p_cli, size_t argc, char ** argv)
{
    if (nrf_cli_help_requested(p_cli))
    {
        nrf_cli_help_print(p_cli, NULL, 0);
    }
    else if (argc == 1)
    {
        cli_missing_param_help(p_cli, "read");
    }
    else
    {
        cli_unknown_param_help(p_cli, argv[1], "read");
    }
}


static void read_cmd_hex(nrf_cli_t const * p_cli, size_t argc, char ** argv)
{
    if (nrf_cli_help_requested(p_cli))
    {
        nrf_cli_help_print(p_cli, NULL, 0);
    }
    else if (argc != 3)
    {
        cli_missing_param_help(p_cli, "read hex");
    }
    else
    {
        uint32_t const addr = strtol(argv[1], NULL, 16);
        uint32_t const len  = strtol(argv[2], NULL, 10);

        fstorage_read(p_cli, addr, len, DATA_FMT_HEX);
    }
}


static void read_cmd_str(nrf_cli_t const * p_cli, size_t argc, char ** argv)
{
    if (nrf_cli_help_requested(p_cli))
    {
        nrf_cli_help_print(p_cli, NULL, 0);
    }
    else if (argc != 3)
    {
        cli_missing_param_help(p_cli, "read str");
    }
    else
    {
        uint32_t const addr = strtol(argv[1], NULL, 16);
        uint32_t const len  = strtol(argv[2], NULL, 10);

        fstorage_read(p_cli, addr, len, DATA_FMT_STR);
    }
}


static void write_cmd(nrf_cli_t const * p_cli, size_t argc, char ** argv)
{
    static uint8_t m_data[256];

    if (nrf_cli_help_requested(p_cli))
    {
        nrf_cli_help_print(p_cli, NULL, 0);
    }
    else if (argc != 3)
    {
        cli_missing_param_help(p_cli, "write");
    }
    else
    {
        uint32_t const addr = strtol(argv[1], NULL, 16);
        uint32_t const len  = strlen(argv[2]) < sizeof(m_data) ?
                              strlen(argv[2]) : sizeof(m_data);

        /* Copy data to a static variable. */
        memset(m_data, 0x00, sizeof(m_data));
        memcpy(m_data, argv[2], len);

        fstorage_write(p_cli, addr, m_data);
    }

}


static void erase_cmd(nrf_cli_t const * p_cli, size_t argc, char ** argv)
{
    if (nrf_cli_help_requested(p_cli))
    {
        nrf_cli_help_print(p_cli, NULL, 0);
    }
    else if (argc != 3)
    {
        cli_missing_param_help(p_cli, argv[0]);
    }
    else
    {
        uint32_t const addr      = strtol(argv[1], NULL, 16);
        uint32_t const pages_cnt = strtol(argv[2], NULL, 10);

        fstorage_erase(p_cli, addr, pages_cnt);
    }
}


static void flasharea_cmd(nrf_cli_t const * p_cli, size_t argc, char ** argv)
{
    if (nrf_cli_help_requested(p_cli))
    {
        nrf_cli_help_print(p_cli, NULL, 0);
    }
    else if (argc == 1)
    {
        cli_missing_param_help(p_cli, "flasharea");
    }
    else
    {
        cli_unknown_param_help(p_cli, argv[1], "flasharea");
    }
}


static void flasharea_cmd_print(nrf_cli_t const * p_cli, size_t argc, char ** argv)
{
    if (nrf_cli_help_requested(p_cli))
    {
        nrf_cli_help_print(p_cli, NULL, 0);
    }
    else
    {
        nrf_cli_fprintf(p_cli, NRF_CLI_NORMAL, "begin: %x, end: %x\n",
                        fstorage.start_addr, fstorage.end_addr);
    }
}


static void flasharea_cmd_set(nrf_cli_t const * p_cli, size_t argc, char ** argv)
{
    if (nrf_cli_help_requested(p_cli))
    {
        nrf_cli_help_print(p_cli, NULL, 0);
    }
    else if (argc != 3)
    {
        cli_missing_param_help(p_cli, "flasharea set");
    }
    else
    {
        fstorage.start_addr = strtol(argv[1], NULL, 16);
        fstorage.end_addr   = strtol(argv[2], NULL, 16);

        nrf_cli_fprintf(p_cli, NRF_CLI_NORMAL, "begin: %x, end: %x\n",
                        fstorage.start_addr, fstorage.end_addr);
    }
}


NRF_CLI_CREATE_STATIC_SUBCMD_SET(m_read_cmd)
{
    NRF_CLI_CMD(hex, NULL, READ_HEX_HELP, read_cmd_hex),
    NRF_CLI_CMD(str, NULL, READ_STR_HELP, read_cmd_str),
    NRF_CLI_SUBCMD_SET_END
};


NRF_CLI_CREATE_STATIC_SUBCMD_SET(m_flasharea_cmd)
{
    NRF_CLI_CMD(print, NULL, FLASHAREA_PRINT_HELP, flasharea_cmd_print),
    NRF_CLI_CMD(set,   NULL, FLASHAREA_SET_HELP,   flasharea_cmd_set),
    NRF_CLI_SUBCMD_SET_END
};


NRF_CLI_CMD_REGISTER(read,      &m_read_cmd,        READ_HELP,      read_cmd);
NRF_CLI_CMD_REGISTER(write,     NULL,               WRITE_HELP,     write_cmd);
NRF_CLI_CMD_REGISTER(erase,     NULL,               ERASE_HELP,     erase_cmd);
NRF_CLI_CMD_REGISTER(flasharea, &m_flasharea_cmd,   FLASHAREA_HELP, flasharea_cmd);
