#ifndef NALL_HTTP_HPP
#define NALL_HTTP_HPP

#if !defined(_WIN32)
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <netdb.h>
#else
  #include <winsock2.h>
  #include <ws2tcpip.h>
  #include <windows.h>
#endif

#include <nall/platform.hpp>
#include <nall/string.hpp>

namespace nall {

struct http {
  string hostname;
  addrinfo* serverinfo;
  int serversocket;
  string header;

  inline void download(const string& path, uint8_t*& data, unsigned& size) {
    data = nullptr;
    size = 0;

    send({
      "GET ", path, " HTTP/1.1\r\n"
      "Host: ", hostname, "\r\n"
      "Connection: close\r\n"
      "\r\n"
    });

    header = downloadHeader();
    downloadContent(data, size);
  }

  inline bool connect(string host, unsigned port) {
    hostname = host;

    addrinfo hints;
    memset(&hints, 0, sizeof(addrinfo));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;

    int status = getaddrinfo(hostname, string(port), &hints, &serverinfo);
    if(status != 0) return false;

    serversocket = socket(serverinfo->ai_family, serverinfo->ai_socktype, serverinfo->ai_protocol);
    if(serversocket == -1) return false;

    int result = ::connect(serversocket, serverinfo->ai_addr, serverinfo->ai_addrlen);
    if(result == -1) return false;

    return true;
  }

  inline bool send(const string& data) {
    return send((const uint8_t*)(const char*)data, data.length());
  }

  inline bool send(const uint8_t* data, unsigned size) {
    while(size) {
      int length = ::send(serversocket, (const char*)data, size, 0);
      if(length == -1) return false;
      data += length;
      size -= length;
    }
    return true;
  }

  inline string downloadHeader() {
    string output;
    do {
      char buffer[2];
      int length = recv(serversocket, buffer, 1, 0);
      if(length <= 0) return output;
      buffer[1] = 0;
      output.append(buffer);
    } while(output.endsWith("\r\n\r\n") == false);
    return output;
  }

  inline string downloadChunkLength() {
    string output;
    do {
      char buffer[2];
      int length = recv(serversocket, buffer, 1, 0);
      if(length <= 0) return output;
      buffer[1] = 0;
      output.append(buffer);
    } while(output.endsWith("\r\n") == false);
    return output;
  }

  inline void downloadContent(uint8_t*& data, unsigned& size) {
    unsigned capacity = 0;

    if(header.ifind("\r\nTransfer-Encoding: chunked\r\n")) {
      while(true) {
        unsigned length = hex(downloadChunkLength());
        if(length == 0) break;
        capacity += length;
        data = (uint8_t*)realloc(data, capacity);

        char buffer[length];
        while(length) {
          int packetlength = recv(serversocket, buffer, length, 0);
          if(packetlength <= 0) break;
          memcpy(data + size, buffer, packetlength);
          size += packetlength;
          length -= packetlength;
        }
      }
    } else if(auto position = header.ifind("\r\nContent-Length: ")) {
      unsigned length = decimal((const char*)header + position() + 18);
      while(length) {
        char buffer[256];
        int packetlength = recv(serversocket, buffer, min(256, length), 0);
        if(packetlength <= 0) break;
        capacity += packetlength;
        data = (uint8_t*)realloc(data, capacity);
        memcpy(data + size, buffer, packetlength);
        size += packetlength;
        length -= packetlength;
      }
    } else {
      while(true) {
        char buffer[256];
        int packetlength = recv(serversocket, buffer, 256, 0);
        if(packetlength <= 0) break;
        capacity += packetlength;
        data = (uint8_t*)realloc(data, capacity);
        memcpy(data + size, buffer, packetlength);
        size += packetlength;
      }
    }

    data = (uint8_t*)realloc(data, capacity + 1);
    data[capacity] = 0;
  }

  inline void disconnect() {
    close(serversocket);
    freeaddrinfo(serverinfo);
    serverinfo = nullptr;
    serversocket = -1;
  }

  #ifdef _WIN32
  inline int close(int sock) {
    return closesocket(sock);
  }

  inline http() {
    int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(sock == INVALID_SOCKET && WSAGetLastError() == WSANOTINITIALISED) {
      WSADATA wsaData;
      if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        WSACleanup();
        return;
      }
    } else {
      close(sock);
    }
  }
  #endif
};

}

#endif
