/*

Part of the TERMINAL MODULE

Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>

*/

#pragma once

#include <Arduino.h>

#include "terminal_parsing.h"

#include <forward_list>
#include <functional>
#include <vector>

namespace terminal {

struct Terminal;

// We need to be able to pass arbitrary Args structure into the command function
// Like Embedis implementation, we only pass things that we actually use instead of complete obj instance
struct CommandContext {
    std::vector<String> argv;
    size_t argc;
    Print& output;
};

class Terminal {
public:
    enum class Result {
        Error,           // Genric error condition
        Command,         // We successfully parsed the line and executed the callback specified via addCommand
        CommandNotFound, // ... similar to the above, but command was never added via addCommand
        BufferOverflow,  // Command line processing failed, no \r\n / \n before buffer was filled
        Pending,         // We got something in the buffer, but can't yet do anything with it
        NoInput          // We got nothing in the buffer and stream read() returns -1
    };

    using CommandFunc = void(*)(const CommandContext&);
    using ProcessFunc = bool(*)(Result);

    using Names = std::vector<const __FlashStringHelper*>;
    using Command = std::pair<const __FlashStringHelper*, CommandFunc>;
    using Commands = std::forward_list<Command>;

    // stream      - see `stream` description below
    // buffer_size - set internal limit for the total command line length
    Terminal(Stream& stream, size_t buffer_size = 128) :
        _stream(stream),
        _buffer_size(buffer_size)
    {
        _buffer.reserve(buffer_size);
    }

    static void addCommand(const __FlashStringHelper* name, CommandFunc func);
    static size_t commands();
    static Names names();

    // Try to process a single line (until either `\r\n` or just `\n`)
    Result processLine();

    // Calls processLine() repeatedly.
    // Blocks until the stream no longer has any data available.
    // `process_f` will return each individual processLine() Result,
    // and we can either stop (false) or continue (true) the function.
    void process(ProcessFunc = defaultProcessFunc);

    private:

    static bool defaultProcessFunc(Result);

    // general input / output stream:
    // - stream.read() should return user input
    // - stream.write() can be called from the command callback
    // - stream.write() can be called by us to show error messages
    Stream& _stream;

    // input stream is buffered until it can be parsed
    // in case parsing did not happen and we filled the buffer up to it's size,
    // the error will be returned and the buffer parsing will start from the beginning
    std::vector<char> _buffer;
    const size_t _buffer_size;

    static Commands _commands;
};

} // namespace terminal
