/*
 * Copyright 2012 Steven Gribble
 *
 *  This file is part of the UW CSE 333 lecture code (333lec).
 *
 *  333lec is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  333lec is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with 333proj.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>

#include <boost/algorithm/string.hpp>
#include <iostream>
#include <stdexcept>
#include <vector>
#include "HttpServer.h"

extern int errno;

HttpServer::HttpServer(std::string portnum) {
  // Do constructor-y stuff here.
  Listen(portnum);
}

void HttpServer::AcceptDispatch(HandleFn handle) {
  // Loop forever, receiving a client connection and doing
  // the HTTP parsing on it.
  while (1) {
    struct sockaddr_storage caddr;
    socklen_t caddr_len = sizeof(caddr);
    struct sockaddr *saddr = reinterpret_cast<struct sockaddr *>(&caddr);
    int client_fd = accept(listen_fd_, saddr, &caddr_len);
    if (client_fd < 0) {
      continue;
    }

    // Have a client connection.  Handle it.
    std::string client_IP, client_DNS;
    unsigned short client_port;
    try {
      GetIPInfo(client_fd, true, &client_IP, &client_DNS);
    } catch(const std::exception &exc) {
      std:: cerr << exc.what() << std::endl;
      close(client_fd);
      continue;
    }
    if (saddr->sa_family == AF_INET) {
      client_port = htons((reinterpret_cast<struct sockaddr_in *>(saddr))->sin_port);
    } else {
      client_port = htons((reinterpret_cast<struct sockaddr_in6 *>(saddr))->sin6_port);
    }

    // Get the URL.
    std::string URL;
    try {
      URL = ParseURL(client_fd);
    } catch(const std::exception &exc) {
      std:: cerr << exc.what() << std::endl;
      close(client_fd);
      continue;
    }

    // Invoke the handler.
    handle(client_fd, URL, client_IP, client_port);
    close(client_fd);
  }
}

void HttpServer::Listen(std::string portnum) {
  // Populate the "hints" addrinfo structure for getaddrinfo().
  // ("man addrinfo")
  struct addrinfo hints;
  memset(&hints, 0, sizeof(struct addrinfo));
  hints.ai_family = AF_UNSPEC;      // allow IPv4 or IPv6
  hints.ai_socktype = SOCK_STREAM;  // stream
  hints.ai_flags = AI_PASSIVE;      // use wildcard "INADDR_ANY"
  hints.ai_protocol = IPPROTO_TCP;  // tcp protocol
  hints.ai_canonname = NULL;
  hints.ai_addr = NULL;
  hints.ai_next = NULL;

  // Use argv[1] as the string representation of our portnumber to
  // pass in to getaddrinfo().  getaddrinfo() returns a list of
  // address structures via the output parameter "result".
  struct addrinfo *result;
  int res = getaddrinfo(NULL, portnum.c_str(), &hints, &result);
  // Did addrinfo() fail?
  if (res != 0) {
    std::string err = "getaddrinfo() failed with ";
    err += gai_strerror(res);
    throw std::runtime_error(err);
  }

  // Loop through the returned address structures until we are able to
  // create a socket and bind to one.  The address structures are
  // linked in a list through the "ai_next" field of result.  We will
  // make sure to break out of the for loop, even if an error exists,
  // so that we can free the structure returned by getaddrinfo.
  listen_fd_ = -1;
  for (struct addrinfo *rp = result; rp != NULL; rp = rp->ai_next) {
    listen_fd_ = socket(rp->ai_family,
                        rp->ai_socktype,
                        rp->ai_protocol);
    if (listen_fd_ == -1) {
      // Creating this socket failed.  So, loop to the next returned
      // result and try again.
      continue;
    }

    // Configure the socket; we're setting a socket "option."  In
    // particular, we set "SO_REUSEADDR", which tells the TCP stack
    // so make the port we bind to available again as soon as we
    // exit, rather than waiting for a few tens of seconds to recycle it.
    int optval = 1;
    if (setsockopt(listen_fd_, SOL_SOCKET, SO_REUSEADDR,
                   &optval, sizeof(optval)) != 0) {
      close(listen_fd_);
      listen_fd_ = -1;
      continue;
    }

    // Try binding the socket to the address and port number returned
    // by getaddrinfo().
    if (bind(listen_fd_, rp->ai_addr, rp->ai_addrlen) == 0) {
      // Bind worked!  Get the information about what we bound to.
      try {
        GetIPInfo(listen_fd_, false, &server_addr_, &server_dns_);
        server_port_ = portnum;
      } catch(const std::exception &exc) {
        // GetIPInfo failed; loop around.
        std:: cerr << exc.what() << std::endl;
        close(listen_fd_);
        listen_fd_ = -1;
        continue;
      }
      break;
    } else {
      // The bind failed.  Close the socket, then loop back around and
      // try the next address/port returned by getaddrinfo().
      close(listen_fd_);
      listen_fd_ = -1;
      continue;
    }
  }

  // Free the structure returned by getaddrinfo().
  freeaddrinfo(result);

  // If we failed to bind, throw an exception.
  if (listen_fd_ <= 0) {
    throw std::runtime_error("Couldn't bind to any addresses.");
  }

  // Success. Tell the OS that we want this to be a listening socket.
  if (listen(listen_fd_, SOMAXCONN) != 0) {
    close(listen_fd_);
    listen_fd_ = -1;
    std::string err = "Failed to mark socket as listening: ";
    err += strerror(errno);
    throw std::runtime_error(err);
  }

  // Done!
}

void HttpServer::GetIPInfo(int fd, bool peer, std::string *addr, std::string *dns) const {
  // Get the socket address information.
  struct sockaddr_storage fdaddr;
  socklen_t fdaddrlen = sizeof(fdaddr);
  if (!peer) {
    if (getsockname(fd,
                    reinterpret_cast<struct sockaddr *>(&fdaddr),
                    &fdaddrlen) != 0) {
      throw std::runtime_error("getsockname failed");
    }
  } else {
    if (getpeername(fd,
                    reinterpret_cast<struct sockaddr *>(&fdaddr),
                    &fdaddrlen) != 0) {
      throw std::runtime_error("getsockname failed");
    }
  }

  // Get the string representation
  struct sockaddr *saddr = reinterpret_cast<struct sockaddr *>(&fdaddr);
  if (saddr->sa_family == AF_INET) {
    // Print out the IPV4 address and port
    char astring[INET_ADDRSTRLEN];
    struct sockaddr_in *in4 = reinterpret_cast<struct sockaddr_in *>(saddr);
    inet_ntop(AF_INET, &(in4->sin_addr), astring, INET_ADDRSTRLEN);
    *addr = astring;
  } else if (saddr->sa_family == AF_INET6) {
    // Print out the IPV6 address and port
    char astring[INET6_ADDRSTRLEN];
    struct sockaddr_in6 *in6 = reinterpret_cast<struct sockaddr_in6 *>(saddr);
    inet_ntop(AF_INET, &(in6->sin6_addr), astring, INET6_ADDRSTRLEN);
    *addr = astring;
  } else {
    throw std::runtime_error("unknown IP protocol type");
  }

  // Get the reverse DNS
  char hostname[1024];  // ought to be big enough.
  if (getnameinfo(saddr, fdaddrlen, hostname, 1024, NULL, 0, 0) != 0) {
    throw std::runtime_error("reverse DNS failed");
  }
  *dns = hostname;
}

std::string HttpServer::ParseURL(int client_fd) {
  std::string data;

  // Keep reading from the client until either an error (in which case
  // throw an exception) or until the request terminator \r\n\r\n is
  // seen.
  char buf[1024];
  while (1) {
    ssize_t res = read(client_fd, buf, 1023);
    if (res == -1) {
      if ((errno == EAGAIN) || (errno == EINTR))
        continue;

      std::string err = "Couldn't read: ";
      err += strerror(errno);
      throw std::runtime_error(err);
    }
    // Append whatever we found, test for end of request.
    buf[res] = '\0';
    data += buf;
    if (data.find("\r\n\r\n") != std::string::npos)
      break;

    // Make sure client didn't hang up on us.
    if (res == 0) {
      // Hung up, no full request.
      throw std::runtime_error("Client disconnected prior to full request");
    }
  }

  // OK, pull out the first line.
  std::vector<std::string> strs;
  boost::split(strs, data, boost::is_any_of("\r\n"));
  if (strs.size() < 1) {
    throw std::runtime_error("couldn't parse request");
  }

  // Great, split that first line.
  std::vector<std::string> comps;
  boost::split(comps, strs[0], boost::is_any_of(" \r\n"));
  if (comps.size() < 3) {
    throw std::runtime_error("couldn't parse request(2)");
  }
  return comps[1];
}