/* http_header.h
   Mathieu Stefani, 19 August 2015

  Declaration of common http headers
*/

#pragma once

#include <memory>
#include <ostream>
#include <string>
#include <type_traits>
#include <vector>

#include <pistache/http_defs.h>
#include <pistache/mime.h>
#include <pistache/net.h>

#define SAFE_HEADER_CAST

namespace Pistache {
namespace Http {
namespace Header {

#ifdef SAFE_HEADER_CAST
namespace detail {

// compile-time FNV-1a hashing algorithm
static constexpr uint64_t basis = 14695981039346656037ULL;
static constexpr uint64_t prime = 1099511628211ULL;

constexpr uint64_t hash_one(char c, const char *remain,
                            unsigned long long value) {
  return c == 0 ? value : hash_one(remain[0], remain + 1, (value ^ c) * prime);
}

constexpr uint64_t hash(const char *str) {
  return hash_one(str[0], str + 1, basis);
}

} // namespace detail
#endif

#ifdef SAFE_HEADER_CAST
#define NAME(header_name)                                                      \
  static constexpr uint64_t Hash =                                             \
      Pistache::Http::Header::detail::hash(header_name);                       \
  uint64_t hash() const override { return Hash; }                              \
  static constexpr const char *Name = header_name;                             \
  const char *name() const override { return Name; }
#else
#define NAME(header_name)                                                      \
  static constexpr const char *Name = header_name;                             \
  const char *name() const override { return Name; }
#endif

// 3.5 Content Codings
// 3.6 Transfer Codings
enum class Encoding { Gzip, Compress, Deflate, Identity, Chunked, Unknown };

const char *encodingString(Encoding encoding);

class Header {
public:
  virtual ~Header() {}
  virtual const char *name() const = 0;

  virtual void parse(const std::string &data);
  virtual void parseRaw(const char *str, size_t len);

  virtual void write(std::ostream &stream) const = 0;

#ifdef SAFE_HEADER_CAST
  virtual uint64_t hash() const = 0;
#endif
};

template <typename H> struct IsHeader {

  template <typename T> static std::true_type test(decltype(T::Name) *);

  template <typename T> static std::false_type test(...);

  static constexpr bool value =
      std::is_base_of<Header, H>::value &&
      std::is_same<decltype(test<H>(nullptr)), std::true_type>::value;
};

#ifdef SAFE_HEADER_CAST
template <typename To>
typename std::enable_if<IsHeader<To>::value, std::shared_ptr<To>>::type
header_cast(const std::shared_ptr<Header> &from) {
  return static_cast<To *>(0)->Hash == from->hash()
             ? std::static_pointer_cast<To>(from)
             : nullptr;
}

template <typename To>
typename std::enable_if<IsHeader<To>::value, std::shared_ptr<const To>>::type
header_cast(const std::shared_ptr<const Header> &from) {
  return static_cast<To *>(0)->Hash == from->hash()
             ? std::static_pointer_cast<const To>(from)
             : nullptr;
}
#endif

class Allow : public Header {
public:
  NAME("Allow");

  Allow() : methods_() {}

  explicit Allow(const std::vector<Http::Method> &methods)
      : methods_(methods) {}
  explicit Allow(std::initializer_list<Http::Method> methods)
      : methods_(methods) {}

  explicit Allow(Http::Method method) : methods_() {
    methods_.push_back(method);
  }

  void parseRaw(const char *str, size_t len) override;
  void write(std::ostream &os) const override;

  void addMethod(Http::Method method);
  void addMethods(std::initializer_list<Method> methods);
  void addMethods(const std::vector<Http::Method> &methods);

  std::vector<Http::Method> methods() const { return methods_; }

private:
  std::vector<Http::Method> methods_;
};

class Accept : public Header {
public:
  NAME("Accept")

  Accept() : mediaRange_() {}

  void parseRaw(const char *str, size_t len) override;
  void write(std::ostream &os) const override;

  const std::vector<Mime::MediaType> media() const { return mediaRange_; }

private:
  std::vector<Mime::MediaType> mediaRange_;
};

class AccessControlAllowOrigin : public Header {
public:
  NAME("Access-Control-Allow-Origin")

  AccessControlAllowOrigin() : uri_() {}

  explicit AccessControlAllowOrigin(const char *uri) : uri_(uri) {}
  explicit AccessControlAllowOrigin(const std::string &uri) : uri_(uri) {}

  void parse(const std::string &data) override;
  void write(std::ostream &os) const override;

  void setUri(std::string uri) { uri_ = std::move(uri); }

  std::string uri() const { return uri_; }

private:
  std::string uri_;
};

class AccessControlAllowHeaders : public Header {
public:
  NAME("Access-Control-Allow-Headers")

