// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
//
// Licensed under the BSD 3-Clause License (the "License"); you may not use this
// file except in compliance with the License. You may obtain a copy of the
// License at
//
// https://opensource.org/licenses/BSD-3-Clause
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

#include "tile/net/http/types.h"
#include "tile/base/internal/early_init.h"

#include <array>
#include <mutex>
#include <unordered_map>

#include "tile/base/enum.h"

namespace tile {

namespace {

const std::array<Slice, 600>
GetStatusCodes()
{
    static std::array<Slice, 600> descs;
    std::once_flag flag;
    std::call_once(flag, [&] {
        descs[100] = "Continue";
        descs[101] = "Switching Protocols";
        descs[103] = "Early Hints";
        descs[200] = "OK";
        descs[201] = "Created";
        descs[202] = "Accepted";
        descs[203] = "Non-Authoritative Information";
        descs[204] = "No Content";
        descs[205] = "Reset Content";
        descs[206] = "Partial Content";
        descs[300] = "Multiple Choices";
        descs[301] = "Moved Permanently";
        descs[302] = "Found";
        descs[303] = "See Other";
        descs[304] = "Not Modified";
        descs[307] = "Temporary Redirect";
        descs[308] = "Permanent Redirect";
        descs[400] = "Bad Request";
        descs[401] = "Unauthorized";
        descs[402] = "Payment Required";
        descs[403] = "Forbidden";
        descs[404] = "Not Found";
        descs[405] = "Method Not Allowed";
        descs[406] = "Not Acceptable";
        descs[407] = "Proxy Authentication Required";
        descs[408] = "Request Timeout";
        descs[409] = "Conflict";
        descs[410] = "Gone";
        descs[411] = "Length Required";
        descs[412] = "Precondition Failed";
        descs[413] = "Payload Too Large";
        descs[414] = "URI Too Long";
        descs[415] = "Unsupported Media Type";
        descs[416] = "Range Not Satisfiable";
        descs[417] = "Expectation Failed";
        descs[418] = "I'm a teapot";
        descs[422] = "Unprocessable Entity";
        descs[425] = "Too Early";
        descs[426] = "Upgrade Required";
        descs[428] = "Precondition Required";
        descs[429] = "Too Many Requests";
        descs[431] = "Request Header Fields Too Large";
        descs[451] = "Unavailable For Legal Reasons";
        descs[500] = "Internal Server Error";
        descs[501] = "Not Implemented";
        descs[502] = "Bad Gateway";
        descs[503] = "Service Unavailable";
        descs[504] = "Gateway Timeout";
        descs[505] = "HTTP Version Not Supported";
        descs[506] = "Variant Also Negotiates";
        descs[507] = "Insufficient Storage";
        descs[508] = "Loop Detected";
        descs[510] = "Not Extended";
        descs[511] = "Network Authentication Required";
    });
    return descs;
};

Slice kStringizedVersion[] = {"(Unspecified)", "HTTP/1.0", "HTTP/1.1", "HTTP/2", "HTTP/3"};

const std::unordered_map<Slice, HttpVersion> kVersionEnumerators = {
    {"(Unspecified)", HttpVersion::Unspecified},
    {"HTTP/1.0",      HttpVersion::V_1_0      },
    {"HTTP/1.1",      HttpVersion::V_1_1      },
    {"HTTP/2",        HttpVersion::V_2        },
    {"HTTP/3",        HttpVersion::V_3        },
};

Slice kStringifiedMethods[] = {
    /* Unspecified */ "UNSPECIFIED",
    /* Head */ "HEAD",
    /* Get */ "GET",
    /* Post */ "POST",
    /* Put */ "PUT",
    /* Delete */ "DELETE",
    /* Options */ "OPTIONS",
    /* Trace */ "TRACE",
    /* Connect */ "CONNECT",
    /* Patch */ "PATCH",
};

const std::unordered_map<Slice, HttpMethod> kMethodEnumerators = {
    {"UNSPECIFIED", HttpMethod::Unspecified},
    {"HEAD",        HttpMethod::Head       },
    {"GET",         HttpMethod::Get        },
    {"POST",        HttpMethod::Post       },
    {"PUT",         HttpMethod::Put        },
    {"DELETE",      HttpMethod::Delete     },
    {"TRACE",       HttpMethod::Trace      },
    {"CONNECT",     HttpMethod::Connect    },
    {"PATCH",       HttpMethod::Patch      },
};

}// namespace

Slice
ToSlice(HttpStatus status) noexcept
{
    auto v = underlying_value(status);
    if (v < 0 || v > sizeof(GetStatusCodes())) { return internal::EarlyInitConstant<Slice>(); }
    return GetStatusCodes()[v];
}

Slice
ToSlice(HttpVersion version) noexcept
{
    auto index = underlying_value(version);
    if (index < 0 || index > sizeof(kStringizedVersion)) { return {}; }
    return kStringizedVersion[index];
}

Slice
ToSlice(HttpMethod method) noexcept
{
    auto v = underlying_value(method);
    if (v < 0 || v > sizeof(kStringifiedMethods)) { return {}; }
    return kStringifiedMethods[v];
}

std::optional<HttpMethod>
TryParseTraits<HttpMethod>::TryParse(Slice s)
{
    auto iter = kMethodEnumerators.find(s);
    if (iter == kMethodEnumerators.end()) { return std::nullopt; }
    return iter->second;
}

std::optional<HttpVersion>
TryParseTraits<HttpVersion>::TryParse(Slice version)
{
    auto iter = kVersionEnumerators.find(version);
    if (iter == kVersionEnumerators.end()) { return std::nullopt; }
    return iter->second;
}

std::ostream &
operator<<(std::ostream &os, HttpMethod method)
{
    return os << ToSlice(method);
}

std::ostream &
operator<<(std::ostream &os, HttpStatus status)
{
    return os << ToSlice(status);
}

std::ostream &
operator<<(std::ostream &os, HttpVersion version)
{
    return os << ToSlice(version);
}

}// namespace tile
