#include "http.h"
#include <cstring>
#include <xs/date.h>

namespace xs { namespace protocol { namespace http {

using panda::string;

static inline void msgfill (Message* m, const Hash& h) {
    for (auto& row : h) {
        auto key = row.key();
        if (!key.length()) continue;
        auto v = row.value();
        switch (key[0]) {
            case 'h':
                if      (key == "headers")      set_headers(m, v);
                else if (key == "http_version") m->http_version = xs::in<int>(v);
                break;
            case 'b':
                if (key == "body") m->body = xs::in<string>(v);
                break;
            case 'c':
                if      (key == "chunked") m->chunked = v.is_true();
                else if (key == "compress") {
                    if (v.is_array_ref()) {
                        Array a(v);
                        switch (a.size()) {
                            case 2: m->compression.level = (Compression::Level)SvIV(a[1]);
                                    // fall through
                            case 1: m->compression.type  = (Compression::Type)SvIV(a[0]);
                        }
                    }
                    else m->compression.type = (Compression::Type)SvIV(v);
                }
                break;
        }
    }
}

static Request::EncType get_encoding(const Sv& sv) {
    using Type = Request::EncType;
    int val = SvIV(sv);
    if (val < (int)Type::Multipart || val > (int)Type::UrlEncoded) throw "invalid form encoding";
    return (Type)val;
}

static void fill(Request::Form& form, Array& arr, Request::EncType enc_type)  {
    if (arr.size()) {
        form.enc_type(enc_type);
        bool even = arr.size() % 2 == 0;
        size_t last = even ? arr.size() - 1 : arr.size() - 2;
        for(size_t i = 0; i < last; i += 2) {
            string key = arr.at(i).as_string();
            auto value = arr.at(i +1);
            if(value.is_array_ref()) {
                auto values = Array(value);
                if (values.size() != 3) {
                    string err = "invalid file fieild '";
                    err += key;
                    err += ": it should be array [$filename => $filecontent, $filetype]";
                    throw err;
                }
                form.add(key, values[1].as_string(), values[0].as_string(), values[2].as_string());
            }
            else {
                form.add(key, value.as_string());
            }
        }
        if (!even) {
            string key = arr.back().as_string();
            form.add(key, "");
        }
    }
}

void fill_form(Request* req, const Sv& sv) {
    if (!sv || !sv.defined()) return;
    auto& form = req->form;
    if (sv.is_hash_ref()) {
        Hash h(sv);
        Request::EncType type = h.exists("enc_type") ? get_encoding(h.fetch("enc_type")) : Request::EncType::Multipart;
        Sv fields;
        if ((fields = h.fetch("fields"))) {
            Array arr(fields);
            fill(form, arr, type);
        }
        else form.enc_type(type);
    }
    else if (sv.is_array_ref()) {
        Array arr(sv);
        fill(form, arr, Request::EncType::Multipart);
    }
    else form.enc_type(get_encoding(sv));
}


void fill (Request* req, const Hash& h) {
    msgfill(req, h);
    for (auto& row : h) {
        auto key = row.key();
        if (!key.length()) continue;
        auto v = row.value();
        switch (key[0]) {
            case 'm':
                if (key == "method") { if (v.defined()) set_method(req, v); }
                break;
            case 'u':
                if (key == "uri") req->uri = xs::in<URISP>(v);
                break;
            case 'c':
                if (key == "cookies") set_request_cookies(req, v);
                break;
            case 'a':
                if (key == "allow_compression") {
                    if (v.is_array_ref()) {
                        Array av(v);
                        for (auto value : av) {
                            auto val = value.as_number<uint8_t>();
                            if (is_valid_compression(val)) req->allow_compression((Compression::Type)val);
                        }
                    }
                    else {
                        uint8_t val = SvIV(v);
                        if (is_valid_compression(val)) req->allow_compression((Compression::Type)val);
                    }
                }
                break;
        }
    }
}

void fill (Response* res, const Hash& h) {
    msgfill(res, h);
    for (auto& row : h) {
        auto key = row.key();
        if (!key.length()) continue;
        auto v = row.value();
        switch (key[0]) {
            case 'c':
                if      (key == "code")    res->code = v.as_number<int>();
                else if (key == "cookies") set_response_cookies(res, v);
                break;
            case 'm':
                if (key == "message") res->message = v.as_string();
                break;
        }
    }
}

void set_headers (Message* p, const Hash& hv) {
    p->headers.clear();
    for (const auto& row : hv) p->headers.add(string(row.key()), xs::in<string>(row.value()));
}

void set_method (Request* req, const Sv& method) {
    using Method = Request::Method;
    int num = SvIV_nomg(method);
    if (num < (int)Method::Options || num > (int)Method::Connect) throw panda::exception("invalid http method");
    req->method_raw((Method)num);
}

void set_request_cookies (Request* p, const Hash& hv) {
    p->cookies.clear();
    for (const auto& row : hv) p->cookies.add(string(row.key()), xs::in<string>(row.value()));
}

void set_response_cookies (Response* p, const Hash& hv) {
    p->cookies.clear();
    for (const auto& row : hv) p->cookies.add(string(row.key()), xs::in<Response::Cookie>(row.value()));
}

}}}

