// -*- tab-width: 4; Mode: C++; c-basic-offset: 4; indent-tabs-mode: t -*-

//
// Simple commandline menu system.
//

#include <FastSerial.h>
#include <AP_Common.h>

#include <ctype.h>
#include <string.h>
#include <avr/pgmspace.h>

#include "include/menu.h"

// statics
char Menu::_inbuf[MENU_COMMANDLINE_MAX];
Menu::arg Menu::_argv[MENU_ARGS_MAX + 1];

// constructor
Menu::Menu(const prog_char *prompt, const Menu::command *commands, uint8_t entries, preprompt ppfunc) :
    _prompt(prompt),
    _commands(commands),
    _entries(entries),
    _ppfunc(ppfunc)
{
}

// run the menu
void
Menu::run(void)
{
    int8_t		ret;
    uint8_t		len, i;
    uint8_t		argc;
    int			c;
    char		*s;

    // loop performing commands
    for (;;) {

        // run the pre-prompt function, if one is defined
        if ((NULL != _ppfunc) && !_ppfunc())
            return;

        // loop reading characters from the input
        len = 0;
        Serial.printf("%S] ", FPSTR(_prompt));
        for (;;) {
            c = Serial.read();
            if (-1 == c)
                continue;
            // carriage return -> process command
            if ('\r' == c) {
                _inbuf[len] = '\0';
                Serial.write('\r');
                Serial.write('\n');
                break;
            }
            // backspace
            if ('\b' == c) {
                if (len > 0) {
                    len--;
                    Serial.write('\b');
                    Serial.write(' ');
                    Serial.write('\b');
                    continue;
                }
            }
            // printable character
            if (isprint(c) && (len < (MENU_COMMANDLINE_MAX - 1))) {
                _inbuf[len++] = c;
                Serial.write((char)c);
                continue;
            }
        }

        // split the input line into tokens
        argc = 0;
        _argv[argc++].str = strtok_r(_inbuf, " ", &s);
        // XXX should an empty line by itself back out of the current menu?
        while (argc <= MENU_ARGS_MAX) {
            _argv[argc].str = strtok_r(NULL, " ", &s);
            if ('\0' == _argv[argc].str)
                break;
            _argv[argc].i = atol(_argv[argc].str);
            _argv[argc].f = atof(_argv[argc].str);	// calls strtod, > 700B !
            argc++;
        }

		if (_argv[0].str == NULL) {
			continue;
		}

        // populate arguments that have not been specified with "" and 0
        // this is safer than NULL in the case where commands may look
        // without testing argc
        i = argc;
        while (i <= MENU_ARGS_MAX) {
            _argv[i].str = "";
            _argv[i].i = 0;
            _argv[i].f = 0;
            i++;
        }

		bool cmd_found = false;
        // look for a command matching the first word (note that it may be empty)
        for (i = 0; i < _entries; i++) {
            if (!strcasecmp_P(_argv[0].str, _commands[i].command)) {
                ret = _call(i, argc);
                cmd_found=true;
                if (-2 == ret)
                    return;
                break;
            }
        }

        // implicit commands
        if (i == _entries) {
            if (!strcmp(_argv[0].str, "?") || (!strcasecmp_P(_argv[0].str, PSTR("help")))) {
                _help();
                cmd_found=true;
            } else if (!strcasecmp_P(_argv[0].str, PSTR("exit"))) {
                return;
            }
        }

        if (cmd_found==false)
        {
    		Serial.println("Invalid command, type 'help'");
    	}

    }
}

// display the list of commands in response to the 'help' command
void
Menu::_help(void)
{
    int		i;

    Serial.println("Commands:");
    for (i = 0; i < _entries; i++)
        Serial.printf("  %S\n", FPSTR(_commands[i].command));
}

// run the n'th command in the menu
int8_t
Menu::_call(uint8_t n, uint8_t argc)
{
    func		fn;

    fn = (func)pgm_read_pointer(&_commands[n].func);
    return(fn(argc, &_argv[0]));
}
