#include "request_parser.hpp"

#include <sstream>

#include "request.hpp"


namespace http::server {
    static void parseParams(Url& u, const std::string& query) {
            std::istringstream iss(query);
            std::string param;
            while (std::getline(iss, param, '&')) {
                size_t equal_pos = param.find('=');
                if (equal_pos != std::string::npos) {
                    const std::string key = param.substr(0, equal_pos);
                    const std::string value = param.substr(equal_pos + 1);
                    u.params[key] = value;
                }
            }
        }

    Url::Url(const std::string &url) {
        size_t question_mark_pos = url.find('?');
        if (question_mark_pos != std::string::npos) {
            path = url.substr(0, question_mark_pos);
            const std::string query = url.substr(question_mark_pos + 1);
            parseParams(*this, query);
        } else {
            path = url;
        }
    }

    Url::~Url() = default;


    RequestParser::RequestParser()
        : state_(method_start) {
    }

    void RequestParser::reset() {
        state_ = method_start;
    }

    RequestParser::result_type RequestParser::consume(Request &req, char input) {
        switch (state_) {
            case method_start:
                if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
                    return bad;
                } else {
                    state_ = method;
                    req.method.push_back(input);
                    return indeterminate;
                }
            case method:
                if (input == ' ') {
                    state_ = uri;
                    return indeterminate;
                } else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
                    return bad;
                } else {
                    req.method.push_back(input);
                    return indeterminate;
                }
            case uri:
                if (input == ' ') {
                    state_ = http_version_h;
                    return indeterminate;
                } else if (is_ctl(input)) {
                    return bad;
                } else {
                    req.queryUri.push_back(input);
                    return indeterminate;
                }
            case http_version_h:
                if (input == 'H') {
                    state_ = http_version_t_1;
                    return indeterminate;
                } else {
                    return bad;
                }
            case http_version_t_1:
                if (input == 'T') {
                    state_ = http_version_t_2;
                    return indeterminate;
                } else {
                    return bad;
                }
            case http_version_t_2:
                if (input == 'T') {
                    state_ = http_version_p;
                    return indeterminate;
                } else {
                    return bad;
                }
            case http_version_p:
                if (input == 'P') {
                    state_ = http_version_slash;
                    return indeterminate;
                } else {
                    return bad;
                }
            case http_version_slash:
                if (input == '/') {
                    req.http_version_major = 0;
                    req.http_version_minor = 0;
                    state_ = http_version_major_start;
                    return indeterminate;
                } else {
                    return bad;
                }
            case http_version_major_start:
                if (is_digit(input)) {
                    req.http_version_major = req.http_version_major * 10 + input - '0';
                    state_ = http_version_major;
                    return indeterminate;
                } else {
                    return bad;
                }
            case http_version_major:
                if (input == '.') {
                    state_ = http_version_minor_start;
                    return indeterminate;
                } else if (is_digit(input)) {
                    req.http_version_major = req.http_version_major * 10 + input - '0';
                    return indeterminate;
                } else {
                    return bad;
                }
            case http_version_minor_start:
                if (is_digit(input)) {
                    req.http_version_minor = req.http_version_minor * 10 + input - '0';
                    state_ = http_version_minor;
                    return indeterminate;
                } else {
                    return bad;
                }
            case http_version_minor:
                if (input == '\r') {
                    state_ = expecting_newline_1;
                    return indeterminate;
                } else if (is_digit(input)) {
                    req.http_version_minor = req.http_version_minor * 10 + input - '0';
                    return indeterminate;
                } else {
                    return bad;
                }
            case expecting_newline_1:
                if (input == '\n') {
                    state_ = header_line_start;
                    return indeterminate;
                } else {
                    return bad;
                }
            case header_line_start:
                if (input == '\r') {
                    state_ = expecting_newline_3;
                    return indeterminate;
                } else if (!req.headers.empty() && (input == ' ' || input == '\t')) {
                    state_ = header_lws;
                    return indeterminate;
                } else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
                    return bad;
                } else {
                    req.headers.emplace_back();
                    req.headers.back().name.push_back(input);
                    state_ = header_name;
                    return indeterminate;
                }
            case header_lws:
                if (input == '\r') {
                    state_ = expecting_newline_2;
                    return indeterminate;
                } else if (input == ' ' || input == '\t') {
                    return indeterminate;
                } else if (is_ctl(input)) {
                    return bad;
                } else {
                    state_ = header_value;
                    req.headers.back().value.push_back(input);
                    return indeterminate;
                }
            case header_name:
                if (input == ':') {
                    state_ = space_before_header_value;
                    return indeterminate;
                } else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
                    return bad;
                } else {
                    req.headers.back().name.push_back(input);
                    return indeterminate;
                }
            case space_before_header_value:
                if (input == ' ') {
                    state_ = header_value;
                    return indeterminate;
                } else {
                    return bad;
                }
            case header_value:
                if (input == '\r') {
                    state_ = expecting_newline_2;
                    return indeterminate;
                } else if (is_ctl(input)) {
                    return bad;
                } else {
                    req.headers.back().value.push_back(input);
                    return indeterminate;
                }
            case expecting_newline_2:
                if (input == '\n') {
                    state_ = header_line_start;
                    return indeterminate;
                } else {
                    return bad;
                }
            case expecting_newline_3:
                if (input == '\n') {
                    req.url = std::make_unique<Url>(req.queryUri);
                    return good;
                }
                return bad;
            default:
                return bad;
        }
    }

    bool RequestParser::is_char(int c) {
        return c >= 0 && c <= 127;
    }

    bool RequestParser::is_ctl(int c) {
        return (c >= 0 && c <= 31) || (c == 127);
    }

    bool RequestParser::is_tspecial(int c) {
        switch (c) {
            case '(':
            case ')':
            case '<':
            case '>':
            case '@':
            case ',':
            case ';':
            case ':':
            case '\\':
            case '"':
            case '/':
            case '[':
            case ']':
            case '?':
            case '=':
            case '{':
            case '}':
            case ' ':
            case '\t':
                return true;
            default:
                return false;
        }
    }

    bool RequestParser::is_digit(int c) {
        return c >= '0' && c <= '9';
    }
} // namespace http::server