  AccessControlAllowHeaders() : val_() {}

  explicit AccessControlAllowHeaders(const char *val) : val_(val) {}
  explicit AccessControlAllowHeaders(const std::string &val) : val_(val) {}

  void parse(const std::string &data) override;
  void write(std::ostream &os) const override;

  void setUri(std::string val) { val_ = std::move(val); }

  std::string val() const { return val_; }

private:
  std::string val_;
};

class AccessControlExposeHeaders : public Header {
public:
  NAME("Access-Control-Expose-Headers")

  AccessControlExposeHeaders() : val_() {}

  explicit AccessControlExposeHeaders(const char *val) : val_(val) {}
  explicit AccessControlExposeHeaders(const std::string &val) : val_(val) {}

  void parse(const std::string &data) override;
  void write(std::ostream &os) const override;

  void setUri(std::string val) { val_ = std::move(val); }

  std::string val() const { return val_; }

private:
  std::string val_;
};

class AccessControlAllowMethods : public Header {
public:
  NAME("Access-Control-Allow-Methods")

  AccessControlAllowMethods() : val_() {}

  explicit AccessControlAllowMethods(const char *val) : val_(val) {}
  explicit AccessControlAllowMethods(const std::string &val) : val_(val) {}

  void parse(const std::string &data) override;
  void write(std::ostream &os) const override;

  void setUri(std::string val) { val_ = std::move(val); }

  std::string val() const { return val_; }

private:
  std::string val_;
};

class CacheControl : public Header {
public:
  NAME("Cache-Control")

  CacheControl() : directives_() {}

  explicit CacheControl(const std::vector<Http::CacheDirective> &directives)
      : directives_(directives) {}
  explicit CacheControl(Http::CacheDirective directive);
  explicit CacheControl(Http::CacheDirective::Directive directive)
      : CacheControl(Http::CacheDirective(directive)) {}

  void parseRaw(const char *str, size_t len) override;
  void write(std::ostream &os) const override;

  std::vector<Http::CacheDirective> directives() const { return directives_; }

  void addDirective(Http::CacheDirective directive);
  void addDirective(Http::CacheDirective::Directive directive) {
    addDirective(Http::CacheDirective(directive));
  }
  void addDirectives(const std::vector<Http::CacheDirective> &directives);

private:
  std::vector<Http::CacheDirective> directives_;
};

class Connection : public Header {
public:
  NAME("Connection")

  Connection() : control_(ConnectionControl::KeepAlive) {}

  explicit Connection(ConnectionControl control) : control_(control) {}

  void parseRaw(const char *str, size_t len) override;
  void write(std::ostream &os) const override;

  ConnectionControl control() const { return control_; }

private:
  ConnectionControl control_;
};

class EncodingHeader : public Header {
public:
  EncodingHeader() : encoding_() {}

  void parseRaw(const char *str, size_t len) override;
  void write(std::ostream &os) const override;

  Encoding encoding() const { return encoding_; }

protected:
  explicit EncodingHeader(Encoding encoding) : encoding_(encoding) {}

private:
  Encoding encoding_;
};

class ContentEncoding : public EncodingHeader {
public:
  NAME("Content-Encoding")

  ContentEncoding() : EncodingHeader(Encoding::Identity) {}

  explicit ContentEncoding(Encoding encoding) : EncodingHeader(encoding) {}
};

class TransferEncoding : public EncodingHeader {
public:
  NAME("Transfer-Encoding")

  TransferEncoding() : EncodingHeader(Encoding::Identity) {}

  explicit TransferEncoding(Encoding encoding) : EncodingHeader(encoding) {}
};

class ContentLength : public Header {
public:
  NAME("Content-Length");

  ContentLength() : value_(0) {}

  explicit ContentLength(uint64_t val) : value_(val) {}

  void parse(const std::string &data) override;
  void write(std::ostream &os) const override;

  uint64_t value() const { return value_; }

private:
  uint64_t value_;
};

class Authorization : public Header {
public:
  NAME("Authorization");

  enum class Method { Basic, Bearer, Unknown };

  Authorization() : value_("NONE") {}

  explicit Authorization(std::string &&val) : value_(std::move(val)) {}
  explicit Authorization(const std::string &val) : value_(val) {}

  // What type of authorization method was used?
  Method getMethod() const noexcept;

  // Check if a particular authorization method was used...
  template <Method M> bool hasMethod() const noexcept { return hasMethod<M>(); }

  // Get decoded user ID and password if basic method was used...
  std::string getBasicUser() const;
  std::string getBasicPassword() const;

