// 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 <string.h>
#include <arpa/inet.h>
#include <stddef.h>

#include "buffer.h"
#include "selector.h"
#include "confignio.h"
#include "cmd.h"
#include "server.h"
#include "socks5nio.h"

#define N(x) (sizeof(x)/sizeof((x)[0]))

static void on_token(struct cmd_parser * p, const uint64_t token) {
    *p->verified = verify_token(token);
    p->state = cmd_cmd;
    if (!*p->verified) {
        p->state = cmd_error_invalid_token;
    }
}

static void on_cmd(struct cmd_parser * p, const uint8_t cmd) {
    uint8_t * selected = p->cmd;
    switch (cmd) {
        case CMD_GET_METRICS:
            *selected = CMD_GET_METRICS;
            break;
        case CMD_GET_USER_PAGES:
            *selected = CMD_GET_USER_PAGES;
            break;
        case CMD_LIST_USERS:
            *selected = CMD_LIST_USERS;
            p->state = cmd_parameters;
            p->param_state = param_value;
            p->remaining = 2;
            break;
        case CMD_MODIFY_USERNAME:
            *selected = CMD_MODIFY_USERNAME;
            p->state = cmd_parameters;
            p->param_state = param_ulen;
            p->remaining = 1;
            break;
        case CMD_MODIFY_PASSWORD:
            *selected = CMD_MODIFY_PASSWORD;
            p->state = cmd_parameters;
            p->param_state = param_ulen;
            p->remaining = 1;
            break;
        case CMD_ADD_USER:
            *selected = CMD_ADD_USER;
            p->state = cmd_parameters;
            p->param_state = param_ulen;
            p->remaining = 1;
            break;
        case CMD_DELETE_USER:
            *selected = CMD_DELETE_USER ;
            p->state = cmd_parameters;
            p->param_state = param_ulen;
            p->remaining = 1;
            break;
        case CMD_GET_PASSWORD_DISSECTOR_STATUS:
            *selected = CMD_GET_PASSWORD_DISSECTOR_STATUS;
            break;
        case CMD_MODIFY_PASSWORD_DISSECTOR_STATUS:
            *selected = CMD_MODIFY_PASSWORD_DISSECTOR_STATUS;
            p->state = cmd_parameters;
            p->param_state = param_byte;
            p->remaining = 1;
            break;
        case CMD_GET_AUTHENTICATION_STATUS:
            *selected = CMD_GET_AUTHENTICATION_STATUS;
            break;
        case CMD_MODIFY_AUTHENTICATION_STATUS:
            *selected = CMD_MODIFY_AUTHENTICATION_STATUS;
            p->state = cmd_parameters;
            p->param_state = param_byte;
            p->remaining = 1;
            break;
        default:
            *selected = CMD_NOT_SUPPORTED_CMD;
    }
}

#define ATTACHMENT(key) ((struct config *)(key)->data)

static void config_read_init(struct selector_key * key) {
    struct cmd_st *d = &ATTACHMENT(key)->client.cmd;

    d->rb = &(ATTACHMENT(key)->read_buffer);
    d->wb = &(ATTACHMENT(key)->write_buffer);
    d->parser.cmd = &d->cmd;
    d->parser.verified = &d->verified;
    d->parser.on_cmd = on_cmd;
    d->parser.on_token = on_token;
    d->parser.idx = 0;

    d->parser.username = d->username;
    d->parser.password = d->password;
    d->parser.byte = &d->byte;
    d->parser.value = &d->value;
    d->parser.new_username = d->new_username;
    cmd_parser_init(&d->parser);
}

