#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/regex.hpp>
#include <boost/shared_ptr.hpp>

#include <cstring>
#include <exception>
#include <iostream>
#include <string>

boost::shared_ptr<boost::asio::ip::tcp::socket> g_socket;
boost::shared_ptr<boost::asio::deadline_timer> g_timer;
boost::array<char, 128> g_recv_buf;

void on_read(
    const boost::system::error_code& err,
    std::size_t bytes_transfered
    )
{
  if ( err ) {
    if ( err != boost::asio::error::eof )
      std::cerr << boost::system::system_error(err).what() << "\n";
    if ( g_timer )
      g_timer->cancel();
    return;
  }
  std::cout.write(g_recv_buf.data(), bytes_transfered);
  g_socket->async_read_some(boost::asio::buffer(g_recv_buf, g_recv_buf.size()), on_read);
}

void on_timeout(const boost::system::error_code& err)
{
  if ( err != boost::asio::error::operation_aborted ) {
    if (!err) {
      std::cerr << "TIMEOUT!\n";
      if ( g_socket )
        g_socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both);
    }
    else
      std::cerr << boost::system::system_error(err).what() << "\n";
  }
}

int main(int c, char** v)
{
  int timeout_sec = 1;

  if ( c < 2 ) {
    std::cout << "usage: " << v[0] << " <url> [<proxy url> [<timeour sec>]]\n";
    return 0;
  }
  const bool use_proxy = c > 2 && std::strlen(v[2]);
  const std::string proxy_url(use_proxy ? v[2] : "");
  const std::string target_url(v[1]);

  if ( c > 3 ) {
    try {
      timeout_sec = boost::lexical_cast<int>(v[3]);
    }
    catch (const std::exception& e) {
      std::cerr << "invalid timeout sec (" << v[3] << ") :" << e.what() << "\n";
      return 1;
    }
  }

  std::string proto, host, port, path;

  try {
    boost::regex r("(?:([^:]+)://)?([^:/]+)(?::(\\d+))?/?(.*$)");
    boost::smatch m;
    const std::string& url = use_proxy ? proxy_url : target_url;
    if ( boost::regex_match(url, m, r) ) {
      proto = m[1].length() > 0 ? m[1].str() : std::string("http");
      host  = m[2];
      port  = m[3];
      path  = use_proxy ? target_url : "/" + m[4];
    }
    else {
      std::cerr << "given url (" << url << ") doesn't match!\n";
      return 1;
    }
  }
  catch (const std::exception& e) {
    std::cerr << "error while analyzing url("
      << (use_proxy ? proxy_url : target_url) << ") : " << e.what() << "\n";
    return 1;
  }

  try {
    using boost::asio::ip::tcp;

    boost::asio::io_service io_service;
    
    tcp::resolver resolver(io_service);
    g_timer.reset(new boost::asio::deadline_timer(io_service));

    tcp::resolver::query query(host, port.empty() ? proto : port );

    tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
    tcp::resolver::iterator end;

    g_socket.reset(new tcp::socket(io_service));
    boost::system::error_code err = boost::asio::error::host_not_found;
    while (err && endpoint_iterator != end ) {
      g_socket->close();
      g_socket->connect(*endpoint_iterator++, err);
    }
    if ( err )
      throw boost::system::system_error(err);

    const std::string message("GET " + path + " HTTP/1.0\r\n\r\n");

    boost::asio::write(*g_socket, boost::asio::buffer(message),
        boost::asio::transfer_all(), err);
    if ( err )
      throw boost::system::system_error(err);


    g_timer->expires_from_now(boost::posix_time::seconds(timeout_sec));
    g_socket->async_read_some(boost::asio::buffer(g_recv_buf, g_recv_buf.size()), on_read);
    g_timer->async_wait(on_timeout);
    io_service.run();

    g_socket.reset();
    g_timer.reset();
  }
  catch (const std::exception& e) {
    std::cerr << e.what() << "\n";
    return 1;
  }

  return 0;
}