  // Set encoded user ID and password for basic method...
  void setBasicUserPassword(const std::string &User,
                            const std::string &Password);

  void parse(const std::string &data) override;
  void write(std::ostream &os) const override;

  std::string value() const { return value_; }

private:
  std::string value_;
};

template <>
bool Authorization::hasMethod<Authorization::Method::Basic>() const noexcept;

template <>
bool Authorization::hasMethod<Authorization::Method::Bearer>() const noexcept;

class ContentType : public Header {
public:
  NAME("Content-Type")

  ContentType() : mime_() {}

  explicit ContentType(const Mime::MediaType &mime) : mime_(mime) {}
  explicit ContentType(std::string &&raw_mime_str)
      : ContentType(Mime::MediaType(std::move(raw_mime_str))) {}
  explicit ContentType(const std::string &raw_mime_str)
      : ContentType(Mime::MediaType(raw_mime_str)) {}

  void parseRaw(const char *str, size_t len) override;
  void write(std::ostream &os) const override;

  Mime::MediaType mime() const { return mime_; }
  void setMime(const Mime::MediaType &mime) { mime_ = mime; }

private:
  Mime::MediaType mime_;
};

class Date : public Header {
public:
  NAME("Date")

  Date() : fullDate_() {}

  explicit Date(const FullDate &date) : fullDate_(date) {}

  void parse(const std::string &data) override;
  void write(std::ostream &os) const override;

  FullDate fullDate() const { return fullDate_; }

private:
  FullDate fullDate_;
};

class Expect : public Header {
public:
  NAME("Expect")

  Expect() : expectation_() {}

  explicit Expect(Http::Expectation expectation) : expectation_(expectation) {}

  void parseRaw(const char *str, size_t len) override;
  void write(std::ostream &os) const override;

  Http::Expectation expectation() const { return expectation_; }

private:
  Expectation expectation_;
};

class Host : public Header {
public:
  NAME("Host");

  Host() : host_(), port_(0) {}

  explicit Host(const std::string &host);
  explicit Host(const std::string &host, Port port)
      : host_(host), port_(port) {}

  void parse(const std::string &data) override;
  void write(std::ostream &os) const override;

  std::string host() const { return host_; }
  Port port() const { return port_; }

private:
  std::string host_;
  Port port_;
};

class Location : public Header {
public:
  NAME("Location")

  Location() : location_() {}

  explicit Location(const std::string &location);

  void parse(const std::string &data) override;
  void write(std::ostream &os) const override;

  std::string location() const { return location_; }

private:
  std::string location_;
};

class Server : public Header {
public:
  NAME("Server")

  Server() : tokens_() {}

  explicit Server(const std::vector<std::string> &tokens);
  explicit Server(const std::string &token);
  explicit Server(const char *token);

  void parse(const std::string &data) override;
  void write(std::ostream &os) const override;

  std::vector<std::string> tokens() const { return tokens_; }

private:
  std::vector<std::string> tokens_;
};

class UserAgent : public Header {
public:
  NAME("User-Agent")

  UserAgent() : ua_() {}

  explicit UserAgent(const char *ua) : ua_(ua) {}

  explicit UserAgent(const std::string &ua) : ua_(ua) {}

  void parse(const std::string &data) override;
  void write(std::ostream &os) const override;

  void setAgent(std::string ua) { ua_ = std::move(ua); }

  std::string agent() const { return ua_; }

private:
  std::string ua_;
};

#define CUSTOM_HEADER(header_name)                                             \
  class header_name : public Pistache::Http::Header::Header {                  \
  public:                                                                      \
    NAME(#header_name)                                                         \
                                                                               \
    header_name() = default;                                                   \
                                                                               \
    explicit header_name(const char *value) : value_{value} {}                 \
                                                                               \
    explicit header_name(std::string value) : value_(std::move(value)) {}      \
                                                                               \
    void parseRaw(const char *str, size_t len) final { value_ = {str, len}; }  \
                                                                               \
    void write(std::ostream &os) const final { os << value_; };                \
                                                                               \
    std::string val() const { return value_; };                                \
                                                                               \
  private:                                                                     \
    std::string value_;                                                        \
  };

class Raw {
public:
  Raw();
  Raw(std::string name, std::string value)
      : name_(std::move(name)), value_(std::move(value)) {}

  Raw(const Raw &other) = default;
  Raw &operator=(const Raw &other) = default;

  Raw(Raw &&other) = default;
  Raw &operator=(Raw &&other) = default;

  std::string name() const { return name_; }
  std::string value() const { return value_; }

private:
  std::string name_;
  std::string value_;
};

} // namespace Header
} // namespace Http
} // namespace Pistache
