#include "tile/rpc/protocol/http/buffer_io.h"

#include "tile/base/slice.h"
#include "tile/base/string.h"
#include "tile/net/http/http_headers.h"
#include "tile/net/http/http_request.h"
#include "tile/net/http/http_response.h"
#include "tile/net/http/types.h"

#include "gflags/gflags.h"

#include <cstdlib>
#include <cstring>

DEFINE_int32(tile_http_max_header_size, 8192, "Maximum size of total size of HTTP headers");

namespace tile {
namespace http {
namespace {
inline void
WriteHeader(const HttpHeaders &headers, NoncontiguousBufferBuilder *builder)
{
    for (auto header : headers) {
        auto &&key   = header.first;
        auto &&value = header.second;
        builder->Append(key, ": ", value, "\r\n");
    }
}

template<typename T>
inline Slice
ToSlice(const T &block)
{
    return Slice(block.data(), block.size());
}

constexpr auto kEndOfHeaderMarker        = "\r\n\r\n";
static const auto kEndOfHeaderMarkerSize = strlen(kEndOfHeaderMarker);

inline bool
EasyDetectHttp(const void *data, std::size_t length)
{
    static const Slice kLeadingWords[] = {
        "HTTP/1.1", "HEAD", "GET ", "POST", "PUT ", "DELETE", "TRACE", "CONNECT", "PATCH"};
    static const std::size_t kMinLength = Slice("HTTP/1.1").size();

    if (length < kMinLength) { return false; }

    for (auto &&e : kLeadingWords) {
        if (memcmp(data, e.data(), 4) == 0) { return true; }
    }

    return false;
}

std::size_t
DetermineHeaderSizeFast(const char *const ptr, std::size_t length)
{
    char *first_end_pos = (char *) memmem(ptr, length, "\r\n\r\n", 4);

    if (first_end_pos == NULL) { return -1; }
    return first_end_pos + 4 - ptr;

    /*
    auto end_ptr = ptr + length;
    auto p       = ptr + 1;
    while (true) {
        // First '\n' in '\r\n\r\n'
        p = static_cast<char *>(memchr(const_cast<char *>(p), '\n', end_ptr - p));
        if (TILE_UNLIKELY(p == nullptr || end_ptr - p < 3)) { return -1; }

        if (p[2] == '\n') {
            TILE_CHECK_GE(p - 1, ptr);
            if (TILE_LIKELY(Slice(p - 1, 4) == kEndOfHeaderMarker)) {
                return (p + 3) - ptr;
            } else {
                return -1;
            }
        }
        ++p;
    }
    */
}

}// namespace

ReadStatus
ReadHeader(const NoncontiguousBuffer &buffer, HeaderBlock *header)
{
    auto first_block = buffer.FirstContiguous();
    const auto size  = DetermineHeaderSizeFast(first_block.data(), first_block.size());

    // Find a header
    if (TILE_LIKELY(size != -1)) {
        header->first  = std::unique_ptr<char[]>(new char[size]);
        header->second = size;
        memcpy(header->first.get(), first_block.data(), size);
    } else {
        if (first_block.size() >= 10 && !EasyDetectHttp(first_block.data(), first_block.size())) {
            return ReadStatus::UnexpectedFormat;
        }

        if (buffer.ByteSize() == first_block.size()) {
            return first_block.size() < FLAGS_tile_http_max_header_size ? ReadStatus::NoEnoughData : ReadStatus::Error;
        }

        auto slow_buffer = FlattenSlowUntil(buffer, kEndOfHeaderMarker);
        if (!EndsWith(slow_buffer, kEndOfHeaderMarker)) {
            return slow_buffer.size() < FLAGS_tile_http_max_header_size ? ReadStatus::NoEnoughData : ReadStatus::Error;
        }

        const auto slow_buffer_size = slow_buffer.size();
        header->first               = std::unique_ptr<char[]>(new char[slow_buffer_size]);
        header->second              = slow_buffer_size;
        memcpy(header->first.get(), slow_buffer.data(), slow_buffer_size);
    }

    if (TILE_UNLIKELY(!EasyDetectHttp(header->first.get(), header->second))) { return ReadStatus::UnexpectedFormat; }

    return ReadStatus::OK;
}

Slice
ReadFieldFromRawBytes(Slice view, Slice key)
{
    std::size_t pos = 0;
    while (true) {
        auto field = view.substr(pos);
        // message-header = field-name ":" [ field-value ]
        // field-name = token
        // field-value = *( field-content | LWS )

        // min size is [key:\r\n\r\n]
        // if (view.size() < key.size() + kEndOfHeaderMarkerSize + 1) {
        //   return "";
        // }

        if (EqualsIgnoreCase(field.substr(0, key.size()), key)) {
            if (TILE_UNLIKELY(field.size() == key.size())) {
                TILE_LOG_WARNING_EVERY_SECOND("Invalid Http header? When read [{}].", field);
                return "";
            }

            if (TILE_LIKELY(field[key.size()] == ':')) {
                const auto last_pos = field.find('\r');

                TILE_CHECK_NE(last_pos, Slice::npos);
                TILE_CHECK_GT(last_pos, key.size() + 1);

                const auto start_pos = key.size() + 1;
                return Trim(field.substr(start_pos, last_pos - (start_pos)));
            }
        }

        pos = view.find('\n', pos + 1);
        if (pos == Slice::npos) { return ""; }
        ++pos;
    }
}

bool
ParseMessagePartial(HeaderBlock &&storage, Slice *start_line, HttpHeaders *headers)
{
    auto storage_ref = headers->RetrieveHeaderStorage(std::move(storage));
    std::vector<std::pair<Slice, Slice>> fields;
    fields.reserve(8);

    TILE_CHECK(EndsWith(storage_ref, kEndOfHeaderMarker));
    auto view = storage_ref.substr(0, storage_ref.size() - 2);
    auto pos  = view.find('\n');
    TILE_CHECK_NE(pos, Slice::npos);
    if (TILE_UNLIKELY(pos == 1)) {
        // is [\r\n\r\n]
        return false;
    }
    *start_line = view.substr(0, pos - 1);
    view        = view.substr(pos + 1);
    while (!view.empty()) {
        auto pos = view.find('\n');
        TILE_CHECK_NE(pos, Slice::npos);
        if (TILE_UNLIKELY(pos < 2 || view[pos - 1] != '\r')) {
            TILE_LOG_WARNING_EVERY_SECOND("Invalid Http header?");
            return false;
        }

        // get a line
        auto current = view.substr(0, pos - 1);
        auto poclon  = current.find(':');
        if (poclon == Slice::npos) {
            TILE_LOG_WARNING_EVERY_SECOND("Invalid message header? Read [{}].", current);
            return false;
        }

        auto name  = current.substr(0, poclon);
        auto value = current.substr(poclon + 1);

        if (name.empty()) {
            TILE_LOG_WARNING_EVERY_SECOND("Empty field-name? Read [{}].", current);
            return false;
        }

        fields.emplace_back(name, Trim(value));
        view = view.substr(pos + 1);
    }

    headers->RetrieveFields(std::move(fields));
    return true;
}

bool
ParseRequestStartLine(Slice s, HttpVersion *version, HttpMethod *method, Slice *uri)
{
    // Request-Line: Method SP Request-URI SP HTTP-Version CRLF
    auto splitted = Split(s, " ");
    if (splitted.size() < 3) {
        TILE_LOG_WARNING_EVERY_SECOND("Invalid HTTP request Start-Line: {}", s);
        return false;
    }

    // Parse HttpVersion
    {
        auto opt = TryParse<HttpVersion>(splitted[2]);
        if (!opt) {
            TILE_LOG_WARNING_EVERY_SECOND("Invalid HTTP request Start-Line: {}", s);
            return false;
        } else if (*opt != HttpVersion::V_1_0 && *opt != HttpVersion::V_1_1) {
            TILE_LOG_WARNING_EVERY_SECOND("Unsupport HttpVersion Start-Line: {}", s);
            return false;
        } else {
            *version = *opt;
        }
    }

    // Parse HttpMethod
    {
        auto opt = TryParse<HttpMethod>(splitted[0]);
        if (!opt) {
            TILE_LOG_WARNING_EVERY_SECOND("Invalid HTTP request Start-Line: {}", s);
            return false;
        } else {
            *method = *opt;
        }
    }

    *uri = splitted[1];
    return true;
}

bool
ParseResponseStartLine(Slice s, HttpStatus *code)
{
    // HTTP/1.1 301 Moved Permanently
    auto splitted = Split(s, " ", false, 3);
    if (splitted.size() < 2) {
        TILE_LOG_WARNING_EVERY_SECOND("Invalid HTTP response Start-Line: {}", s);
        return false;
    }

    auto opt = TryParse<int>(splitted[1]);
    if (!opt) {
        TILE_LOG_WARNING_EVERY_SECOND("Invalid HTTP response Start-Line: {}", s);
        return false;
    } else {
        *code = static_cast<HttpStatus>(*opt);
    }
    return true;
}

void
WriteMessage(const HttpRequest &req, NoncontiguousBufferBuilder *builder)
{
    // Request-Line: Method SP Request-URI SP HTTP-Version CRLF
    builder->Append(ToSlice(req.method()), " ", req.uri(), " ", ToSlice(req.version()), "\r\n");

    WriteHeader(*req.headers(), builder);
    builder->Append("\r\n");
    if (req.noncontiguous_body()) {
        builder->Append(*req.noncontiguous_body());
    } else {
        builder->Append(*req.body());
    }
}

void
WriteMessage(const HttpResponse &resp, NoncontiguousBufferBuilder *builder)
{
    // Status-Line: HTTP-Version SP Status-Code SP Reason-Phrase CRLF
    builder->Append(ToSlice(resp.version()), " ", GetStatusCodeWithDescString(resp.status()), "\r\n");

    WriteHeader(*resp.headers(), builder);
    builder->Append("\r\n");
    if (resp.noncontiguous_body()) {
        builder->Append(*resp.noncontiguous_body());
    } else {
        builder->Append(*resp.body());
    }
}

}// namespace http
}// namespace tile
