#include <pkgconf/system.h>
#ifndef CYGPKG_RBL
# error The eCos configuration does not contain the RBL package.
#endif
#ifndef CYGPKG_CRC
# error The eCos configuration does not contain the CRC package.
#endif
#include <pkgconf/hal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <cyg/rbl/rbl.h>
#include <cyg/hal/hal_if.h>

extern int   xyzModem_stream_open(int *err);    
extern void  xyzModem_stream_close(int *err);    
extern void  xyzModem_stream_terminate(int method, int (*getc)(void));    
extern int   xyzModem_stream_read(char *buf, int size, int *err);    
extern char *xyzModem_error(int err);

// ----------------------------------------------------------------------------
// Buffer space for code & data. This is dynamically allocated since the
// required sizes depend on how RedBoot was configured rather than on the
// application.
static int      code_buffer_size    = 0;
static int      data_buffer_size    = 0;
static char*    code_buffer         = (char*)0;
static char*    data_buffer         = (char*)0;

static cyg_bool
allocate_buffers(void)
{
    rbl_flash_details   flash_details;
    if (! rbl_get_flash_details(&flash_details)) {
        fputs("Error: failed to get RBL flash details\n", stderr);
        return false;
    }

    code_buffer_size = (flash_details.rbl_flash_block_size * flash_details.rbl_code_num_blocks) - flash_details.rbl_trailer_size;
    code_buffer = malloc(code_buffer_size);
    if (code_buffer == (char*)0) {
        fprintf(stderr, "Error: failed to allocate a %dK buffer for new code\n",
                (flash_details.rbl_flash_block_size * flash_details.rbl_code_num_blocks) / 1024);
        return false;
    }
    if (0 != flash_details.rbl_data_num_blocks) {
        data_buffer_size = (flash_details.rbl_flash_block_size * flash_details.rbl_data_num_blocks) - flash_details.rbl_trailer_size;
        data_buffer = malloc(data_buffer_size);
        if (data_buffer == (char*)0) {
            fprintf(stderr, "Error: failed to allocate a %dK buffer for data\n",
                    (flash_details.rbl_flash_block_size * flash_details.rbl_data_num_blocks) / 1024);
            return false;
        }
    }
    return true;
}

// ----------------------------------------------------------------------------
static void
handle_show_flash_details(void)
{
    rbl_flash_details   flash_details;
    if (! rbl_get_flash_details(&flash_details)) {
        fputs("sertest: failed to get RBL flash details.\n", stderr);
        return;
    }
    printf("\nFlash: %d blocks * %d K @ %p\n", flash_details.rbl_flash_num_blocks,
           flash_details.rbl_flash_block_size / 1024, flash_details.rbl_flash_base);
    printf("Each code image uses %d flash block(s)\n", flash_details.rbl_code_num_blocks);
    if (0 == flash_details.rbl_data_num_blocks) {
        puts("Persistent data support has been disabled.\n");
    } else {
        printf("Each data image uses %d flash block(s)\n", flash_details.rbl_data_num_blocks);
    }
    printf("Trailer size is %d bytes\n", flash_details.rbl_trailer_size);
}

static void
handle_show_flash_blocks(void)
{
    rbl_flash_details       flash_details;
    rbl_flash_block_purpose last_purpose, purpose;
    int                     i;
    
    if (! rbl_get_flash_details(&flash_details)) {
        fputs("sertest: failed to get RBL flash details.\n", stderr);
        return;
    }
    last_purpose = rbl_flash_block_invalid;
    for (i = 0; i < flash_details.rbl_flash_num_blocks; i++) {
        purpose = rbl_get_flash_block_purpose(i);
        if (last_purpose != purpose) {
            char* tmp;
            switch(purpose) {
              case rbl_flash_block_reserved:
                tmp = "Reserved"; break;
              case rbl_flash_block_code_A:
                tmp = "Code A  "; break;
              case rbl_flash_block_code_B:
                tmp = "Code B  "; break;
              case rbl_flash_block_data_A:
                tmp = "Data A  "; break;
              case rbl_flash_block_data_B:
                tmp = "Data B  "; break;
              case rbl_flash_block_free:
                tmp = "Free    "; break;
              case rbl_flash_block_invalid:
                tmp = "Invalid "; break;
              default:
                tmp = "<UNKNOWN>"; break;
            }
            printf("\n%s flash block(s): %d", tmp, i);
            last_purpose = purpose;
        } else {
            printf(", %d", i);
        }
    }
    putchar('\n');
}

