#include "Response.h"
#include "Request.h"
#include <ctime>

namespace panda { namespace protocol { namespace http {

panda::time::TimezoneSP gmtz() {
    static panda::time::TimezoneSP inst = panda::time::tzget("GMT");
    return inst;
}

int64_t Response::Cookie::max_age_any () const {
    if (_max_age || !_expires) return _max_age;
    return Date(_expires).epoch() - std::time(nullptr);
}

optional<date::Date> Response::Cookie::expires_any () const {
    if (!_max_age) return expires();
    auto ret = Date::now();
    ret.epoch(ret.epoch() + _max_age);
    return ret;
}

string Response::Cookie::to_string (const string& name, const Request* req) const {
    string str(200); // should be enough for average set-cookie header
    serialize_to(str, name, req);
    return str;
}

void Response::Cookie::serialize_to (string& acc, const string& name, const Request* req) const {
    acc += name;
    acc += '=';
    acc += _value;

    const string& domain = !_domain && req ? req->headers.get("Host") : _domain;
    if (domain) {
        acc += "; Domain=";
        acc += domain;
    }

    if (_path) {
        acc += "; Path=";
        acc += _path;
    }

    if (_max_age) {
        acc += "; Max-Age=";
        acc += panda::to_string(_max_age);
    }
    else if (_expires) {
        acc += "; Expires=";
        acc += _expires;
    }

    if (_secure)    acc += "; Secure";
    if (_http_only) acc += "; HttpOnly";

    switch (_same_site) {
        case SameSite::None   : acc += "; SameSite=None"; break;
        case SameSite::Lax    : acc += "; SameSite=Lax";  break;
        case SameSite::Strict : acc += "; SameSite";      break;
        default               : {}
    }
}

string Response::_http_header (SerializationContext &ctx) const {
    //part 1: precalc pieces
    auto req = ctx.request;
    auto tmp_http_ver = ctx.http_version;
    auto tmp_code = code ? code : 200;

    auto out_connection = headers.get("Connection");
    if (req) {
        if (!tmp_http_ver) tmp_http_ver = req->http_version;

        if (req->keep_alive()) { // user can change connection to 'close'
            if (tmp_http_ver == 10 && !out_connection) out_connection = "keep-alive";
        }
        else { // user can not change connection to 'keep-alive'
            if (tmp_http_ver == 10) out_connection = "";
            else                    out_connection = "close";
        }
    }

    auto out_message = message ? message : message_for_code(tmp_code);

    string out_content_length;
    if (!chunked && !headers.has("Content-Length") && code != 304) {
        out_content_length = panda::to_string(ctx.body->length());
    }

    auto out_content_encoding = _content_encoding(ctx);

    // part 2: summarize pieces size
    size_t reserved = 5 + 4 + 4 + out_message.length() + 2 + headers.length() + 2;
    if (out_connection)       reserved += 10 + 2 + out_connection.length()   + 2;
    if (out_content_length)   reserved += 14 + 2 + out_content_length.length()   + 2;
    if (out_content_encoding) reserved += 16 + 2 + out_content_encoding.length() + 2;

    for (auto& h: ctx.handled_headers) reserved += h.name.length() + 2 + h.value.length() + 2;
    for (auto& h: headers) {
        if (ctx.handled_headers.has(h.name)) continue;  // let's speedup a little bit
        if (h.name == "Connection") continue;  // already handled
        reserved += h.name.length() + 2 + h.value.length() + 2;
    };
    reserved += (200 + 14) * cookies.fields.size(); // should be enough for average set-cookie header
    reserved += 2;

    // part 3: write out pieces
    string s(reserved);
    s += "HTTP/";
    switch (tmp_http_ver) {
        case 0:
        case 11: s += "1.1 "; break;
        case 10: s += "1.0 "; break;
        default: assert(false && "invalid http version");
    }
    s += panda::to_string(tmp_code);
    s += ' ';
    s += out_message;
    s += "\r\n";

    if (out_connection)       { s += "Connection: "      ; s += out_connection      ; s += "\r\n" ;};
    if (out_content_length)   { s += "Content-Length: "  ; s += out_content_length  ; s += "\r\n" ;};
    if (out_content_encoding) { s += "Content-Encoding: "; s += out_content_encoding; s += "\r\n" ;};

    for (auto& h: ctx.handled_headers) { s += h.name; s += ": "; s += h.value; s+= "\r\n"; }
    for (auto& h: headers)  {
        if (ctx.handled_headers.has(h.name)) continue;
        if (h.name == "Connection") continue;  // already handled
        s += h.name; s += ": "; s += h.value; s+= "\r\n";
    };
    for (auto& c: cookies.fields) {
        s += "Set-Cookie: ";
        c.value.serialize_to(s, c.name, req);
        s += "\r\n";
    }

    s += "\r\n";

    //assert(reserved >= s.length());
    return s;
}

std::vector<string> Response::to_vector (const Request* req) const {
    /* if client didn't announce Accept-Encoding or we do not support it, just pass data as it is */
    auto applied_compression
            = req && (compression.type != Compression::IDENTITY) && (req->allowed_compression() & (uint8_t)compression.type)
            ? compression.type
            : Compression::IDENTITY;

    SerializationContext ctx;
    ctx.compression = applied_compression;
    ctx.request = req;
    ctx.body    = &body;
    ctx.chunked = this->chunked;

    return _to_vector(ctx, [&]() { return _compile_prepare(ctx); }, [&]() { return _http_header(ctx); });
}

string Response::message_for_code (int code) {
    switch (code) {
        case 100: return "Continue";
        case 101: return "Switching Protocol";
        case 102: return "Processing";
        case 103: return "Early Hints";
        case 200: return "OK";
        case 201: return "Created";
        case 202: return "Accepted";
        case 203: return "Non-Authoritative Information";
        case 204: return "No Content";
        case 205: return "Reset Content";
        case 206: return "Partial Content";
        case 300: return "Multiple Choice";
        case 301: return "Moved Permanently";
        case 302: return "Found";
        case 303: return "See Other";
        case 304: return "Not Modified";
        case 305: return "Use Proxy";
        case 306: return "Switch Proxy";
        case 307: return "Temporary Redirect";
        case 308: return "Permanent Redirect";
        case 400: return "Bad Request";
        case 401: return "Unauthorized";
        case 402: return "Payment Required";
        case 403: return "Forbidden";
        case 404: return "Not Found";
        case 405: return "Method Not Allowed";
        case 406: return "Not Acceptable";
        case 407: return "Proxy Authentication Required";
        case 408: return "Request Timeout";
        case 409: return "Conflict";
        case 410: return "Gone";
        case 411: return "Length Required";
        case 412: return "Precondition Failed";
        case 413: return "Request Entity Too Large";
        case 414: return "Request-URI Too Long";
        case 415: return "Unsupported Media Type";
        case 416: return "Requested Range Not Satisfiable";
        case 417: return "Expectation Failed";
        case 500: return "Internal Server Error";
        case 501: return "Not Implemented";
        case 502: return "Bad Gateway";
        case 503: return "Service Unavailable";
        case 504: return "Gateway Timeout";
        case 505: return "HTTP Version Not Supported";
        default : return {};
    }
}

}}}