#include <booster/aio/io_service.h>
#include <booster/aio/stream_socket.h>
#include <booster/aio/buffer.h>
#include <booster/posix_time.h>
#include <booster/shared_ptr.h>
#include <iostream>
#include <string>

class client;

struct bind_io {
public:
	typedef void (client::*member_function)(booster::system::error_code const &e,size_t n);
	bind_io(member_function f,client *c) : c_(c) , m_(f)
	{
	}
	void operator()(booster::system::error_code const &e,size_t n) const;
private:
	client *c_;
	member_function m_;
};

struct bind_handler {
public:
	typedef void (client::*member_function)(booster::system::error_code const &e);
	bind_handler(member_function f,client *c) : c_(c) , m_(f)
	{
	}
	void operator()(booster::system::error_code const &e) const;
private:
	client *c_;
	member_function m_;
};

int total,max_total;
int stopped,max_stopped;
int total_read;

std::string first_request;

class client {
public:
	client(booster::aio::io_service &srv,std::string ip,int port,std::string url) :
		socket_(srv)
	{
		request_ =	"GET " + url + " HTTP/1.0\r\n"
					"Host:" + ip + "\r\n"
					"Connection:close\r\n"
					"\r\n";
		ep_ = booster::aio::endpoint(ip,port);
	}
	void run()
	{
		if(total>= max_total) {
			stopped++;
			if(stopped == max_stopped)
				socket_.get_io_service().stop();
			return;
		}
		is_first_ = total++ == 0;
		if((total-1) / (max_total / 10) != total / (max_total/10) ) {
			std::cout << total << std::endl;
		}
		socket_.open(booster::aio::pf_inet);
		socket_.set_option(booster::aio::basic_socket::tcp_no_delay,1);
		socket_.async_connect(ep_,bind_handler(&client::on_connected,this));
	}
	void on_connected(booster::system::error_code const &e)
	{
		handle_error(e);
		socket_.async_write(booster::aio::buffer(request_),bind_io(&client::on_written,this));
	}
	void on_written(booster::system::error_code const &e,size_t)
	{
		handle_error(e);
		socket_.async_read_some(booster::aio::buffer(chunk,sizeof(chunk)),bind_io(&client::on_read,this));
	}
	void on_read(booster::system::error_code const &e,size_t n)
	{
		if(is_first_) {
			first_request.append(chunk,n);
		}
		total_read+=n;
		if(e==booster::system::error_code(booster::aio::aio_error::eof,booster::aio::aio_error_cat)) {
			booster::system::error_code err;
			socket_.shutdown(booster::aio::stream_socket::shut_rdwr,err);
			socket_.close(err);
			run();
		}
		else {
			on_written(e,0);
		}
	}
	void handle_error(booster::system::error_code const &e)
	{
		if(e) {
			throw booster::system::system_error(e);
		}
	}
private:
	char chunk[65536];
	booster::aio::stream_socket socket_;
	booster::aio::endpoint ep_;
	std::string request_;
	bool is_first_;
};

void bind_io::operator()(booster::system::error_code const &e,size_t n) const
{
	(c_->*m_)(e,n);
}

void bind_handler::operator()(booster::system::error_code const &e) const
{
	(c_->*m_)(e);
}

void help()
{
	std::cerr <<
	"benchmark  [options] \n"
	"--help  display help\n"
	"--ip    target ip - default 127.0.0.1\n"
	"--port  target port - default 8080\n"
	"--url   target url - default \"/\"\n"
	"--req   requests number - default 10000\n"
	"--con   concurrency - default 10 connections\n"
	<< std::endl;
	exit(1);
}

int main(int argc, char **argv)
{
	std::string ip="127.0.0.1";
	int port = 8080;
	std::string url="/";
	max_total = 10000;
	max_stopped = 10;

	for(int i=1;i<argc;i++) {
		std::string curr = argv[i];
		if(curr == "--help")
			help();
		else {
			if(i+1>= argc) {
				help();
				break;
			}
			std::string val=argv[i+1];
			i++;
			if(curr == "--ip")
				ip=val;
			else if(curr == "--port")
				port = atoi(val.c_str());
			else if(curr == "--url")
				url = val;
			else if(curr == "--req")
				max_total = atoi(val.c_str());
			else if(curr == "--con")
				max_stopped = atoi(val.c_str());
			else
				help();
		}
	}
	
	booster::ptime start,end;
	
	try {
		std::vector<booster::shared_ptr<client> > clients(max_stopped);
		booster::aio::io_service srv;
		for(size_t i=0;i<clients.size();i++) {
			clients[i].reset(new client(srv,ip,port,url));
		}
		start = booster::ptime::now();
		for(size_t i=0;i<clients.size();i++)
			clients[i]->run();
		srv.run();
		end = booster::ptime::now();
	}
	catch(std::exception const &e)
	{
		std::cerr << e.what() << std::endl;
		return 1;
	}
	std::cout << first_request << std::endl;
	std::cout << "Req/s:" << total / booster::ptime::to_number(end-start) << std::endl;
	std::cout << "Size: " << total_read / total << std::endl;
}

