/* http_defs.h
   Mathieu Stefani, 01 September 2015

   Various http definitions
*/

#pragma once

#include <string>
#include <ostream>
#include <stdexcept>
#include <chrono>
#include <functional>

namespace Pistache {
namespace Http {

#define HTTP_METHODS \
    METHOD(Options, "OPTIONS") \
    METHOD(Get, "GET") \
    METHOD(Post, "POST") \
    METHOD(Head, "HEAD") \
    METHOD(Put, "PUT") \
    METHOD(Patch, "PATCH") \
    METHOD(Delete, "DELETE") \
    METHOD(Trace, "TRACE") \
    METHOD(Connect, "CONNECT")

// 10. Status Code Definitions
#define STATUS_CODES \
    CODE(100, Continue, "Continue") \
    CODE(101, Switching_Protocols, "Switching Protocols") \
    CODE(102, Processing, "Processing") \
    CODE(103, Early_Hints, "Early Hints") \
    CODE(200, Ok, "OK") \
    CODE(201, Created, "Created") \
    CODE(202, Accepted, "Accepted") \
    CODE(203, NonAuthoritative_Information, "Non-Authoritative Information") \
    CODE(204, No_Content, "No Content") \
    CODE(205, Reset_Content, "Reset Content") \
    CODE(206, Partial_Content, "Partial Content") \
    CODE(207, MultiStatus, "Multi-Status") \
    CODE(208, Already_Reported, "Already Reported") \
    CODE(226, IM_Used, "IM Used") \
    CODE(300, Multiple_Choices, "Multiple Choices") \
    CODE(301, Moved_Permanently, "Moved Permanently") \
    CODE(302, Found, "Found") \
    CODE(303, See_Other, "See Other") \
    CODE(304, Not_Modified, "Not Modified") \
    CODE(305, Use_Proxy, "Use Proxy") \
    CODE(307, Temporary_Redirect, "Temporary Redirect") \
    CODE(308, Permanent_Redirect, "Permanent Redirect") \
    CODE(400, Bad_Request, "Bad Request") \
    CODE(401, Unauthorized, "Unauthorized") \
    CODE(402, Payment_Required, "Payment Required") \
    CODE(403, Forbidden, "Forbidden") \
    CODE(404, Not_Found, "Not Found") \
    CODE(405, Method_Not_Allowed, "Method Not Allowed") \
    CODE(406, Not_Acceptable, "Not Acceptable") \
    CODE(407, Proxy_Authentication_Required, "Proxy Authentication Required") \
    CODE(408, Request_Timeout, "Request Timeout") \
    CODE(409, Conflict, "Conflict") \
    CODE(410, Gone, "Gone") \
    CODE(411, Length_Required, "Length Required") \
    CODE(412, Precondition_Failed, "Precondition Failed") \
    CODE(413, Request_Entity_Too_Large, "Request Entity Too Large") \
    CODE(414, RequestURI_Too_Long, "Request-URI Too Long") \
    CODE(415, Unsupported_Media_Type, "Unsupported Media Type") \
    CODE(416, Requested_Range_Not_Satisfiable, "Requested Range Not Satisfiable") \
    CODE(417, Expectation_Failed, "Expectation Failed") \
    CODE(418, I_m_a_teapot, "I'm a teapot") \
    CODE(421, Misdirected_Request, "Misdirected Request") \
    CODE(422, Unprocessable_Entity, "Unprocessable Entity") \
    CODE(423, Locked, "Locked") \
    CODE(424, Failed_Dependency, "Failed Dependency") \
    CODE(426, Upgrade_Required, "Upgrade Required") \
    CODE(428, Precondition_Required, "Precondition Required") \
    CODE(429, Too_Many_Requests, "Too Many Requests")\
    CODE(431, Request_Header_Fields_Too_Large, "Request Header Fields Too Large") \
    CODE(444, Connection_Closed_Without_Response, "Connection Closed Without Response") \
    CODE(451, Unavailable_For_Legal_Reasons, "Unavailable For Legal Reasons") \
    CODE(499, Client_Closed_Request, "Client Closed Request") \
    CODE(500, Internal_Server_Error, "Internal Server Error") \
    CODE(501, Not_Implemented, "Not Implemented") \
    CODE(502, Bad_Gateway, "Bad Gateway") \
    CODE(503, Service_Unavailable, "Service Unavailable") \
    CODE(504, Gateway_Timeout, "Gateway Timeout") \
    CODE(505, HTTP_Version_Not_Supported, "HTTP Version Not Supported")\
    CODE(506, Variant_Also_Negotiates, "Variant Also Negotiates") \
    CODE(507, Insufficient_Storage, "Insufficient Storage") \
    CODE(508, Loop_Detected, "Loop Detected") \
    CODE(510, Not_Extended, "Not Extended") \
    CODE(511, Network_Authentication_Required, "Network Authentication Required")\
    CODE(599, Network_Connect_Timeout_Error, "Network Connect Timeout Error")

// 3.4. Character Sets
// See http://tools.ietf.org/html/rfc2978 and
// http://www.iana.org/assignments/character-sets/character-sets.xhtml
#define CHARSETS \
    CHARSET(UsAscii, "us-ascii") \
    CHARSET(Iso-8859-1, "iso-8859-1") \
    CHARSET(Iso-8859-2, "iso-8859-2") \
    CHARSET(Iso-8859-3, "iso-8859-3") \
    CHARSET(Iso-8859-4, "iso-8859-4") \
    CHARSET(Iso-8859-5, "iso-8859-5") \
    CHARSET(Iso-8859-6, "iso-8859-6") \
    CHARSET(Iso-8859-7, "iso-8859-7") \
    CHARSET(Iso-8859-8, "iso-8859-8") \
    CHARSET(Iso-8859-9, "iso-8859-9") \
    CHARSET(Iso-8859-10, "iso-8859-10") \
    CHARSET(Shift-JIS, "shift_jis") \
    CHARSET(Utf7, "utf-7") \
    CHARSET(Utf8, "utf-8") \
    CHARSET(Utf16, "utf-16") \
    CHARSET(Utf16-BE, "utf-16be") \
    CHARSET(Utf16-LE, "utf-16le") \
    CHARSET(Utf32, "utf-32") \
    CHARSET(Utf32-BE, "utf-32be") \
    CHARSET(Utf32-LE, "utf-32le") \
    CHARSET(Unicode-11, "unicode-1-1")


enum class Method {
#define METHOD(m, _) m,
    HTTP_METHODS
#undef METHOD
};

enum class Code {
#define CODE(value, name, _) name = value,
    STATUS_CODES
#undef CODE
};

enum class Version {
    Http10, // HTTP/1.0
    Http11 // HTTP/1.1
};

enum class ConnectionControl {
    Close,
    KeepAlive,
    Ext
};

enum class Expectation {
    Continue,
    Ext
};

class CacheDirective {
public:
    enum Directive { NoCache, NoStore, MaxAge, MaxStale, MinFresh,
                     NoTransform, OnlyIfCached,
                     Public, Private, MustRevalidate, ProxyRevalidate, SMaxAge,
                     Ext };