namespace xs {

using Response = panda::protocol::http::Response;
using CookieJar = panda::protocol::http::CookieJar;

Response::Cookie Typemap<Response::Cookie>::in (const Hash& h) {
    Response::Cookie c;
    Scalar v;
    if ((v = h.fetch("value")))     c.value(v.as_string());
    if ((v = h.fetch("domain")))    c.domain(v.as_string());
    if ((v = h.fetch("path")))      c.path(v.as_string());
    if ((v = h.fetch("expires")))   c.expires(xs::in<panda::date::Date>(v));
    if ((v = h.fetch("max_age")))   c.max_age(v.as_number<uint64_t>());
    if ((v = h.fetch("secure")))    c.secure(v.is_true());
    if ((v = h.fetch("http_only"))) c.http_only(v.is_true());
    if ((v = h.fetch("same_site"))) c.same_site((Cookie::SameSite)v.as_number<int>());
    return c;
}

Sv Typemap<Response::Cookie>::out (const Response::Cookie& c, const Sv& ) {
    Hash h = Hash::create();
    h["value"]     = xs::out(c.value());
    h["secure"]    = xs::out(c.secure());
    h["http_only"] = xs::out(c.http_only());
    h["same_site"] = xs::out((int)c.same_site());

    if (c.domain())  { h["domain"]  = xs::out(c.domain());  }
    if (c.path())    { h["path"]    = xs::out(c.path());    }
    if (c.expires()) { h["expires"] = xs::out(c.expires()); }
    if (c.max_age()) { h["max_age"] = xs::out(c.max_age()); }
    return Ref::create(h);
}

Sv Typemap<CookieJar::Cookie>::out (const CookieJar::Cookie& c, const Sv&) {
    Ref r = xs::out<Response::Cookie>(c);
    Hash h(r);
    h["name"]      = xs::out(c.name());
    h["host_only"] = c.host_only() ? Simple::yes : Simple::no;
    if (c.origin()) h["origin"] = xs::out(c.origin());
    return std::move(r);
}

Sv Typemap<CookieJar::Cookies>::out(const CookieJar::Cookies& cookies, const Sv&) {
    auto r = Array::create(cookies.size());
    for (auto& coo: cookies) r.push(xs::out(coo));
    return Ref::create(r);
}

Sv Typemap<CookieJar::DomainCookies>::out(const DomainCookies& domain_cookies, const Sv&) {
    Hash h = Hash::create();
    for(auto& pair : domain_cookies) {
        auto domain = pair.first.substr(1); // cut "." part
        h[domain] = xs::out(pair.second);
    }
    return Ref::create(h);
}

}