#include <library/cpp/http/server/http.h>
#include <library/cpp/json/json_writer.h>
#include <mapreduce/lib/all.h>
#include <util/generic/vector.h>

#include <wmconsole/version3/protos/sitemap.pb.h>
#include <wmconsole/version3/wmcutil/log.h>

#include "http_server.h"
#include <util/string/split.h>

namespace NWebmaster {

THttpCookie::THttpCookie(const TString &cookieHeader) {
    TVector<TString> parts = StringSplitter(cookieHeader).SplitBySet("; \t").SkipEmpty();

    for (const TString &part : parts) {
        TString key, value;

        TVector<TString> keyVal = StringSplitter(part).SplitBySet("= \t").SkipEmpty();
        if (keyVal.size() > 0) {
            key = keyVal[0];
        }

        if (keyVal.size() > 1) {
            value = keyVal[1];
        }

        Cookies[key] = value;
    }
}

bool THttpCookie::Get(const TString &name, TString &dest) const {
    auto it = Cookies.find(name);

    if (it != Cookies.end()) {
        dest = it->second;
        return true;
    }

    return false;
}

THttpServer::TReplier::TReplier(TUserService *userService)
    : UserService(userService)
{
}

bool THttpServer::TReplier::DoReply(const TReplyParams &params) {
    THttpServer::TRequest request(*this, params);

    if (UserService != nullptr) {
        bool result = UserService->Reply(request);
        params.Output.Finish();
        return result;
    }

    request.SendResponse("", 200, "text/html");

    return true;
}

THttpServer::TRequest::TRequest(TReplier &replierInstance, const TRequestReplier::TReplyParams &params)
    : ReplierParams(params)
    , ReplierInstance(replierInstance)
{
    TVector<TString> parts = SplitString(Input().FirstLine(), " ");

    if (parts.empty()) {
        ythrow yexception() << "there is no request method";
    }

    NUri::TUri uri;
    uri.Parse(parts[1]);

    Query = uri.GetField(NUri::TField::FieldQuery);
    Params = TCgiParameters(Query);
    Method = uri.GetField(NUri::TField::FieldPath);
    ThreadSpecificResource = params.ThreadSpecificResource;

    for (size_t i = 0; i < ReplierInstance.ParsedHeaders.size(); i++) {
        HeadersMap[ReplierInstance.ParsedHeaders[i].first] = ReplierInstance.ParsedHeaders[i].second;
    }

    auto cookieIt = HeadersMap.find("Cookie");
    if (cookieIt != HeadersMap.end()) {
        Cookies = THttpCookie(cookieIt->second);
    }
}

void THttpServer::TRequest::SendResponse(const TString& content, int code, const TString& contentType) {
    // Initial line
    //
    Output() << "HTTP/1.1" << " " << code << " ";

    switch (code) { // TODO switch should only choose the string
    case 200:
        Output() << "OK";
        break;
    case 400:
        Output() << "Bad Request";
        break;
    case 403:
        Output() << "Forbidden";
        break;
    case 404:
        Output() << "Not Found";
        break;
    case 500:
    default:
        Output() << "Internal Server Error";
        break;
    }

    Output() << "\r\n";

    // Headers
    //
    Output() << "Access-Control-Allow-Origin: *\r\n"
             << "Content-type: " << contentType << "; charset=utf-8\r\n";

    if (content.length())
        Output() << "Content-Length: " << content.length() << "\r\n";

    // Content
    //
    Output() << "\r\n" << content << "\r\n";
    Output().Finish();
}

TString THttpServer::TRequest::GetTimerString() const {
    return Timer.Get().ToString();
}

TString THttpServer::TRequest::GetRemoteAddr() const {
    char remoteAddr[128];
    ::GetRemoteAddr(ReplierInstance.Socket(), remoteAddr, sizeof(remoteAddr));
    return remoteAddr;
}

bool THttpServer::TRequest::GetParameter(const TString &name, TString &value) const {
    TCgiParameters::const_iterator it = Params.Find(name);

    if (it == Params.end()) {
        return false;
    }

    value = it->second;
    return true;
}

bool THttpServer::TRequest::GetFilledParameter(const TString &name, TString &value) const {
    TString tmpValue;

    if (!GetParameter(name, tmpValue)) {
        return false;
    }

    if (!tmpValue.empty()) {
        value = tmpValue;
        return true;
    }

    return false;
}

void THttpServer::TRequest::Die(int code, const TString& what) {
    SendResponse(what, code, "text/plain");
}

THttpServer::THttpServer(size_t port, size_t threads, TUserService *userService)
    : ::THttpServer(this, THttpServerOptions().SetPort(port).SetThreads(threads))
    , UserService(userService)
{
    Start();
}

THttpServer::THttpServer(const THttpServerOptions &options, TUserService *userService)
    : ::THttpServer(this, options)
    , UserService(userService)
{
    Start();
}

THttpServer::~THttpServer() {
    Shutdown();
}

TRequestReplier* THttpServer::CreateClient() {
    return new TReplier(UserService);
}

void *THttpServer::CreateThreadSpecificResource() {
    if (UserService) {
        return UserService->CreateThreadSpecificResource();
    }
    return nullptr;
}

void THttpServer::DestroyThreadSpecificResource(void *tsr) {
    if (UserService) {
        UserService->DestroyThreadSpecificResource(tsr);
    }
}

} //namespace NWebmaster