static void
handle_show_rbl_blocks(void)
{
    rbl_flash_details       flash_details;
    rbl_block_details       block_details;
    
    if (! rbl_get_flash_details(&flash_details)) {
        fputs("sertest: failed to get RBL flash details.\n", stderr);
        return;
    }
    puts("\nPrimary  code block: ");
    if (rbl_get_block_details(rbl_block_code_primary, &block_details)) {
        if (!block_details.rbl_valid) {
            puts("not valid\n");
        } else {
            printf("starts @ %p (flash block %d), len %d, sequence number %d\n", block_details.rbl_address,
                   block_details.rbl_first_flash_block, block_details.rbl_size, block_details.rbl_sequence_number);
        } 
    } else {
        puts("failed to get details\n");
    }
    puts("Backup   code block: ");
    if (rbl_get_block_details(rbl_block_code_backup, &block_details)) {
        if (!block_details.rbl_valid) {
            puts("not valid\n");
        } else {
            printf("starts @ %p (flash block %d), len %d, sequence number %d\n", block_details.rbl_address,
                   block_details.rbl_first_flash_block, block_details.rbl_size, block_details.rbl_sequence_number);
        } 
    } else {
        puts("failed to get details\n");
    }
    if (0 != flash_details.rbl_data_num_blocks) {
        puts("Primary  data block: ");
        if (rbl_get_block_details(rbl_block_data_primary, &block_details)) {
            if (!block_details.rbl_valid) {
                puts("not valid\n");
            } else {
                printf("starts @ %p (flash block %d), len %d, sequence number %d\n", block_details.rbl_address,
                       block_details.rbl_first_flash_block, block_details.rbl_size, block_details.rbl_sequence_number);
            } 
        } else {
            puts("failed to get details\n");
        }
        puts("Backup   data block: ");
        if (rbl_get_block_details(rbl_block_data_backup, &block_details)) {
            if (!block_details.rbl_valid) {
                puts("not valid\n");
            } else {
                printf("starts @ %p (flash block %d), len %d, sequence number %d\n", block_details.rbl_address,
                       block_details.rbl_first_flash_block, block_details.rbl_size, block_details.rbl_sequence_number);
            } 
        } else {
            puts("failed to get details\n");
        }
    }
}

static void
handle_show_current_data(void)
{
    rbl_block_details   block_details;
    int                 i;
    
    if (! rbl_get_block_details(rbl_block_data_primary, &block_details)) {
        fputs("sertest: failed to get primary data block details\n", stderr);
        return;
    }
    if (! block_details.rbl_valid) {
        puts("There is no valid primary data block.\n");
        return;
    }

    if (! rbl_load_data(data_buffer, block_details.rbl_size)) {
        fputs("sertest: failed to load primary data into RAM\n", stderr);
        return;
    }
    for (i = 0; i < block_details.rbl_size; i++) {
        if (! (isprint(data_buffer[i]) || ('\r' == data_buffer[i]) || ('\n' == data_buffer[i]))) {
            break;
        }
        putchar(data_buffer[i]);
    }
    putchar('\n');
}

static void
handle_update_code(void)
{
    int err;
    int read;
    int i;

    printf("Please start a ymodem transfer.\n");
    if (xyzModem_stream_open(&err) < 0) {
        fprintf(stderr, "sertest: failed to start ymodem transfer, %s\n", xyzModem_error(err));
        return;
    }
    read = xyzModem_stream_read(code_buffer, code_buffer_size, &err);
    xyzModem_stream_close(&err);
    if (read < 0) {
        fprintf(stderr, "sertest: ymodem transfer failed, %s\n", xyzModem_error(err));
        return;
    }

    // We need to delay here for a bit to ensure the terminal emulator
    // is back to a sensible state.
    for (i = 0; i < 5; i++) {
        CYGACC_CALL_IF_DELAY_US(1000000);
    }
    
    printf("Transfer complete (%d bytes), updating flash.\n", read);
    if (! rbl_update_code(code_buffer, read)) {
        fputs("sertest: failed to update RBL code block\n", stderr);
    }
}

