/*
 * Copyright (C) 2015 Freie Universität Berlin
 *
 * This file is subject to the terms and conditions of the GNU Lesser
 * General Public License v2.1. See the file LICENSE in the top level
 * directory for more details.
 */

/**
 * @ingroup     tests
 * @{
 *
 * @file
 * @brief       Manual test application for flashpage peripheral drivers
 *
 * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
 *
 * @}
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "shell.h"
#include "periph/flashpage.h"

#define LINE_LEN            (16)

/**
 * @brief   Allocate space for 1 flash page in RAM
 */
static uint8_t page_mem[FLASHPAGE_SIZE];

#ifdef MODULE_PERIPH_FLASHPAGE_RAW
/*
 * @brief   Allocate an aligned buffer for raw writings
 */
static char raw_buf[64] __attribute__ ((aligned (FLASHPAGE_RAW_ALIGNMENT)));

static uint32_t getaddr(const char *str)
{
    uint32_t addr = strtol(str, NULL, 16);

    return addr;
}
#endif

static int getpage(const char *str)
{
    int page = atoi(str);
    if ((page >= (int)FLASHPAGE_NUMOF) || (page < 0)) {
        printf("error: page %i is invalid\n", page);
        return -1;
    }
    return page;
}

static void dumpchar(uint8_t mem)
{
    if (mem >= ' ' && mem <= '~') {
        printf("  %c  ", mem);
    }
    else {
        printf("  ?? ");
    }
}

static void memdump(void *addr, size_t len)
{
    unsigned pos = 0;
    uint8_t *mem = (uint8_t *)addr;

    while (pos < (unsigned)len) {
        for (unsigned i = 0; i < LINE_LEN; i++) {
            printf("0x%02x ", mem[pos + i]);
        }
        puts("");
        for (unsigned i = 0; i < LINE_LEN; i++) {
            dumpchar(mem[pos++]);
        }
        puts("");
    }
}

static void dump_local(void)
{
    puts("Local page buffer:");
    memdump(page_mem, FLASHPAGE_SIZE);
}

static int cmd_info(int argc, char **argv)
{
    (void)argc;
    (void)argv;

    printf("Flash start addr:\t0x%08x\n", (int)CPU_FLASH_BASE);
    printf("Page size:\t\t%i\n", (int)FLASHPAGE_SIZE);
    printf("Number of pages:\t%i\n", (int)FLASHPAGE_NUMOF);

    return 0;
}

static int cmd_dump(int argc, char **argv)
{
    int page;
    void *addr;

    if (argc < 2) {
        printf("usage: %s <page>\n", argv[0]);
        return 1;
    }

    page = getpage(argv[1]);
    if (page < 0) {
        return 1;
    }
    addr = flashpage_addr(page);

    printf("Flash page %i at address %p\n", page, addr);
    memdump(addr, FLASHPAGE_SIZE);

    return 0;
}

static int cmd_dump_local(int argc, char **argv)
{
    (void)argc;
    (void)argv;

    dump_local();

    return 0;
}

static int cmd_read(int argc, char **argv)
{
    int page;

    if (argc < 2) {
        printf("usage: %s <page>\n", argv[0]);
        return 1;
    }

    page = getpage(argv[1]);
    if (page < 0) {
        return 1;
    }

    flashpage_read(page, page_mem);
    printf("Read flash page %i into local page buffer\n", page);
    dump_local();

    return 0;
}

static int cmd_write(int argc, char **argv)
{
    int page;

    if (argc < 2) {
        printf("usage: %s <page>\n", argv[0]);
        return 1;
    }

    page = getpage(argv[1]);
    if (page < 0) {
        return 1;
    }

    if (flashpage_write_and_verify(page, page_mem) != FLASHPAGE_OK) {
        printf("error: verification for page %i failed\n", page);
        return 1;
    }

    printf("wrote local page buffer to flash page %i at addr %p\n",
           page, flashpage_addr(page));
    return 0;
}

#ifdef MODULE_PERIPH_FLASHPAGE_RAW
static int cmd_write_raw(int argc, char **argv)
{
    uint32_t addr;

    if (argc < 3) {
        printf("usage: %s <addr> <data>\n", argv[0]);
        return 1;
    }

    addr = getaddr(argv[1]);

    /* try to align */
    memcpy(raw_buf, argv[2], strlen(argv[2]));

    flashpage_write_raw((void*)addr, raw_buf, strlen(raw_buf));

    printf("wrote local data to flash address %#lx of len %u\n",
           addr, strlen(raw_buf));
    return 0;
}
#endif

static int cmd_erase(int argc, char **argv)
{
    int page;

    if (argc < 2) {
        printf("usage: %s <page>\n", argv[0]);
        return 1;
    }

    page = getpage(argv[1]);
    if (page < 0) {
        return 1;
    }
    flashpage_write(page, NULL);

    printf("successfully erased page %i (addr %p)\n",
           page, flashpage_addr(page));
    return 0;
}

static int cmd_edit(int argc, char **argv)
{
    int offset;
    size_t data_len;

    if (argc < 3) {
        printf("usage: %s <offset> <data>\n", argv[0]);
        return 1;
    }

    offset = atoi(argv[1]);
    if (offset >= (int)FLASHPAGE_SIZE) {
        printf("error: given offset is out of bounce\n");
        return -1;
    }
    data_len = strlen(argv[2]);
    if ((data_len + offset) > FLASHPAGE_SIZE) {
        data_len = FLASHPAGE_SIZE - offset;
    }

    memcpy(&page_mem[offset], argv[2], data_len);
    dump_local();

    return 0;
}

static int cmd_test(int argc, char **argv)
{
    int page;
    char fill = 'a';

    if (argc < 2) {
        printf("usage: %s <page>\n", argv[0]);
        return 1;
    }

    page = getpage(argv[1]);
    if (page < 0) {
        return 1;
    }

    for (unsigned i = 0; i < sizeof(page_mem); i++) {
        page_mem[i] = (uint8_t)fill++;
        if (fill > 'z') {
            fill = 'a';
        }
    }

    if (flashpage_write_and_verify(page, page_mem) != FLASHPAGE_OK) {
        printf("error verifying the content of page %i\n", page);
        return 1;
    }

    printf("wrote local page buffer to flash page %i at addr %p\n",
           page, flashpage_addr(page));
    return 0;
}

static const shell_command_t shell_commands[] = {
    { "info", "Show information about pages", cmd_info },
    { "dump", "Dump the selected page to STDOUT", cmd_dump },
    { "dump_local", "Dump the local page buffer to STDOUT", cmd_dump_local },
    { "read", "Copy the given page to the local page buffer and dump to STDOUT", cmd_read },
    { "write", "Write the local page buffer to the given page", cmd_write },
#ifdef MODULE_PERIPH_FLASHPAGE_RAW
    { "write_raw", "Write (ASCII, max 64B) data to the given address", cmd_write_raw },
#endif
    { "erase", "Erase the given page buffer", cmd_erase },
    { "edit", "Write bytes to the local page buffer", cmd_edit },
    { "test", "Write and verify test pattern", cmd_test },
    { NULL, NULL, NULL }
};

int main(void)
{
    puts("ROM flash read write test\n");
    puts("Please refer to the README.md for further information\n");

    cmd_info(0, NULL);

    /* run the shell */
    char line_buf[SHELL_DEFAULT_BUFSIZE];
    shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);
    return 0;
}