ssize_t cmd_process(struct selector_key *key, buffer * b) {
    struct cmd_st *d = &ATTACHMENT(key)->client.cmd;

    size_t n;
    uint8_t *buff = buffer_write_ptr(b, &n);

    if (n < 2) {
        return 0;
    }

    ssize_t i = 0;
    buff[i++] = 0x01;

    if (d->status == CMD_NOT_SUPPORTED_CMD || d->status == CONFIG_VERSION_NOT_SUPPORTED || d->status == CONFIG_INVALID_PARAMETER || d->status == CONFIG_INVALID_TOKEN) {
        buff[i++] = d->status;
        buffer_write_adv(b, i);
        return i;
    }

    int ret;
    uint64_t bytes;
    uint32_t total_conn, now_conn;
    uint16_t pages, k = 0, users = 0, status = 1;
    uint8_t buffer[PAGE_SIZE * USERNAME_MAX_LEN];

    buff[i++] = 0x00;
    switch (d->cmd) {
        case CMD_GET_METRICS:
            bytes = get_bytes_transferred();
            now_conn = get_now_connect();
            total_conn = get_total_connect();

            buff[i++] = total_conn >> 24;
            buff[i++] = (uint8_t) (total_conn >> 16);
            buff[i++] = (uint8_t) (total_conn >> 8);
            buff[i++] = (uint8_t) total_conn;

            buff[i++] = now_conn >> 24;
            buff[i++] = (uint8_t) (now_conn >> 16);
            buff[i++] = (uint8_t) (now_conn >> 8);
            buff[i++] = (uint8_t) now_conn;

            buff[i++] = bytes >> 56;
            buff[i++] = (uint8_t) (bytes >> 48);
            buff[i++] = (uint8_t) (bytes >> 40);
            buff[i++] = (uint8_t) (bytes >> 32);
            buff[i++] = (uint8_t) (bytes >> 24);
            buff[i++] = (uint8_t) (bytes >> 16);
            buff[i++] = (uint8_t) (bytes >> 8);
            buff[i++] = (uint8_t) bytes;

            break;
        case CMD_GET_USER_PAGES:
            pages = get_user_pages();
            buff[i++] = (uint8_t) (pages >> 8);
            buff[i++] = (uint8_t) pages;
            break;
        case CMD_LIST_USERS:
            if (!get_users((char *) buffer, d->value - 1)) {
                buff[status] = CONFIG_INVALID_PARAMETER;
                break;
            }
            while (users < PAGE_SIZE && !(k > 0 && buffer[k - 1] == '\n' && buffer[k] == '\n')) {
                if (buffer[k] == '\n') {
                    users++;
                }
                buff[i++] = buffer[k++];
            }
            buff[i++] = buffer[k];
            break;
        case CMD_MODIFY_USERNAME:
            ret = modify_username(d->username, d->new_username);
            if (!ret) {
                buff[status] = CONFIG_USER_NOT_FOUND;
            }
            else if (ret == -1) {
                buff[status] = CONFIG_SERVER_ERROR;
            }
            break;
        case CMD_MODIFY_PASSWORD:
            ret = modify_password(d->username, d->password);
            if (!ret) {
                buff[status] = CONFIG_USER_NOT_FOUND;
            }
            else if (ret == -1) {
                buff[status] = CONFIG_SERVER_ERROR;
            }
            break;
        case CMD_ADD_USER:
            ret = add_user(d->username, d->password);
            if (ret == -1) {
                buff[status] = CONFIG_SERVER_ERROR;
            }
            else if (ret == -2) {
                buff[status] = CONFIG_ALREADY_USER_EXISTS;
            }
            break;
        case CMD_DELETE_USER:
            if (!delete_user(d->username)) {
                buff[status] = CONFIG_USER_NOT_FOUND;
                break;
            }
            break;
        case CMD_GET_PASSWORD_DISSECTOR_STATUS:
            buff[i++] = get_pass_dissector_status();
            break;
        case CMD_MODIFY_PASSWORD_DISSECTOR_STATUS:
            set_pass_dissector_status(d->byte);
            break;
        case CMD_GET_AUTHENTICATION_STATUS:
            buff[i++] = get_auth_status();
            break;
        case CMD_MODIFY_AUTHENTICATION_STATUS:
            set_auth_status(d->byte);
            break;
        default:
            buff[status] = CONFIG_UNKNOWN_COMMAND;
            break;
    }

    buffer_write_adv(b, i);
    return i;
}

void config_read(struct selector_key *key) {
    struct cmd_st *d = &ATTACHMENT(key)->client.cmd;
    bool error = false;
    uint8_t *ptr;
    size_t  count;
    ssize_t  n;
    struct sockaddr_storage client_addr;
    socklen_t client_addr_len = sizeof(client_addr);

    config_read_init(key);
    ptr = buffer_write_ptr(d->rb, &count);
    n = recvfrom(key->fd, ptr, BUFF_SIZE, 0, (struct sockaddr *) &client_addr, &client_addr_len);
    if (n > 0) {
        buffer_write_adv(d->rb, n);
        const enum cmd_state st = cmd_consume(d->rb, &d->parser, &error);

        if (cmd_is_done(st, 0)) {
            if (error)
                d->status = cmd_error(&d->parser);
            if (SELECTOR_SUCCESS == selector_set_interest_key(key, OP_WRITE)) {
                if (cmd_process(key, d->wb) == 0)
                    return;
            } else {
                return;
            }
        }
    } else {
        return;
    }

    ptr = buffer_read_ptr(d->wb, &count);
    n = sendto(key->fd, ptr, count, 0, (struct sockaddr *) &client_addr, sizeof(client_addr));
    if (n == -1) {
        return;
    } else {
        buffer_read_adv(d->wb, n);
        if (!buffer_can_read(d->wb)) {
            if (SELECTOR_SUCCESS != selector_set_interest_key(key, OP_READ)) {
                return;
            }
        }
    }
    buffer_reset(d->rb);
    buffer_reset(d->wb);
    selector_set_interest_key(key, OP_READ);
}
