/*/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

***** HeadSocket v0.1, created by Jan Pinter **** Minimalistic header only WebSocket server implementation in C++ *****
                    Sources: https://github.com/P-i-N/HeadSocket, contact: Pinter.Jan@gmail.com
                     PUBLIC DOMAIN - no warranty implied or offered, use this at your own risk

-----------------------------------------------------------------------------------------------------------------------

Usage:
- use this as a regular header file, but in EXACTLY one of your C++ files (ie. main.cpp) you must define
  HEADSOCKET_IMPLEMENTATION beforehand, like this:

    #define HEADSOCKET_IMPLEMENTATION
    #include <headsocket.h>

/*/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifndef __HEADSOCKET_H__
#define __HEADSOCKET_H__

#include <memory>
#include <string>
#include <map>

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace headsocket {

/* Forward declarations */
class connection;
class basic_tcp_server;
class basic_tcp_client;
class tcp_client;
class async_tcp_client;

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

template <typename T> using ptr = std::shared_ptr<T>;
typedef size_t id_t;

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace detail {
  
/* Forward declarations */
struct connection_impl;
struct basic_tcp_server_impl;
struct basic_tcp_client_impl;
struct async_tcp_client_impl;

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static bool handshake_websocket(connection &conn);

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

struct less_comparator
{
  bool operator()(const std::string &s1, const std::string &s2) const
  {
    return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), [](char c1, char c2) -> bool
    {
      return tolower(c1) < tolower(c2);
    });
  }
};

}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class connection
{
public:
  connection(const detail::connection_impl &impl);
  ~connection();

  detail::connection_impl *impl() const { return _p.get(); }

  bool is_valid() const;
  id_t id() const;

  size_t write(const void *ptr, size_t length);
  size_t write(const std::string &text) { return write(text.c_str(), text.length()); }
  size_t read(void *ptr, size_t length);

  bool force_write(const void *ptr, size_t length);
  bool force_read(void *ptr, size_t length);

  bool read_line(std::string &output);

private:
  std::unique_ptr<detail::connection_impl> _p;
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

enum class opcode
{
  continuation = 0x00,
  text = 0x01,
  binary = 0x02,
  connection_close = 0x08,
  ping = 0x09,
  pong = 0x0A
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

struct data_block
{
  opcode op;
  size_t offset;
  size_t length = 0;
  bool is_completed = false;

  data_block(opcode opc, size_t off)
    : op(opc)
    , offset(off)
  {

  }
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class basic_tcp_server : public std::enable_shared_from_this<basic_tcp_server>
{
public:
  int port() const;
  void stop();
  bool is_running() const;
  bool disconnect(ptr<basic_tcp_client> client);
  bool disconnect(id_t id);

protected:
  struct protected_tag { };

    virtual void init() { }

  explicit basic_tcp_server(int port);
  virtual ~basic_tcp_server();

  virtual bool handshake(connection &conn) = 0;
  virtual ptr<basic_tcp_client> accept(connection &conn) = 0;
  virtual void client_connected(ptr<basic_tcp_client> client) = 0;
  virtual void client_disconnected(ptr<basic_tcp_client> client) = 0;

  std::unique_ptr<detail::basic_tcp_server_impl> _p;

private:
  template <typename T> friend class tcp_server;

  void remove_disconnected() const;

  size_t acquire_clients() const;
  void release_clients() const;

  ptr<basic_tcp_client> client_at(size_t index) const;
  size_t num_clients() const;

  void accept_thread();
  void disconnect_thread();
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    class basic_tcp_client
    {
    public:
        enum { is_basic_tcp_client };

        static const size_t invalid_operation = static_cast<size_t>(-1);

        virtual ~basic_tcp_client();

        bool disconnect();
        bool is_connected() const;

        ptr<basic_tcp_server> server() const;
        id_t id() const;

    protected:
        struct protected_tag { };

        friend class basic_tcp_server;

        virtual void on_accept() { }
        virtual void on_disconnect() { }

        basic_tcp_client(const std::string &address, int port);
        basic_tcp_client(ptr<basic_tcp_server> server, connection &conn);

        std::unique_ptr<detail::basic_tcp_client_impl> _p;
    };

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct protected_tag { };

#define HEADSOCKET_SERVER(className, baseClassName) \
  protected: \
    explicit className(int port): baseClassName(port) { init(); } \
  public: \
    typedef baseClassName base_t; \
    className(const protected_tag &, int port): className(port) { } \
    static headsocket::ptr<className> create(int port) { return std::make_shared<className>(protected_tag{}, port); } \
  protected: \
    void init()

template <typename T>
class tcp_server : public basic_tcp_server
{
  HEADSOCKET_SERVER(tcp_server, basic_tcp_server) { }

public:
  typedef T client_t;
  typedef ptr<client_t> client_ptr;

  virtual ~tcp_server()
  {
    base_t::stop();
  }

  class enumerator
  {
  public:
    explicit enumerator(const tcp_server &server)
      : _server(server)
      , _count(server.acquire_clients())
    {

    }

    ~enumerator()
    {
      _server.release_clients();
    }

    const tcp_server &server() const { return _server; }
    size_t size() const { return _count; }

    struct iterator
    {
      enumerator &e;
      size_t index;

      iterator(enumerator &enu, size_t idx)
        : e(enu)
        , index(idx)
      {

      }

      bool operator==(const iterator &iter) const { return iter.index == index && &iter.e == &e; }
      bool operator!=(const iterator &iter) const { return iter.index != index || &iter.e != &e; }
      ptr<T> operator*() const { return std::dynamic_pointer_cast<T>(e.server().client_at(index)); }

      iterator &operator++()
      {
        ++index;
        return *this;
      }
    };

    iterator begin() { return iterator(*this, 0); }
    iterator end() { return iterator(*this, _count); }

  private:
    const tcp_server &_server;
    size_t _count;
  };

  enumerator clients() const { return enumerator(*this); }

protected:
  bool handshake(connection &conn) override { return true; }

  virtual void client_connected(client_ptr client) { }

  virtual void client_disconnected(client_ptr client) { }

private:
  enum { needs_basic_tcp_client = T::is_basic_tcp_client };

  ptr<basic_tcp_client> accept(connection &conn) override
  {
    ptr<basic_tcp_client> newClient = T::create(shared_from_this(), conn);
    return newClient->is_connected() ? newClient : nullptr;
  }

  void client_connected(ptr<basic_tcp_client> client) override
  {
    client_connected(std::dynamic_pointer_cast<T>(client));
  }

  void client_disconnected(ptr<basic_tcp_client> client) override
  {
    client_disconnected(std::dynamic_pointer_cast<T>(client));
  }
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#define __HEADSOCKET_CLIENT_STATIC_CTORS(className) \
  className(const protected_tag &, const std::string &address, int port): className(address, port) { } \
  className(const protected_tag &, headsocket::ptr<headsocket::basic_tcp_server> server, headsocket::connection &conn): className(server, conn) { } \
  static headsocket::ptr<className> create(const std::string &address, int port) { return std::make_shared<className>(protected_tag{}, address, port); } \
  static headsocket::ptr<className> create(headsocket::ptr<headsocket::basic_tcp_server> server, headsocket::connection &conn) { return std::make_shared<className>(protected_tag{}, server, conn); }

#define HEADSOCKET_CLIENT_BASE(className) \
  protected: \
    className(const std::string &address, int port); \
    className(ptr<basic_tcp_server> server, connection &conn); \
  public: \
    __HEADSOCKET_CLIENT_STATIC_CTORS(className)

#define HEADSOCKET_CLIENT(className, baseClassName) \
  protected: \
    className(const std::string &address, int port): baseClassName(address, port) { } \
    className(headsocket::ptr<headsocket::basic_tcp_server> server, headsocket::connection &conn): baseClassName(server, conn) { } \
  public: \
    __HEADSOCKET_CLIENT_STATIC_CTORS(className)

class tcp_client : public basic_tcp_client
{
  HEADSOCKET_CLIENT_BASE(tcp_client)

public:
  typedef basic_tcp_client base_t;
  enum { is_tcp_client };

  virtual ~tcp_client();

  virtual size_t write(const void *ptr, size_t length);
  virtual size_t read(void *ptr, size_t length);

  bool force_write(const void *ptr, size_t length);
  bool force_read(void *ptr, size_t length);

  bool read_line(std::string &output);
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class async_tcp_client : public basic_tcp_client
{
  HEADSOCKET_CLIENT_BASE(async_tcp_client)

public:
  typedef basic_tcp_client base_t;
  enum { is_async_tcp_client };
  
  virtual ~async_tcp_client();

  void push(const void *ptr, size_t length);
  void push(const std::string &text);
  size_t peek() const;
  size_t pop(void *ptr, size_t length);

protected:
  void on_accept() override { init_threads(); }
  void on_disconnect() override { kill_threads(); }

  virtual void init_threads();

  virtual size_t async_write_handler(uint8_t *ptr, size_t length);
  virtual size_t async_read_handler(uint8_t *ptr, size_t length);

  virtual bool async_received_data(const data_block &db, uint8_t *ptr, size_t length) { return false; }

  virtual void push(const void *ptr, size_t length, opcode opcode);

  void kill_threads();

  std::unique_ptr<detail::async_tcp_client_impl> _ap;

private:
  void write_thread();
  void read_thread();
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class web_socket_client : public async_tcp_client
{
  HEADSOCKET_CLIENT_BASE(web_socket_client)
  
public:
  static const size_t frame_size_limit = 128 * 1024;

  typedef async_tcp_client base_t;
  enum { is_web_socket_client };

  virtual ~web_socket_client();

  size_t peek(opcode *op) const;

protected:
  size_t async_write_handler(uint8_t *ptr, size_t length) override;
  size_t async_read_handler(uint8_t *ptr, size_t length) override;

private:
  struct frame_header
  {
    bool fin;
    opcode op;
    bool masked;
    size_t payload_length;
    uint32_t masking_key;

    size_t write(uint8_t *ptr, size_t length) const;
    size_t read(const uint8_t *ptr, size_t length);
  };

  size_t _payload_size = 0;
  frame_header _current_header;
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

template <typename T>
class web_socket_server : public tcp_server<T>
{
  HEADSOCKET_SERVER(web_socket_server, tcp_server<T>) { }

public:
  virtual ~web_socket_server()
  {
    base_t::stop();
  }

protected:
  bool handshake(connection &conn) override { return detail::handshake_websocket(conn); }

private:
  enum { needs_web_socket_client = T::is_web_socket_client };
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class http_server : public tcp_server<tcp_client>
{
  HEADSOCKET_SERVER(http_server, tcp_server<tcp_client>) { }

public:
  ~http_server()
  {
    stop();
  }

  struct response
  {
    std::string content_type = "text/html";
    std::string message = "";
  };

  struct parameter
  {
    std::string name;
    std::string value;
    bool boolean;
    int integer;
    double real;
  };

  typedef std::map<std::string, parameter, detail::less_comparator> parameters_t;

protected:
  virtual bool request(const std::string &path, const parameters_t &params, response &resp) { return false; }

private:
  bool handshake(connection &conn) final override;

  ptr<basic_tcp_client> accept(connection &conn) final override { return nullptr; }
  void client_connected(client_ptr client) final override { }
  void client_disconnected(client_ptr client) final override { }
};

}

#endif // __HEADSOCKET_H__

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#ifdef HEADSOCKET_IMPLEMENTATION
#ifndef __HEADSOCKET_H_IMPL__
#define __HEADSOCKET_H_IMPL__

#include <thread>
#include <atomic>
#include <iomanip>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <memory>
#include <sstream>

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#ifndef HEADSOCKET_PLATFORM_OVERRIDE
#ifdef _WIN32
#define HEADSOCKET_PLATFORM_WINDOWS
#elif __ANDROID__
#define HEADSOCKET_PLATFORM_ANDROID
#define HEADSOCKET_PLATFORM_NIX
#elif __APPLE__
#include "TargetConditionals.h"
#ifdef TARGET_OS_MAC
#define HEADSOCKET_PLATFORM_MAC
#endif
#elif __linux
#define HEADSOCKET_PLATFORM_NIX
#elif __unix
#define HEADSOCKET_PLATFORM_NIX
#elif __posix
#define HEADSOCKET_PLATFORM_NIX
#else
#error Unsupported platform!
#endif
#endif

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#if defined(HEADSOCKET_PLATFORM_WINDOWS)
#pragma comment(lib, "ws2_32.lib")
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <functional>

#elif defined(HEADSOCKET_PLATFORM_ANDROID) || defined(HEADSOCKET_PLATFORM_NIX)
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <netdb.h>
#endif

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#define HEADSOCKET_LOCK_SUFFIX(var, suffix) std::lock_guard<decltype(var)> __scope_lock##suffix(var);
#define HEADSOCKET_LOCK_SUFFIX2(var, suffix) HEADSOCKET_LOCK_SUFFIX(var, suffix)
#define HEADSOCKET_LOCK(var) HEADSOCKET_LOCK_SUFFIX2(var, __LINE__)

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace headsocket {

namespace detail {
#if defined(HEADSOCKET_PLATFORM_WINDOWS)
typedef SOCKET socket_type;
static const int socket_error = SOCKET_ERROR;
static const SOCKET invalid_socket = INVALID_SOCKET;
void close_socket(socket_type s) { closesocket(s); }
#define HEADSOCKET_SPRINTF sprintf_s
#elif defined(HEADSOCKET_PLATFORM_ANDROID) || defined(HEADSOCKET_PLATFORM_NIX)
typedef int socket_type;
static const int socket_error = -1;
static const int invalid_socket = -1;
void close_socket(socket_type s) { close(s); }
#define HEADSOCKET_SPRINTF sprintf
#endif
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace detail {

class sha1
{
public:
  typedef uint32_t digest32_t[5];
  typedef uint8_t digest8_t[20];

  inline static uint32_t rotate_left(uint32_t value, size_t count) { return (value << count) ^ (value >> (32 - count)); }

  sha1()
  {
    _digest[0] = 0x67452301;
    _digest[1] = 0xEFCDAB89;
    _digest[2] = 0x98BADCFE;
    _digest[3] = 0x10325476;
    _digest[4] = 0xC3D2E1F0;
  }

  ~sha1()
  {

  }

  void process_byte(uint8_t octet)
  {
    _block[_block_byte_index++] = octet;
    ++_byte_count;

    if (_block_byte_index == 64)
    {
      _block_byte_index = 0;
      process_block();
    }
  }

  void process_block(const void *start, const void *end)
  {
    const uint8_t *begin = static_cast<const uint8_t *>(start);

    while (begin != end)
      process_byte(*begin++);
  }

  void process_bytes(const void *data, size_t len)
  {
    process_block(data, static_cast<const uint8_t *>(data) + len);
  }

  const uint32_t *get_digest(digest32_t digest)
  {
    size_t bitCount = _byte_count * 8;
    process_byte(0x80);

    if (_block_byte_index > 56)
    {
      while (_block_byte_index != 0)
        process_byte(0);

      while (_block_byte_index < 56)
        process_byte(0);
    }
    else
      while (_block_byte_index < 56)
        process_byte(0);

    process_byte(0);
    process_byte(0);
    process_byte(0);
    process_byte(0);

    for (int i = 24; i >= 0; i -= 8)
      process_byte(static_cast<unsigned char>((bitCount >> i) & 0xFF));

    memcpy(digest, _digest, 5 * sizeof(uint32_t));
    return digest;
  }

  const uint8_t *get_digest_bytes(digest8_t digest)
  {
    digest32_t d32;
    get_digest(d32);
    size_t s[] = { 24, 16, 8, 0 };

    for (size_t i = 0, j = 0; i < 20; ++i, j = i % 4)
      digest[i] = ((d32[i >> 2] >> s[j]) & 0xFF);

    return digest;
  }

private:
  void process_block()
  {
    uint32_t w[80], s[] = { 24, 16, 8, 0 };

    for (size_t i = 0, j = 0; i < 64; ++i, j = i % 4)
      w[i / 4] = j ? (w[i / 4] | (_block[i] << s[j])) : (_block[i] << s[j]);

    for (size_t i = 16; i < 80; i++)
      w[i] = rotate_left((w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]), 1);

    digest32_t dig = { _digest[0], _digest[1], _digest[2], _digest[3], _digest[4] };

    for (size_t f, k, i = 0; i < 80; ++i)
    {
      if (i < 20)
        f = (dig[1] & dig[2]) | (~dig[1] & dig[3]), k = 0x5A827999;
      else if (i < 40)
        f = dig[1] ^ dig[2] ^ dig[3], k = 0x6ED9EBA1;
      else if (i < 60)
        f = (dig[1] & dig[2]) | (dig[1] & dig[3]) | (dig[2] & dig[3]), k = 0x8F1BBCDC;
      else
        f = dig[1] ^ dig[2] ^ dig[3], k = 0xCA62C1D6;

      uint32_t temp = static_cast<uint32_t>(rotate_left(dig[0], 5) + f + dig[4] + k + w[i]);
      dig[4] = dig[3];
      dig[3] = dig[2];
      dig[2] = rotate_left(dig[1], 30);
      dig[1] = dig[0];
      dig[0] = temp;
    }

    for (size_t i = 0; i < 5; ++i)
      _digest[i] += dig[i];
  }

  digest32_t _digest;
  uint8_t _block[64];
  size_t _block_byte_index = 0;
  size_t _byte_count = 0;
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

struct utils
{
  static std::string base64_encode(const void *ptr, size_t length)
  {
    static const char *encoding_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    static size_t mod_table[] = { 0, 2, 1 };

    std::string result(4 * ((length + 2) / 3), '=');

    if (ptr && length)
    {
      const uint8_t *input = reinterpret_cast<const uint8_t *>(ptr);

      for (size_t i = 0, j = 0, triplet = 0; i < length; triplet = 0)
      {
        for (size_t k = 0; k < 3; ++k)
          triplet = (triplet << 8) | (i < length ? static_cast<uint8_t>(input[i++]) : 0);

        for (size_t k = 4; k--;)
          result[j++] = encoding_table[(triplet >> k * 6) & 0x3F];
      }

      for (size_t i = 0; i < mod_table[length % 3]; i++)
        result[result.length() - 1 - i] = '=';
    }

    return result;
  }

  static size_t xor32(uint32_t key, void *ptr, size_t length)
  {
    uint8_t *data = reinterpret_cast<uint8_t *>(ptr);
    uint8_t *mask = reinterpret_cast<uint8_t *>(&key);

    for (size_t i = 0; i < length; ++i, ++data)
      *data = (*data) ^ mask[i % 4];

    return length;
  }

  static std::string url_encode(const std::string &str)
  {
    std::ostringstream result;
    result.fill('0');
    result << std::hex;

    for (std::string::const_iterator i = str.begin(), n = str.end(); i != n; ++i)
    {
      auto c = (*i);

      if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
        result << c;
      else
        result << '%' << std::setw(2) << static_cast<int>(c);
    }

    return result.str();
  }

  static std::string url_decode(const std::string &str)
  {
    std::ostringstream result;

    for (size_t i = 0, S = str.length(); i < S; ++i)
    {
      auto c = str[i];

      if (c == '%')
      {
        char hexBuff[3] = { 0, 0, 0 };
        hexBuff[0] = str[++i];
        hexBuff[1] = str[++i];

        int value;
        sscanf(hexBuff, "%x", &value);
        result << static_cast<char>(value);
      }
      else if (c == '+')
        result << ' ';
      else
        result << c;
    }

    return result.str();
  }

  static uint16_t swap16bits(uint16_t x) { return ((x & 0x00FF) << 8) | ((x & 0xFF00) >> 8); }

  static uint32_t swap32bits(uint32_t x)
  {
    return ((x & 0x000000FF) << 24) | ((x & 0x0000FF00) << 8) | ((x & 0x00FF0000) >> 8) | ((x & 0xFF000000) >> 24);
  }

  static uint64_t swap64bits(uint64_t x)
  {
    return
      ((x & 0x00000000000000FFULL) << 56) | ((x & 0x000000000000FF00ULL) << 40) | ((x & 0x0000000000FF0000ULL) << 24) |
      ((x & 0x00000000FF000000ULL) << 8) | ((x & 0x000000FF00000000ULL) >> 8) | ((x & 0x0000FF0000000000ULL) >> 24) |
      ((x & 0x00FF000000000000ULL) >> 40) | ((x & 0xFF00000000000000ULL) >> 56);
  }

  static std::string trim(const std::string &str)
  {
    size_t trimLeft = 0, trimRight = str.length() - 1;

    while (trimLeft < str.length() && isspace(str[trimLeft]))
      ++trimLeft;

    while (trimRight < str.length() && isspace(str[trimRight]))
      --trimRight;

    return (trimRight >= str.length() || trimLeft >= str.length() || trimRight < trimLeft)
      ? std::string("")
      : str.substr(trimLeft, trimRight - trimLeft + 1);
  }

  static std::string cut_front(std::string &str, char delimiter = ' ', bool first = true, bool hungry = true)
  {
    std::string result;

    auto pos = first ? str.find(delimiter) : str.rfind(delimiter);
    if (pos == std::string::npos)
    {
      if (hungry)
      {
        result = str;
        str = "";
      }
    }
    else
    {
      result = str.substr(0, pos);
      str = str.substr(pos + 1);
    }

    return result;
  }

  static std::string cut_back(std::string &str, char delimiter = ' ', bool first = true, bool hungry = true)
  {
    std::string result;

    auto pos = first ? str.rfind(delimiter) : str.find(delimiter);
    if (pos == std::string::npos)
    {
      if (hungry)
      {
        result = str;
        str = "";
      }
    }
    else
    {
      result = str.substr(pos + 1);
      str = str.substr(0, pos);
    }

    return result;
  }
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

struct critical_section
{
  mutable std::atomic_bool consumer_lock;

  critical_section()
  {
    consumer_lock = false;
  }

  void lock() const { while (consumer_lock.exchange(true)); }
  void unlock() const { consumer_lock = false; }
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

template <typename T, typename M = critical_section>
struct lockable_value : M
{
  T value;

  T *operator->() { return &value; }
  const T *operator->() const { return &value; }
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

struct semaphore
{
  mutable std::atomic_size_t count;
  mutable std::mutex mutex;
  mutable std::condition_variable cv;

  semaphore()
  {
    count = 0;
  }

  void lock(size_t minCount = 0) const
  {
    std::unique_lock<std::mutex> lock(mutex);
    cv.wait(lock, [&]()->bool { return count > minCount; });
    lock.release();
  }

  void unlock()
  {
    mutex.unlock();
  }

  void notify()
  {
    {
      std::lock_guard<std::mutex> lock(mutex);
      ++count;
    }

    cv.notify_one();
  }

  void consume() const
  {
    if (count)
      --count;
  }
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

struct data_block_buffer
{
  std::vector<data_block> blocks;
  std::vector<uint8_t> buffer;

  data_block_buffer()
  {
    buffer.reserve(65536);
  }

  data_block &block_begin(opcode op)
  {
    blocks.emplace_back(op, buffer.size());
    return blocks.back();
  }

  data_block &block_end()
  {
    blocks.back().is_completed = true;
    return blocks.back();
  }

  void block_remove()
  {
    if (blocks.empty())
      return;

    buffer.resize(blocks.back().offset);
    blocks.pop_back();
  }

  void write(const void *ptr, size_t length)
  {
    if (!length)
      return;

    buffer.resize(buffer.size() + length);
    memcpy(buffer.data() + buffer.size() - length, reinterpret_cast<const char *>(ptr), length);
    blocks.back().length += length;
  }

  size_t read(void *ptr, size_t length)
  {
    if (!ptr || blocks.empty() || !blocks.front().is_completed)
      return 0;

    data_block &db = blocks.front();
    size_t result = db.length >= length ? length : db.length;

    if (result)
    {
      memcpy(ptr, buffer.data() + db.offset, result);
      buffer.erase(buffer.begin(), buffer.begin() + result);
    }

    if (!(db.length -= result))
      blocks.erase(blocks.begin());
    else
      blocks.front().op = opcode::continuation;

    if (result) for (auto &block : blocks) if (block.offset > db.offset)
          block.offset -= result;

    return result;
  }

  size_t peek(opcode *op = nullptr) const
  {
    if (blocks.empty() || !blocks.front().is_completed)
      return 0;

    if (op)
      *op = blocks.front().op;

    return blocks.front().length;
  }
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//---------------------------------------------------------------------------------------------------------------------
bool handshake_websocket(connection &conn)
{
  std::string line, key;

  while (conn.read_line(line))
  {
    if (line.empty())
      break;

    if (!memcmp(line.c_str(), "Sec-WebSocket-Key: ", 19))
      key = line.substr(19);
  }

  if (key.empty())
    return false;

  key += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

  detail::sha1 sha;
  detail::sha1::digest8_t digest;
  sha.process_bytes(key.c_str(), key.length());

  std::string response = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ";
  response += detail::utils::base64_encode(sha.get_digest_bytes(digest), 20);
  response += "\r\n\r\n";

  return conn.force_write(response.c_str(), response.length());
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#ifdef HEADSOCKET_PLATFORM_WINDOWS
void set_thread_name(const char *name)
{
}
#else
void set_thread_name(const char *name)
{

}
#endif

} // namespace detail;

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace detail {
  
struct connection_impl
{
  detail::socket_type socket = detail::invalid_socket;
  sockaddr_in from;
  size_t id = 0;

  void assign(const connection_impl &impl)
  {
    socket = impl.socket;
    from = impl.from;
    id = impl.id;
  }

  void close()
  {
    if (socket != detail::invalid_socket)
    {
      detail::close_socket(socket);
      socket = detail::invalid_socket;
    }
  }
};

}

//---------------------------------------------------------------------------------------------------------------------
connection::connection(const detail::connection_impl &impl)
  : _p(std::make_unique<detail::connection_impl>())
{
  _p->assign(impl);
}

//---------------------------------------------------------------------------------------------------------------------
connection::~connection()
{
  
}

//---------------------------------------------------------------------------------------------------------------------
bool connection::is_valid() const { return _p->socket != detail::invalid_socket; }

//---------------------------------------------------------------------------------------------------------------------
size_t connection::id() const { return _p->id; }

//---------------------------------------------------------------------------------------------------------------------
size_t connection::write(const void *ptr, size_t length)
{
  if (!is_valid())
    return detail::socket_error;

  if (!ptr || !length)
    return 0;

  int result = send(_p->socket, static_cast<const char *>(ptr), static_cast<int>(length), 0);

  if (!result || result == detail::socket_error)
    return 0;

  return static_cast<size_t>(result);
}

//---------------------------------------------------------------------------------------------------------------------
bool connection::force_write(const void *ptr, size_t length)
{
  if (!is_valid())
    return false;

  if (!ptr)
    return true;

  const char *chPtr = static_cast<const char *>(ptr);

  while (length)
  {
    int result = send(_p->socket, chPtr, static_cast<int>(length), 0);

    if (!result || result == detail::socket_error)
      return false;

    length -= static_cast<size_t>(result);
    chPtr += result;
  }

  return true;
}

//---------------------------------------------------------------------------------------------------------------------
size_t connection::read(void *ptr, size_t length)
{
  if (!is_valid())
    return detail::socket_error;

  if (!ptr || !length)
    return 0;

  int result = recv(_p->socket, static_cast<char *>(ptr), static_cast<int>(length), 0);

  if (!result || result == detail::socket_error)
    return 0;

  return static_cast<size_t>(result);
}

//---------------------------------------------------------------------------------------------------------------------
bool connection::force_read(void *ptr, size_t length)
{
  if (!is_valid())
    return false;

  if (!ptr)
    return true;

  char *chPtr = static_cast<char *>(ptr);

  while (length)
  {
    int result = recv(_p->socket, chPtr, static_cast<int>(length), 0);

    if (!result || result == detail::socket_error)
      return false;

    length -= static_cast<size_t>(result);
    chPtr += result;
  }

  return true;
}

//---------------------------------------------------------------------------------------------------------------------
bool connection::read_line(std::string &output)
{
  if (!is_valid())
    return false;

  output = "";

  while (true)
  {
    char ch;
    int r = recv(_p->socket, &ch, 1, 0);

    if (!r || r == detail::socket_error)
      return false;

    if (ch == '\n')
      break;
    else if (ch != '\r')
      output += ch;
  }

  return true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace detail {

struct basic_tcp_client_ref
{
  size_t refCount = 0;
  ptr<basic_tcp_client> client;

  basic_tcp_client_ref(ptr<basic_tcp_client> c)
    : client(c)
  {
  
  }
};

struct basic_tcp_server_impl
{
  std::atomic_bool isRunning;
  std::atomic_bool disconnectThreadQuit;
  sockaddr_in local;
  detail::lockable_value<std::vector<basic_tcp_client_ref>> connections;
  detail::semaphore disconnectSemaphore;
  int port = 0;
  detail::socket_type serverSocket = invalid_socket;
  std::unique_ptr<std::thread> acceptThread;
  std::unique_ptr<std::thread> disconnectThread;
  id_t nextClientID = 1;

  basic_tcp_server_impl()
  {
    isRunning = false;
    disconnectThreadQuit = false;
  }
};

}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//---------------------------------------------------------------------------------------------------------------------
basic_tcp_server::basic_tcp_server(int port)
  : _p(std::make_unique<detail::basic_tcp_server_impl>())
{
#ifdef HEADSOCKET_PLATFORM_WINDOWS
  WSADATA wsaData;
  WSAStartup(0x101, &wsaData);
#endif

  _p->local.sin_family = AF_INET;
  _p->local.sin_addr.s_addr = INADDR_ANY;
  _p->local.sin_port = htons(static_cast<unsigned short>(port));

  _p->serverSocket = socket(AF_INET, SOCK_STREAM, 0);

  int opt_enable = 1;
  setsockopt(_p->serverSocket, SOL_SOCKET, SO_REUSEADDR, (const char*) &opt_enable, sizeof(int));

  if (bind(_p->serverSocket, reinterpret_cast<sockaddr *>(&_p->local), sizeof(_p->local)) != 0)
    return;

  if (listen(_p->serverSocket, 8))
    return;

  _p->isRunning = true;
  _p->port = port;
  _p->acceptThread = std::make_unique<std::thread>(std::bind(&basic_tcp_server::accept_thread, this));
  _p->disconnectThread = std::make_unique<std::thread>(std::bind(&basic_tcp_server::disconnect_thread, this));
}

//---------------------------------------------------------------------------------------------------------------------
basic_tcp_server::~basic_tcp_server()
{
  stop();

#ifdef HEADSOCKET_PLATFORM_WINDOWS
  WSACleanup();
#endif
}

//---------------------------------------------------------------------------------------------------------------------
int basic_tcp_server::port() const { return _p->port; }

//---------------------------------------------------------------------------------------------------------------------
void basic_tcp_server::stop()
{
  if (_p->isRunning.exchange(false))
  {
    detail::close_socket(_p->serverSocket);

    {
      acquire_clients();

      for (size_t i = 0, S = num_clients(); i < S; ++i)
        client_at(i)->disconnect();

      release_clients();
    }

    if (_p->acceptThread)
    {
      _p->acceptThread->join();
      _p->acceptThread = nullptr;
    }

    if (_p->disconnectThread)
    {
      _p->disconnectThreadQuit = true;
      _p->disconnectSemaphore.notify();

      _p->disconnectThread->join();
      _p->disconnectThread = nullptr;
    }
  }
}

//---------------------------------------------------------------------------------------------------------------------
bool basic_tcp_server::is_running() const { return _p->isRunning; }

//---------------------------------------------------------------------------------------------------------------------
bool basic_tcp_server::disconnect(ptr<basic_tcp_client> client)
{
  bool found = false;

  if (client)
  {
    {
      HEADSOCKET_LOCK(_p->connections);
      for (size_t i = 0, S = _p->connections->size(); i < S; ++i)
        if (_p->connections->at(i).client == client)
        {
          found = true;
          break;
        }
    }

    if (found && !client->disconnect())
    {
      client_disconnected(client);
      _p->disconnectSemaphore.notify();
    }
  }

  return found;
}

//---------------------------------------------------------------------------------------------------------------------
bool basic_tcp_server::disconnect(id_t id)
{
  bool found = false;

  if (id)
  {
    ptr<basic_tcp_client> client;

    {
      HEADSOCKET_LOCK(_p->connections);
      for (size_t i = 0, S = _p->connections->size(); i < S; ++i)
        if (_p->connections->at(i).client->id() == id)
        {
          client = _p->connections->at(i).client;
          found = true;
          break;
        }
    }

    if (found && !client->disconnect())
    {
      client_disconnected(client);
      _p->disconnectSemaphore.notify();
    }
  }

  return found;
}

//---------------------------------------------------------------------------------------------------------------------
ptr<basic_tcp_client> basic_tcp_server::client_at(size_t index) const
{
  HEADSOCKET_LOCK(_p->connections);
  return index < _p->connections->size() ? _p->connections->at(index).client : nullptr;
}

//---------------------------------------------------------------------------------------------------------------------
size_t basic_tcp_server::num_clients() const
{
  HEADSOCKET_LOCK(_p->connections);
  return _p->connections->size();
}

//---------------------------------------------------------------------------------------------------------------------
size_t basic_tcp_server::acquire_clients() const
{
  HEADSOCKET_LOCK(_p->connections);

  for (auto &clientRef : _p->connections.value)
    ++clientRef.refCount;

  return _p->connections->size();
}

//---------------------------------------------------------------------------------------------------------------------
void basic_tcp_server::release_clients() const
{
  HEADSOCKET_LOCK(_p->connections);

  for (auto &clientRef : _p->connections.value)
    --clientRef.refCount;

  remove_disconnected();
}

//---------------------------------------------------------------------------------------------------------------------
void basic_tcp_server::remove_disconnected() const
{
  size_t i = 0;

  while (i < _p->connections->size())
  {
    auto &clientRef = _p->connections.value[i];

    if (!clientRef.client->is_connected() && clientRef.refCount == 0)
    {
      clientRef.client->on_disconnect();
      _p->connections->erase(_p->connections->begin() + i);
    }
    else
      ++i;
  }
}

//---------------------------------------------------------------------------------------------------------------------
void basic_tcp_server::accept_thread()
{
  detail::set_thread_name("BaseTcpServer::acceptThread");

  while (_p->isRunning)
  {
    detail::connection_impl conn_impl;
    conn_impl.socket = ::accept(_p->serverSocket, reinterpret_cast<struct sockaddr *>(&conn_impl.from), nullptr);
    conn_impl.id = _p->nextClientID++;

    if (!_p->nextClientID)
      ++_p->nextClientID;

    if (!_p->isRunning)
      break;

    if (conn_impl.socket != detail::invalid_socket)
    {
      connection conn(conn_impl);

      ptr<basic_tcp_client> newClient;
      bool failed = false;

      if (handshake(conn))
      {
        if ((newClient = accept(conn)))
        {
          newClient->on_accept();

          HEADSOCKET_LOCK(_p->connections);
          _p->connections->push_back(newClient);
        }
        else {
            failed = true;
        }
      }
      else {
          failed = true;
      }

      if (failed)
      {
        conn_impl.close();
        --_p->nextClientID;

        if (!_p->nextClientID)
          --_p->nextClientID;
      }
      else
        client_connected(newClient);
    }
  }
}

//---------------------------------------------------------------------------------------------------------------------
void basic_tcp_server::disconnect_thread()
{
  detail::set_thread_name("BaseTcpServer::disconnectThread");

  while (!_p->disconnectThreadQuit)
  {
    {
      HEADSOCKET_LOCK(_p->disconnectSemaphore);
      HEADSOCKET_LOCK(_p->connections);

      remove_disconnected();
      _p->disconnectSemaphore.consume();
    }
  }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace detail {

struct basic_tcp_client_impl
{
  std::atomic_int refCount;
  std::atomic_bool isConnected;
  std::weak_ptr<basic_tcp_server> server;
  connection conn = detail::connection_impl();
  std::string address = "";
  int port = 0;

  basic_tcp_client_impl()
  {
    refCount = 0;
    isConnected = false;
  }
};

}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//---------------------------------------------------------------------------------------------------------------------
basic_tcp_client::basic_tcp_client(const std::string &address, int port)
  : _p(std::make_unique<detail::basic_tcp_client_impl>())
{
  struct addrinfo *result = nullptr, *ptr = nullptr, hints;

  memset(&hints, 0, sizeof(hints));
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_protocol = IPPROTO_TCP;

  char buff[16];
  HEADSOCKET_SPRINTF(buff, "%d", port);

  if (getaddrinfo(address.c_str(), buff, &hints, &result))
    return;

  for (ptr = result; ptr != nullptr; ptr = ptr->ai_next)
  {
    _p->conn.impl()->socket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);

    if (!_p->conn.is_valid())
      return;

    if (connect(_p->conn.impl()->socket, ptr->ai_addr, static_cast<int>(ptr->ai_addrlen)) == detail::socket_error)
    {
      detail::close_socket(_p->conn.impl()->socket);
      _p->conn.impl()->socket = detail::invalid_socket;
      continue;
    }

    break;
  }

  freeaddrinfo(result);

  if (!_p->conn.is_valid())
    return;

  _p->address = address;
  _p->port = port;
  _p->isConnected = true;
}

//---------------------------------------------------------------------------------------------------------------------
basic_tcp_client::basic_tcp_client(ptr<basic_tcp_server> server, connection &conn)
  : _p(std::make_unique<detail::basic_tcp_client_impl>())
{
  _p->server = server;
  _p->conn.impl()->assign(*(conn.impl()));
  _p->isConnected = true;
}

//---------------------------------------------------------------------------------------------------------------------
basic_tcp_client::~basic_tcp_client()
{
  disconnect();
}

//---------------------------------------------------------------------------------------------------------------------
bool basic_tcp_client::disconnect()
{
  bool wasConnected = _p->isConnected.exchange(false);

  if (wasConnected)
  {
    _p->conn.impl()->close();

    ptr<basic_tcp_server> s = server();

    if (s)
      s->disconnect(_p->conn.id());
  }

  return wasConnected;
}

//---------------------------------------------------------------------------------------------------------------------
bool basic_tcp_client::is_connected() const { return _p->isConnected; }

//---------------------------------------------------------------------------------------------------------------------
ptr<basic_tcp_server> basic_tcp_client::server() const { return _p->server.lock(); }

//---------------------------------------------------------------------------------------------------------------------
id_t basic_tcp_client::id() const { return _p->conn.id(); }

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//---------------------------------------------------------------------------------------------------------------------
tcp_client::tcp_client(const std::string &address, int port)
  : base_t(address, port)
{

}

//---------------------------------------------------------------------------------------------------------------------
tcp_client::tcp_client(ptr<basic_tcp_server> server, connection &conn)
  : base_t(server, conn)
{

}

//---------------------------------------------------------------------------------------------------------------------
tcp_client::~tcp_client()
{

}

//---------------------------------------------------------------------------------------------------------------------
size_t tcp_client::write(const void *ptr, size_t length) { return _p->conn.write(ptr, length); }

//---------------------------------------------------------------------------------------------------------------------
size_t tcp_client::read(void *ptr, size_t length) { return _p->conn.read(ptr, length); }

//---------------------------------------------------------------------------------------------------------------------
bool tcp_client::force_write(const void *ptr, size_t length) { return _p->conn.force_write(ptr, length); }

//---------------------------------------------------------------------------------------------------------------------
bool tcp_client::force_read(void *ptr, size_t length) { return _p->conn.force_read(ptr, length); }

//---------------------------------------------------------------------------------------------------------------------
bool tcp_client::read_line(std::string &output) { return _p->conn.read_line(output); }

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace detail {

struct async_tcp_client_impl
{
  detail::semaphore writeSemaphore;
  detail::lockable_value<detail::data_block_buffer> writeBlocks;
  detail::lockable_value<detail::data_block_buffer> readBlocks;
  std::unique_ptr<std::thread> writeThread;
  std::unique_ptr<std::thread> readThread;
  std::atomic_int threadCounter = { 0 };
};

}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//---------------------------------------------------------------------------------------------------------------------
async_tcp_client::async_tcp_client(const std::string &address, int port)
  : base_t(address, port)
  , _ap(std::make_unique<detail::async_tcp_client_impl>())
{
  
}

//---------------------------------------------------------------------------------------------------------------------
async_tcp_client::async_tcp_client(ptr<basic_tcp_server> server, connection &conn)
  : base_t(server, conn)
  , _ap(new detail::async_tcp_client_impl())
{
  
}

//---------------------------------------------------------------------------------------------------------------------
async_tcp_client::~async_tcp_client()
{
  disconnect();

  _ap->writeSemaphore.notify();
  _ap->writeThread->join();
  _ap->readThread->join();
}

//---------------------------------------------------------------------------------------------------------------------
void async_tcp_client::push(const void *ptr, size_t length, opcode opcode)
{
  if (!ptr)
    return;

  {
    HEADSOCKET_LOCK(_ap->writeBlocks);
    _ap->writeBlocks->block_begin(opcode);
    _ap->writeBlocks->write(ptr, length);
    _ap->writeBlocks->block_end();
  }

  _ap->writeSemaphore.notify();
}

//---------------------------------------------------------------------------------------------------------------------
void async_tcp_client::push(const void *ptr, size_t length)
{
  push(ptr, length, opcode::binary);
}

//---------------------------------------------------------------------------------------------------------------------
void async_tcp_client::push(const std::string &text)
{
  push(text.c_str(), text.length(), opcode::text);
}

//---------------------------------------------------------------------------------------------------------------------
size_t async_tcp_client::peek() const
{
  HEADSOCKET_LOCK(_ap->readBlocks);
  return _ap->readBlocks->peek(nullptr);
}

//---------------------------------------------------------------------------------------------------------------------
size_t async_tcp_client::pop(void *ptr, size_t length)
{
  if (!ptr)
    return invalid_operation;

  if (!length)
    return 0;

  HEADSOCKET_LOCK(_ap->readBlocks);
  return _ap->readBlocks->read(ptr, length);
}

//---------------------------------------------------------------------------------------------------------------------
void async_tcp_client::init_threads()
{
  _ap->threadCounter = 0;
  _ap->writeThread = std::make_unique<std::thread>(std::bind(&async_tcp_client::write_thread, this));
  _ap->readThread = std::make_unique<std::thread>(std::bind(&async_tcp_client::read_thread, this));
}

//---------------------------------------------------------------------------------------------------------------------
void async_tcp_client::write_thread()
{
  ++_ap->threadCounter;
  detail::set_thread_name("AsyncTcpClient::writeThread");

  std::vector<uint8_t> buffer(1024 * 1024);

  while (_p->isConnected)
  {
    size_t written = 0;
    {
      HEADSOCKET_LOCK(_ap->writeSemaphore);

      if (!_p->isConnected)
        break;

      written = async_write_handler(buffer.data(), buffer.size());
    }

    if (written == invalid_operation)
      break;

    if (!written)
      buffer.resize(buffer.size() * 2);
    else
    {
      const char *cursor = reinterpret_cast<const char *>(buffer.data());

      while (written)
      {
        int result = send(_p->conn.impl()->socket, cursor, static_cast<int>(written), 0);

        if (!result || result == detail::socket_error)
          break;

        cursor += result;
        written -= static_cast<size_t>(result);
      }
    }
  }

  kill_threads();
  --_ap->threadCounter;
}

//---------------------------------------------------------------------------------------------------------------------
size_t async_tcp_client::async_write_handler(uint8_t *ptr, size_t length)
{
  HEADSOCKET_LOCK(_ap->writeBlocks);
 
  size_t toWrite = _ap->writeBlocks->peek(nullptr);
  size_t toConsume = length > toWrite ? toWrite : length;
  _ap->writeBlocks->read(ptr, toConsume);

  if (toWrite == toConsume)
    _ap->writeSemaphore.consume();

  return toConsume;
}

//---------------------------------------------------------------------------------------------------------------------
size_t async_tcp_client::async_read_handler(uint8_t *ptr, size_t length)
{
  HEADSOCKET_LOCK(_ap->readBlocks);

  _ap->readBlocks->block_begin(opcode::binary);
  _ap->readBlocks->write(ptr, length);
  _ap->readBlocks->block_end();

  return length;
}

//---------------------------------------------------------------------------------------------------------------------
void async_tcp_client::read_thread()
{
  ++_ap->threadCounter;
  detail::set_thread_name("AsyncTcpClient::readThread");

  std::vector<uint8_t> buffer(1024 * 1024);
  size_t bufferBytes = 0, consumed = 0;

  while (_p->isConnected)
  {
    while (true)
    {
      int result = static_cast<int>(bufferBytes);

      if (!result || !consumed)
      {
        result = recv(
          _p->conn.impl()->socket,
          reinterpret_cast<char *>(buffer.data() + bufferBytes),
          static_cast<int>(buffer.size() - bufferBytes),
          0);

        if (!result || result == detail::socket_error)
        {
          consumed = invalid_operation;
          break;
        }

        bufferBytes += static_cast<size_t>(result);
      }

      consumed = async_read_handler(buffer.data(), bufferBytes);

      if (!consumed)
      {
        if (bufferBytes == buffer.size())
          buffer.resize(buffer.size() * 2);
      }
      else
        break;
    }

    if (consumed == invalid_operation)
      break;

    bufferBytes -= consumed;

    if (bufferBytes)
      memcpy(buffer.data(), buffer.data() + consumed, bufferBytes);
  }

  kill_threads();
  --_ap->threadCounter;
}

//---------------------------------------------------------------------------------------------------------------------
void async_tcp_client::kill_threads()
{
  if (std::this_thread::get_id() == _ap->readThread->get_id())
    _ap->writeSemaphore.notify();

  disconnect();
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//---------------------------------------------------------------------------------------------------------------------
web_socket_client::web_socket_client(const std::string &address, int port)
  : base_t(address, port)
{

}

//---------------------------------------------------------------------------------------------------------------------
web_socket_client::web_socket_client(ptr<basic_tcp_server> server, connection &conn)
  : base_t(server, conn)
{

}

//---------------------------------------------------------------------------------------------------------------------
web_socket_client::~web_socket_client()
{

}

//---------------------------------------------------------------------------------------------------------------------
size_t web_socket_client::peek(opcode *op) const
{
  HEADSOCKET_LOCK(_ap->readBlocks);
  return _ap->readBlocks->peek(op);
}

//---------------------------------------------------------------------------------------------------------------------
size_t web_socket_client::async_write_handler(uint8_t *ptr, size_t length)
{
  uint8_t *cursor = ptr;
  HEADSOCKET_LOCK(_ap->writeBlocks);

  while (length >= 16)
  {
    opcode op{};
    size_t toWrite = _ap->writeBlocks->peek(&op);
    size_t toConsume = (length - 15) > frame_size_limit ? frame_size_limit : (length - 15);
    toConsume = toConsume > toWrite ? toWrite : toConsume;

    frame_header header;
    header.fin = (toWrite - toConsume) == 0;
    header.op = op;
    header.masked = false;
    header.payload_length = toConsume;

    size_t headerSize = header.write(cursor, length);
    cursor += headerSize;
    length -= headerSize;
    _ap->writeBlocks->read(cursor, toConsume);
    cursor += toConsume;
    length -= toConsume;

    if (header.fin)
      _ap->writeSemaphore.consume();

    if (!_ap->writeBlocks->peek(&op))
      break;
  }

  return cursor - ptr;
}

//---------------------------------------------------------------------------------------------------------------------
size_t web_socket_client::async_read_handler(uint8_t *ptr, size_t length)
{
  uint8_t *cursor = ptr;
  HEADSOCKET_LOCK(_ap->readBlocks);

  if (!_payload_size)
  {
    opcode prevOpcode = _current_header.op;
    size_t headerSize = _current_header.read(cursor, length);

    if (!headerSize)
      return 0;
    else if (headerSize == invalid_operation)
      return invalid_operation;

    _payload_size = _current_header.payload_length;
    cursor += headerSize;
    length -= headerSize;

    if (_current_header.op != opcode::continuation)
      _ap->readBlocks->block_begin(_current_header.op);
    else
      _current_header.op = prevOpcode;
  }

  if (_payload_size)
  {
    size_t toConsume = length >= _payload_size ? _payload_size : length;

    if (toConsume)
    {
      _ap->readBlocks->write(cursor, toConsume);
      _payload_size -= toConsume;
      cursor += toConsume;
      length -= toConsume;
    }
  }

  if (!_payload_size)
  {
    if (_current_header.masked)
    {
      //data_block &db = _ap->readBlocks->blocks.back();
      size_t len = _current_header.payload_length;
      detail::utils::xor32(_current_header.masking_key, _ap->readBlocks->buffer.data() + _ap->readBlocks->buffer.size() - len, len);
    }

    if (_current_header.fin)
    {
      data_block &db = _ap->readBlocks->blocks.back();

      switch (_current_header.op)
      {
        case opcode::ping:
          push(_ap->readBlocks->buffer.data() + db.offset, db.length, opcode::pong);
          break;

        case opcode::text:
          _ap->readBlocks->buffer.push_back(0);
          ++db.length;
          break;

        case opcode::connection_close:
          kill_threads();
          break;

        default:
          break;
      }

      if (_current_header.op == opcode::text || _current_header.op == opcode::binary)
      {
        _ap->readBlocks->block_end();
        if (async_received_data(db, _ap->readBlocks->buffer.data() + db.offset, db.length))
          _ap->readBlocks->block_remove();
      }
    }
  }

  return cursor - ptr;
}

//---------------------------------------------------------------------------------------------------------------------
#define HAVE_ENOUGH_BYTES(num) if (length < num) return 0; else length -= num;
size_t web_socket_client::frame_header::read(const uint8_t *ptr, size_t length)
{
  const uint8_t *cursor = ptr;
  HAVE_ENOUGH_BYTES(2);
  this->fin = ((*cursor) & 0x80) != 0;
  this->op = static_cast<opcode>((*cursor++) & 0x0F);

  this->masked = ((*cursor) & 0x80) != 0;
  uint8_t byte = (*cursor++) & 0x7F;

  if (byte < 126)
    this->payload_length = byte;
  else if (byte == 126)
  {
    HAVE_ENOUGH_BYTES(2);
    this->payload_length = detail::utils::swap16bits(*(reinterpret_cast<const uint16_t *>(cursor)));
    cursor += 2;
  }
  else if (byte == 127)
  {
    HAVE_ENOUGH_BYTES(8);
    uint64_t length64 = detail::utils::swap64bits(*(reinterpret_cast<const uint64_t *>(cursor))) & 0x7FFFFFFFFFFFFFFFULL;
    this->payload_length = static_cast<size_t>(length64);
    cursor += 8;
  }

  if (this->masked)
  {
    HAVE_ENOUGH_BYTES(4);
    this->masking_key = *(reinterpret_cast<const uint32_t *>(cursor));
    cursor += 4;
  }

  return cursor - ptr;
}

//---------------------------------------------------------------------------------------------------------------------
size_t web_socket_client::frame_header::write(uint8_t *ptr, size_t length) const
{
  uint8_t *cursor = ptr;
  HAVE_ENOUGH_BYTES(2);
  *cursor = this->fin ? 0x80 : 0x00;
  *cursor++ |= static_cast<uint8_t>(this->op);

  *cursor = this->masked ? 0x80 : 0x00;

  if (this->payload_length < 126)
    *cursor++ |= static_cast<uint8_t>(this->payload_length);
  else if (this->payload_length < 65536)
  {
    HAVE_ENOUGH_BYTES(2);
    *cursor++ |= 126;
    *reinterpret_cast<uint16_t *>(cursor) = detail::utils::swap16bits(static_cast<uint16_t>(this->payload_length));
    cursor += 2;
  }
  else
  {
    HAVE_ENOUGH_BYTES(8);
    *cursor++ |= 127;
    *reinterpret_cast<uint64_t *>(cursor) = detail::utils::swap64bits(static_cast<uint64_t>(this->payload_length));
    cursor += 8;
  }

  if (this->masked)
  {
    HAVE_ENOUGH_BYTES(4);
    *reinterpret_cast<uint32_t *>(cursor) = this->masking_key;
    cursor += 4;
  }

  return cursor - ptr;
}
#undef HAVE_ENOUGH_BYTES

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace detail {

}

//---------------------------------------------------------------------------------------------------------------------
bool http_server::handshake(connection &conn)
{
  std::string requestLine;

  if (!conn.read_line(requestLine))
    return false;

  std::string headerLine;
  while (conn.read_line(headerLine))
  {
    if (headerLine.empty())
      break;
  }

  std::string method = detail::utils::cut_front(requestLine);
  std::string path = detail::utils::url_decode(detail::utils::cut_front(requestLine));
  
  if (!path.empty() && path.front() == '/') path = path.substr(1);
  if (!path.empty() && path.back() == '/') path = path.substr(0, path.length() - 1);

  std::string params_get = detail::utils::cut_back(path, '?', false, false);
  std::string version = detail::utils::cut_front(requestLine);
  
  parameters_t params;
  std::string param_str;
  while (!(param_str = detail::utils::cut_front(params_get, '&')).empty())
  {
    parameter param;
    param.name = detail::utils::cut_front(param_str, '=');
    param.value = param_str;
    param.integer = atoi(param_str.c_str());
    param.real = atof(param_str.c_str());
    param.boolean = (param.integer != 0) || (param_str == "true");

    params[param.name] = param;
  }

  response resp;
  if (path != "favicon.ico" && request(path, params, resp))
  {
    std::stringstream ss;
    ss << version << " 200 OK\r\n";
    ss << "Content-Type: " << resp.content_type << "\r\n";
    ss << "Content-Length: " << resp.message.length() << "\r\n\r\n";
    ss << resp.message;

    conn.write(ss.str());
  }
  else
  {
    conn.write(version);
    conn.write(" 404 Not Found\r\n");
  }

  return false;
}

}
#endif
#endif
