#include "CommandMap.h"
#include "Command.h"
#include <cstring>

void CommandMap::add(const char * s, uint32_t id,
        std::shared_ptr<CommandMap> next_map, bool following_char)
{
    size_t length = strlen(s);
    if (length < 1u)
        return;
    std::vector<uint32_t> v(length);
    for (size_t i = 0u; i < length; i++)
    {
        v[i] = s[i];
    }
    add(v, id, next_map, following_char);
}

void CommandMap::add(const std::vector<uint32_t> & s, uint32_t id,
        std::shared_ptr<CommandMap> next_map, bool following_char)
{
    Node * node = &m_root_node;
    size_t length = s.size();
    if (length < 1u)
        return;
    for (size_t i = 0u; i < length; i++)
    {
        const uint32_t c = s[i];
        auto it = node->next_chars.find(c);
        if (it != node->next_chars.end())
        {
            node = &*it->second;
        }
        else
        {
            auto new_node = std::make_shared<Node>();
            node->next_chars[c] = new_node;
            node = &*new_node;
        }
    }
    uint8_t flags = Node::FLAG_TERMINATOR;
    if (following_char)
        flags |= Node::FLAG_FOLLOWING_CHAR;
    node->id = id;
    node->flags = flags;
    node->next_map = next_map;
}

uint8_t CommandMap::lookup_command(const uint32_t * command_characters,
        size_t length, Command & command) const
{
    std::list<Command::Unit> units;
    uint8_t scan_result = scan_units(units, command_characters, length, &m_root_node);
    if (scan_result == COMMAND_COMPLETE)
    {
        int i = 0;
        for (const auto & unit : units)
        {
            switch (i)
            {
            case 0:
                command.main = unit;
                break;
            case 1:
                command.motion = unit;
                break;
            }
            i++;
        }
    }
    return scan_result;
}

uint8_t CommandMap::scan_units(
        std::list<Command::Unit> & units,
        const uint32_t * command_characters, size_t length,
        const Node * start_node) const
{
    if (length < 1u)
        return COMMAND_INCOMPLETE;
    uint8_t result = COMMAND_INCOMPLETE;
    uint8_t recurse_result = COMMAND_INVALID;
    bool gathering_count = true;
    Command::Unit unit;
    const Node * node = start_node;
    for (size_t i = 0u; i < length; i++)
    {
        uint32_t c = command_characters[i];
        if (gathering_count &&
                ((('1' <= c) && (c <= '9')) ||
                 ((c == '0') && (unit.count != 0u))))
        {
            unit.count *= 10u;
            unit.count += (c - '0');
        }
        else
        {
            gathering_count = false;
            auto it = node->next_chars.find(c);
            if (it != node->next_chars.end())
            {
                node = &*it->second;
                if (node->flags & Node::FLAG_TERMINATOR)
                {
                    unit.id = node->id;
                    if (node->flags & Node::FLAG_FOLLOWING_CHAR)
                    {
                        if (i < (length - 1u))
                        {
                            unit.following_char = command_characters[i + 1u];
                            units.push_front(unit);
                            return COMMAND_COMPLETE;
                        }
                        return COMMAND_INCOMPLETE;
                    }
                    else if (node->next_map)
                    {
                        recurse_result = scan_units(units,
                                &command_characters[i + 1u],
                                length - i - 1u, &node->next_map->m_root_node);
                        if (recurse_result == COMMAND_COMPLETE)
                        {
                            units.push_front(unit);
                            return COMMAND_COMPLETE;
                        }
                        result = recurse_result;
                    }
                    else
                    {
                        units.push_front(unit);
                        return COMMAND_COMPLETE;
                    }
                }
            }
            else
            {
                return recurse_result;
            }
        }
    }
    return result;
}
