/*
 * EEPROM read/write utility
 * Copyright (c) 2011 William R Sowerbutts, Deus Ex Machina Ltd.
 * http://sowerbutts.com/optiboot-w5100/
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * http://www.gnu.org/licenses/gpl-2.0.html 
 */

#include <avr/io.h>
#include <string.h>
#include <stdio.h>
#include <util/delay.h>
#include <avr/eeprom.h>
#include "w5100.h"
#include "serial.h"

#define CMD_LEN     256   // buffer size 

#if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega328__)
#define EEPROM_SIZE 1024
#else
#error "unknown device type; this is a job for fix it duck!"
#endif


int is_cmd(char *cmd, const char *expected)
{
    int len = strlen(expected);
    return (strncasecmp(cmd, expected, len) == 0 && (cmd[len] == 0 || cmd[len] == ' ')) ? -1 : 0;
}

char *parse_number(char *str, int *target, int *okay)
{
    char *p = str;
    int val = 0;
    int valid = 1;

    if(*p == 0)
        return p; // note: target is unmodified!

    if(strncasecmp(p, "0x", 2) == 0){ // hex!
        p+=2;
        while(*p && *p != ' '){ // until we hit a terminator
            if(*p >= '0' && *p <= '9'){
                val *= 16;
                val += (*p - '0');
            }else if(*p >= 'a' && *p <= 'f'){
                val *= 16;
                val += (10 + *p - 'a');
            }else if(*p >= 'A' && *p <= 'F'){
                val *= 16;
                val += (10 + *p - 'A');
            }else
                valid = 0;
            p++;
        }
    }else{ // decimal?
        while(*p && *p != ' '){
            if(*p >= '0' && *p <= '9'){
                val *= 10;
                val += (*p - '0');
            }else
                valid = 0;
            p++;
        }
    }

    if(valid){
        *target = val;
    }else{
        *okay = 0;
    }

    while(*p == ' ')
        p++;

    return p;
}

enum { FMT_PRETTY, FMT_DEC, FMT_HEX, FMT_WRITE };

#define BYTES_PER_LINE (1<<4)
void process_read(char *_args)
{
    int start, length = EEPROM_SIZE, okay = 1;
    char *args;
    char fmt = FMT_PRETTY;
    char line[80], *linep;

    args = _args;

    // assume first argument is the format; skip over it
    while(*args && *args != ' ')
        args++;
    while(*args && *args == ' ')
        args++;

    // set format if required
    if(is_cmd(_args, "pretty"))
        fmt = FMT_PRETTY;
    else if(is_cmd(_args, "hex"))
        fmt = FMT_HEX;
    else if(is_cmd(_args, "dec"))
        fmt = FMT_DEC;
    else if(is_cmd(_args, "write"))
        fmt = FMT_WRITE;
    else{
        // well, turns there was no valid format specified after all
        args = _args; // try and process it as a number
    }

    start = 0;
    args = parse_number(args, &start, &okay);

    length = EEPROM_SIZE - start;
    args = parse_number(args, &length, &okay);

    if(start < 0)
        okay = 0;

    if((start+length) > EEPROM_SIZE)
        okay = 0;

    if(!okay){
        serial_write("Bad parameter.\r\n");
        return;
    }

    if(fmt == FMT_PRETTY){
        serial_write("ADDRESS _0 _1 _2 _3 _4 _5 _6 _7 _8 _9 _A _B _C _D _E _F\r\n");

        int addr = start & ~(BYTES_PER_LINE-1);

        while(1){
            linep = line;
            linep += sprintf(linep, "0x%04x  ", addr);

            for(int i=0; i<BYTES_PER_LINE; i++){
                if(addr >= start && addr < (start+length)){
                    linep += sprintf(linep, "%02x ", eeprom_read_byte((const uint8_t *)addr));
                }else{
                    linep += sprintf(linep, "   ");
                }
                addr++;
            }

            linep += sprintf(linep, "\r\n");
            serial_write(line);

            if(addr >= (start+length))
                break;
        }
    }else if(fmt == FMT_WRITE){
        int i=0;
        while(i<length){
            sprintf(line, "write 0x%04x ", start+i);
            serial_write(line);
            for(int j=0; j<8 && i<length; j++){
                sprintf(line, "0x%02x ", eeprom_read_byte((const uint8_t *)(start+i)));
                serial_write(line);
                i++;
            }
            serial_write("\r\n");
        }
    }else{
        for(int i=0; i<length; i++){
            if(fmt == FMT_HEX)
                sprintf(line, "0x%02x ", eeprom_read_byte((const uint8_t *)(start+i)));
            else
                sprintf(line, "%u ", eeprom_read_byte((const uint8_t *)(start+i)));
            serial_write(line);
        }
        serial_write("\r\n");
    }
}