static void
handle_update_data(void)
{
    int err;
    int read;
    int i;

    printf("Please start a ymodem transfer.\n");
    if (xyzModem_stream_open(&err) < 0) {
        fprintf(stderr, "sertest: failed to start ymodem transfer, %s\n", xyzModem_error(err));
        return;
    }
    read = xyzModem_stream_read(data_buffer, data_buffer_size, &err);
    xyzModem_stream_close(&err);
    if (read < 0) {
        fprintf(stderr, "sertest: ymodem transfer failed, %s\n", xyzModem_error(err));
        return;
    }

    // We need to delay here for a bit to ensure the terminal emulator
    // is back to a sensible state.
    for (i = 0; i < 5; i++) {
        CYGACC_CALL_IF_DELAY_US(1000000);
    }
    
    printf("Transfer complete (%d bytes), updating flash.\n", read);
    if (! rbl_update_data(data_buffer, read)) {
        fputs("sertest: failed to update RBL data block\n", stderr);
    }
}

static void
handle_reset(void)
{
    rbl_reset();
}

// ----------------------------------------------------------------------------
int
main(int argc, char** argv)
{
    hal_virtual_comm_table_t* chan;
    char c;
    rbl_flash_details flash_details;

    // We want an interactive application on the default HAL diag channel,
    // but there may be an installed interrupt handler looking for ctrl-C.
    // The interrupt handler should only be installed when running in gdb
    // mode, but in practice that does not always appear to be the case.
    CYGACC_CALL_IF_SET_CONSOLE_COMM(0);
    chan = CYGACC_CALL_IF_CONSOLE_PROCS();
    CYGACC_COMM_IF_CONTROL(*chan, __COMMCTL_IRQ_DISABLE);
    
    if (! rbl_get_flash_details(&flash_details)) {
        fputs("sertest: failed to get RBL flash details.\n", stderr);
        fputs("       : please check that the installed RedBoot is correctly configured.\n", stderr);
        return 1;
    }
    if (! allocate_buffers()) {
        return 1;
    }

    for ( ; ; ) {
        printf("\n\nsertest: ");
#ifdef VERSION
# define VERSION_STRINGIFY1(a) # a    
# define VERSION_STRINGIFY(a) VERSION_STRINGIFY1(a)
        printf("version %s, ", VERSION_STRINGIFY(VERSION));
# undef VERSION_STRINGIFY
# undef VERSION_STRINGIFY1
#endif
        printf("built %s %s\n\n", __DATE__, __TIME__);
        
        printf("    1) show flash details\n");
        printf("    2) show flash blocks\n");
        printf("    3) show rbl blocks\n");
        printf("    4) show current data\n");
        printf("    5) update code (ymodem upload)\n");
        printf("    6) update data (ymodem upload)\n");
        printf("    7) reset\n");
        printf("    Action (1-7)? ");
        fflush(stdout);

        // This should really just be a getc() after setting stdin to
        // non-buffered.
        c = CYGACC_COMM_IF_GETC(*chan);
        
        if (isprint(c)) {
            printf("%c\n", c);
        } else {
            putchar('\n');
        }
        
        switch(c) {
          case '1' :
            handle_show_flash_details(); break;
          case '2' :
            handle_show_flash_blocks(); break;
          case '3' :
            handle_show_rbl_blocks(); break;
          case '4' :
            handle_show_current_data(); break;
          case '5' :
            handle_update_code(); break;
          case '6' :
            handle_update_data(); break;
          case '7' :
            handle_reset(); break;
            
          default:
            // Do nothing, just go around the loop again.
            break;
        }
    }
}

