/* http_headers.h
   Mathieu Stefani, 19 August 2015

   A list of HTTP headers
*/

#pragma once

#include <algorithm>
#include <functional>
#include <memory>
#include <unordered_map>
#include <vector>

#include <pistache/http_header.h>
#include <pistache/type_checkers.h>

namespace Pistache {
namespace Http {
namespace Header {

std::string toLowercase(std::string str);

struct LowercaseHash {
  size_t operator()(const std::string &key) const {
    return std::hash<std::string>{}(toLowercase(key));
  }
};

bool LowercaseEqualStatic(const std::string &dynamic,
                          const std::string &statik);

struct LowercaseEqual {
  bool operator()(const std::string &left, const std::string &right) const {
    return std::equal(left.begin(), left.end(), right.begin(), right.end(),
                      [](const char &a, const char &b) {
                        return std::tolower(a) == std::tolower(b);
                      });
  };
};

class Collection {
public:
  Collection() : headers(), rawHeaders() {}

  template <typename H>
  typename std::enable_if<IsHeader<H>::value, std::shared_ptr<const H>>::type
  get() const {
    return std::static_pointer_cast<const H>(get(H::Name));
  }
  template <typename H>
  typename std::enable_if<IsHeader<H>::value, std::shared_ptr<H>>::type get() {
    return std::static_pointer_cast<H>(get(H::Name));
  }

  template <typename H>
  typename std::enable_if<IsHeader<H>::value, std::shared_ptr<const H>>::type
  tryGet() const {
    return std::static_pointer_cast<const H>(tryGet(H::Name));
  }
  template <typename H>
  typename std::enable_if<IsHeader<H>::value, std::shared_ptr<H>>::type
  tryGet() {
    return std::static_pointer_cast<H>(tryGet(H::Name));
  }

  Collection &add(const std::shared_ptr<Header> &header);
  Collection &addRaw(const Raw &raw);

  template <typename H, typename... Args>
  typename std::enable_if<IsHeader<H>::value, Collection &>::type
  add(Args &&... args) {
    return add(std::make_shared<H>(std::forward<Args>(args)...));
  }

  template <typename H>
  typename std::enable_if<IsHeader<H>::value, bool>::type remove() {
    return remove(H::Name);
  }

  std::shared_ptr<const Header> get(const std::string &name) const;
  std::shared_ptr<Header> get(const std::string &name);
  Raw getRaw(const std::string &name) const;

  std::shared_ptr<const Header> tryGet(const std::string &name) const;
  std::shared_ptr<Header> tryGet(const std::string &name);
  Optional<Raw> tryGetRaw(const std::string &name) const;

  template <typename H>
  typename std::enable_if<IsHeader<H>::value, bool>::type has() const {
    return has(H::Name);
  }
  bool has(const std::string &name) const;

  std::vector<std::shared_ptr<Header>> list() const;

  const std::unordered_map<std::string, Raw, LowercaseHash, LowercaseEqual> &
  rawList() const {
    return rawHeaders;
  }

  bool remove(const std::string &name);

  void clear();

private:
  std::pair<bool, std::shared_ptr<Header>>
  getImpl(const std::string &name) const;

  std::unordered_map<std::string, std::shared_ptr<Header>, LowercaseHash,
                     LowercaseEqual>
      headers;
  std::unordered_map<std::string, Raw, LowercaseHash, LowercaseEqual>
      rawHeaders;
};

class Registry {

public:
  Registry(const Registry &) = delete;
  Registry &operator=(const Registry &) = delete;
  static Registry &instance();

  template <typename H, REQUIRES(IsHeader<H>::value)> void registerHeader() {
    registerHeader(H::Name, []() -> std::unique_ptr<Header> {
      return std::unique_ptr<Header>(new H());
    });
  }

  std::vector<std::string> headersList();

  std::unique_ptr<Header> makeHeader(const std::string &name);
  bool isRegistered(const std::string &name);

private:
  Registry();
  ~Registry();

  using RegistryFunc = std::function<std::unique_ptr<Header>()>;
  using RegistryStorageType = std::unordered_map<std::string, RegistryFunc,
                                                 LowercaseHash, LowercaseEqual>;

  void registerHeader(const std::string &name, RegistryFunc func);

  RegistryStorageType registry;
};

template <typename H> struct Registrar {
  static_assert(IsHeader<H>::value, "Registrar only works with header types");

  Registrar() { Registry::instance().registerHeader<H>(); }
};

/* Crazy macro machinery to generate a unique variable name
 * Don't touch it !
 */
#define CAT(a, b) CAT_I(a, b)
#define CAT_I(a, b) a##b

#define UNIQUE_NAME(base) CAT(base, __LINE__)

#define RegisterHeader(Header)                                                 \
  Registrar<Header> UNIQUE_NAME(CAT(CAT_I(__, Header), __))

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