void process_write(char *args)
{
    int addr, val, length, okay = 1;
    char line[80];
    char *bytes;

    addr = -1;
    bytes = parse_number(args, &addr, &okay);

    // pass through once, check each byte value
    length = 0;
    while(*bytes){
        bytes = parse_number(bytes, &val, &okay);
        if(val < 0 || val > 255)
            okay = 0;
        length++;
    }

    if(!okay || addr < 0 || (addr+length) > EEPROM_SIZE){
        serial_write("Bad parameter.\r\n");
        return;
    }

    // start over
    bytes = parse_number(args, &addr, &okay);

    // pass through again, do the writes
    sprintf(line, "Programming %d bytes from 0x%04x:", length, addr);
    serial_write(line);

    while(*bytes){
        bytes = parse_number(bytes, &val, &okay);

        sprintf(line, " %02x", val);
        serial_write(line);
        eeprom_update_byte((uint8_t *)addr, (uint8_t)val);

        addr++;
    }
    serial_write("\r\n");
}

void process_fixed(char *args, unsigned char val)
{
    int start = 0, length = EEPROM_SIZE, okay = 1;
    char line[80];

    start = 0;
    args = parse_number(args, &start, &okay);

    length = -1;
    args = parse_number(args, &length, &okay);

    if(start < 0 || length < 0)
        okay = 0;

    if((start+length) > EEPROM_SIZE)
        okay = 0;

    if(!okay){
        serial_write("Bad parameter.\r\n");
        return;
    }

    sprintf(line, "Programming %d bytes from 0x%04x:", length, start);
    serial_write(line);

    for(int addr=start; addr<(start+length); addr++){
        sprintf(line, " %02x", val);
        serial_write(line);
        eeprom_update_byte((uint8_t *)addr, (uint8_t)val);
    }
    serial_write("\r\n");
}

void process_help(char *args)
{
    serial_write("EEPROM programmer commands:\r\n");
    serial_write("    read [fmt] (addr) [len] -- read len bytes starting at addr.\r\n");
    serial_write("        optional fmt: dec, hex, write, pretty (default)\r\n");
    serial_write("    zero (addr) (len) -- write all zeroes to len bytes starting at addr.\r\n");
    serial_write("    one (addr) (len) -- write all ones to len bytes starting at addr.\r\n");
    serial_write("    write (addr) (byte...) -- write bytes starting at address.\r\n");
    serial_write("\r\n");
    serial_write("Numbers can be in decimal or hex.\r\n");
    serial_write("Have fun!\r\n");
}

void process_cmd(char *cmd)
{
    char *p = cmd;

    while(*p && *p != ' ') // skip forward to space
        p++;

    while(*p && *p == ' ') // skip over spaces
        p++;

    // p now points at first argument

    if(is_cmd(cmd, "help")){
        process_help(p);
    }else if(is_cmd(cmd, "zero")){
        process_fixed(p, 0);
    }else if(is_cmd(cmd, "one")){
        process_fixed(p, 0xff);
    }else if(is_cmd(cmd, "read")){
        process_read(p);
    }else if(is_cmd(cmd, "write")){
        process_write(p);
    }else{
        serial_write("Unrecognised command. Enter 'help' for instructions.\r\n");
    }
}

int main(void)
{
    char cmd[CMD_LEN], *p;
    // initialise serial
    serial_init();

    while(1){
        serial_write("\rEEPROM> ");
        serial_read_line((unsigned char*)cmd, CMD_LEN);
        serial_write("\r\n");
        p = cmd;
        while(*p && *p == ' ') // skip spaces at start
            p++;
        if(*p) // non-empty line?
            process_cmd(p);
    }
}
