// This is a personal academic project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include <stdio.h>
#include <stdlib.h>

#include "cmd.h"

void cmd_parser_init(struct cmd_parser *p) {
    p->state = cmd_version;
    p->remaining = 0;
}

enum cmd_state cmd_parser_feed(struct cmd_parser * p, uint8_t b) {
    switch (p->state) {
        case cmd_version:
            if (0x01 == b) {
                p->state = cmd_token;
            } else {
                p->state = cmd_error_unsupported_version;
            }
            p->remaining = 8;
            break;
        case cmd_token:
            p->token <<= 8;
            p->token += b;
            p->remaining--;
            if (p->remaining == 0) {
                p->state = cmd_cmd;
                p->on_token(p, p->token);
            }
            break;
        case cmd_cmd:
            if (NULL != p->on_cmd) {
                p->state = cmd_done;
                p->on_cmd(p, b);
            }
            break;
        case cmd_parameters:
            switch (p->param_state) {
                case param_ulen:
                    p->remaining = b;
                    if (p->remaining == 0)
                        p->state = cmd_error_invalid_parameter;
                    p->param_state = param_uname;
                    break;
                case param_uname:
                    if (!is_valid(b)) {
                        p->state = cmd_error_invalid_parameter;
                        break;
                    }
                    *(p->username + p->idx++) = (char) b;
                    p->remaining--;
                    if (p->remaining == 0) {
                        *(p->username + p->idx++) = 0;
                        p->idx = 0;

                        if (*p->cmd == CMD_DELETE_USER)
                            p->state = cmd_done;
                        else if (*p->cmd == CMD_MODIFY_USERNAME)
                            p->param_state = param_new_ulen;
                        else if (*p->cmd == CMD_MODIFY_PASSWORD || *p->cmd == CMD_ADD_USER)
                            p->param_state = param_plen;
                    }
                    break;
                case param_plen:
                    p->remaining = b;
                    if (p->remaining == 0)
                        p->state = cmd_error_invalid_parameter;
                    p->param_state = param_passwd;
                    break;
                case param_passwd:
                    if (!is_valid(b)) {
                        p->state = cmd_error_invalid_parameter;
                        break;
                    }
                    *(p->password + p->idx++) = (char) b;
                    p->remaining--;

                    if (p->remaining == 0) {
                        *(p->password + p->idx++) = 0;
                        p->idx = 0;
                        p->state = cmd_done;
                    }
                    break;
                case param_byte:
                    *p->byte = b;
                    p->state = cmd_done;
                    break;
                case param_new_ulen:
                    p->remaining = b;
                    if (p->remaining == 0)
                        p->state = cmd_error_invalid_parameter;
                    p->param_state = param_new_uname;
                    break;
                case param_new_uname:
                    if (!is_valid(b)) {
                        p->state = cmd_error_invalid_parameter;
                        break;
                    }
                    *(p->new_username + p->idx++) = (char) b;
                    p->remaining--;

                    if (p->remaining == 0) {
                        *(p->new_username + p->idx++) = 0;
                        p->idx = 0;
                        p->state = cmd_done;
                    }
                    break;
                case param_value:
                    *p->value <<= 8;
                    *p->value += b;
                    p->remaining--;
                    if (p->remaining == 0) {
                        p->state = cmd_done;
                    }
            }
            break;
        case cmd_done:
        case cmd_error_unsupported_version:
        case cmd_error_unsupported_command:
        case cmd_error_invalid_parameter:
        case cmd_error_invalid_token:
            break;
        default:
            fprintf(stderr, "Unknown state %d\n", p->state);
            abort();
    }
    return p->state;
}

extern bool cmd_is_done(const enum cmd_state state, bool * errored) {
    bool ret = true;

    switch (state) {
        case cmd_error_unsupported_command:
        case cmd_error_invalid_token:
        case cmd_error_unsupported_version:
        case cmd_error_invalid_parameter:
            if (0 != errored) {
                *errored = true;
            }
            break;
        case cmd_done:
            break;
        default:
            ret = false;
            break;
    }

    return ret;
}

extern uint8_t cmd_error(const struct cmd_parser * p) {
    uint8_t ret;
    switch (p->state) {
        case cmd_error_unsupported_version:
            ret = CONFIG_VERSION_NOT_SUPPORTED;
            break;
        case cmd_error_unsupported_command:
            ret = CMD_NOT_SUPPORTED_CMD;
            break;
        case cmd_error_invalid_token:
            ret = CONFIG_INVALID_TOKEN;
            break;
        case cmd_error_invalid_parameter:
            ret = CONFIG_INVALID_PARAMETER;
            break;
        default:
            ret = CONFIG_OK;
            break;
    }

    return ret;
}

extern void cmd_parser_close(struct cmd_parser * p) {}

extern enum cmd_state cmd_consume(buffer * b, struct cmd_parser * p, bool * errored) {
    enum cmd_state st = p->state;

    while (buffer_can_read(b)) {
        const uint8_t c = buffer_read(b);
        st = cmd_parser_feed(p, c);
        if (cmd_is_done(st, errored)) {
            break;
        }
    }

    return st;
}