    CacheDirective()
        : directive_()
        , data()
    { }

    CacheDirective(Directive directive);
    CacheDirective(Directive directive, std::chrono::seconds delta);

    Directive directive() const { return directive_; }
    std::chrono::seconds delta() const;

private:
    void init(Directive directive, std::chrono::seconds delta);
    Directive directive_;
    // Poor way of representing tagged unions in C++
    union {
        uint64_t maxAge;
        uint64_t sMaxAge;
        uint64_t maxStale;
        uint64_t minFresh;
    } data;
};

// 3.3.1 Full Date
class FullDate {
public:
    using time_point = std::chrono::system_clock::time_point;
    FullDate()
       : date_()
    { }

    enum class Type {
        RFC1123,
        RFC850,
        AscTime
    };

    FullDate(time_point date)
        : date_(date)
    { }

    time_point date() const { return date_; }
    void write(std::ostream& os, Type type = Type::RFC1123) const;

    static FullDate fromString(const std::string& str);

private:
    time_point date_;
};

const char* methodString(Method method);
const char* versionString(Version version);
const char* codeString(Code code);

std::ostream& operator<<(std::ostream& os, Version version);
std::ostream& operator<<(std::ostream& os, Method method);
std::ostream& operator<<(std::ostream& os, Code code);

struct HttpError : public std::exception {
    HttpError(Code code, std::string reason);
    HttpError(int code, std::string reason);

    ~HttpError() noexcept { }

    const char* what() const noexcept { return reason_.c_str(); }

    int code() const { return code_; }
    std::string reason() const { return reason_; }

private:
    int code_;
    std::string reason_;
};

} // namespace Http
} // namespace Pistache

namespace std {

template<>
struct hash<Pistache::Http::Method> {
    size_t operator()(Pistache::Http::Method method) const {
        return std::hash<int>()(static_cast<int>(method));
    }
};

} // namespace std
