#include <iomanip>
#include <sstream>
#include <thread>
#include <memory>

#include <boost/asio.hpp>
#include <boost/bind.hpp>

#include "Base.h"
#include "FS.h"
#include "Log.h"
#include "Config.h"
#include "Tunnel.h"
#include "TransitTunnel.h"
#include "Transports.h"
#include "NetDb.h"
#include "HTTP.h"
#include "LeaseSet.h"
#include "Destination.h"
#include "RouterContext.h"
#include "ClientContext.h"
#include "HTTPServer.h"
#include "Daemon.h"
#include "util.h"

// For image and info
#include "version.h"

namespace i2p {
namespace http {
	const char *itoopieFavicon =
		"data:image/png;base64,"
		"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv"
		"8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4wOGVynO"
		"EAAAIzSURBVDhPjZNdSFNhGMf3nm3n7OzMs+8JtfJGzdlgoPtoWBrkqc1OsLTMKEY3eZOQbbS6aBVYO"
		"oO8CKSLXEulQtZNahAM9Cq6lS533UUaeDEEKcN/79x7kbQT/eDhfPB7/u/7Poej08JqtXoEQbhoMpmG"
		"ZFn2stf/h8nEZ4aHue1SiWBlhSCV4n41NBifBINBjina8DyfzOUIVlcJtrYINjcJ3rw1oFAg4HnjHaZ"
		"p4/Ppv8zPH0G5XKZNPZibO4lKpYJ8vgOqqv+uKMq/d9Hfz/0sFr3w+/3IZt2YnbWhszOAxUUv0mkCs9"
		"ncyNT6hEL6dYBgY4Ngd5eger+zU7sODHA/mpubzUytj9FofLa0VGv4s9bWCCTJUGSaNvSzXT3stuHDM"
		"rc3xEqF4N2CERciURyyHfgqSZKPqfuxUMyC+OKcL4YHyl28nDFAPdqDZMcQ7tPnSfURUt0jMBgMH1nL"
		"fkRRDPvcLds3otfhbRTwasaE8b6He43VSrT3QW3tBT3iPdbyN3T7Ibsor988H8OxtiaMx2sB1aBbCRW"
		"R1hbQhbqYXh+6QkaJn8DZyzF09x6HeiaOTC6NK9cSsFqkb3aH3cLU+tCAx9l8FoXPBUy9n8LgyCCmS9"
		"MYez0Gm9P2iWna0GOcDp8KY2JhAsnbSQS6Ahh9OgrlklINeM40bWhAkBd4SLIEh8cBURLhOeiBIArVA"
		"U4yTRvJItk5PRehQVFaYfpbt9PBtTmdziaXyyUzjaHT/QZBQuKHAA0UxAAAAABJRU5ErkJggg==";

	const char *cssStyles =
		"<style>\r\n"
		"  body { font: 100%/1.5em sans-serif; margin: 0; padding: 1.5em; background: #FAFAFA; color: #103456; }\r\n"
		"  a { text-decoration: none; color: #894C84; }\r\n"
		"  a:hover { color: #FAFAFA; background: #894C84; }\r\n"
		"  .header { font-size: 2.5em; text-align: center; margin: 1.5em 0; color: #894C84; }\r\n"
		"  .wrapper { margin: 0 auto; padding: 1em; max-width: 60em; }\r\n"
		"  .left  { float: left; position: absolute; }\r\n"
		"  .right { float: left; font-size: 1em; margin-left: 13em; max-width: 46em; overflow: auto; }\r\n"
		"  .tunnel.established { color: #56B734; }\r\n"
		"  .tunnel.expiring    { color: #D3AE3F; }\r\n"
		"  .tunnel.failed      { color: #D33F3F; }\r\n"
		"  .tunnel.another     { color: #434343; }\r\n"
		"  caption { font-size: 1.5em; text-align: center; color: #894C84; }\r\n"
		"  table { width: 100%; border-collapse: collapse; text-align: center; }\r\n"
		"</style>\r\n";

	const char HTTP_PAGE_TUNNELS[] = "tunnels";
	const char HTTP_PAGE_TRANSIT_TUNNELS[] = "transit_tunnels";
	const char HTTP_PAGE_TRANSPORTS[] = "transports";	
	const char HTTP_PAGE_LOCAL_DESTINATIONS[] = "local_destinations";
	const char HTTP_PAGE_LOCAL_DESTINATION[] = "local_destination";
	const char HTTP_PAGE_SAM_SESSIONS[] = "sam_sessions";
	const char HTTP_PAGE_SAM_SESSION[] = "sam_session";
	const char HTTP_PAGE_I2P_TUNNELS[] = "i2p_tunnels";
	const char HTTP_PAGE_JUMPSERVICES[] = "jumpservices";
	const char HTTP_PAGE_COMMANDS[] = "commands";
	const char HTTP_COMMAND_START_ACCEPTING_TUNNELS[] = "start_accepting_tunnels";	
	const char HTTP_COMMAND_STOP_ACCEPTING_TUNNELS[] = "stop_accepting_tunnels";	
	const char HTTP_COMMAND_SHUTDOWN_START[] = "shutdown_start";
	const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel";
	const char HTTP_COMMAND_SHUTDOWN_NOW[]   = "terminate";
	const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test";
	const char HTTP_COMMAND_RELOAD_CONFIG[] = "reload_config";	
	const char HTTP_PARAM_BASE32_ADDRESS[] = "b32";
	const char HTTP_PARAM_SAM_SESSION_ID[] = "id";
	const char HTTP_PARAM_ADDRESS[] = "address";

	std::map<std::string, std::string> jumpservices = {
		{ "inr.i2p",    "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/search/?q=" },
		{ "stats.i2p",  "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" },
	};

	void ShowUptime (std::stringstream& s, int seconds) {
		int num;

		if ((num = seconds / 86400) > 0) {
			s << num << " days, ";
			seconds -= num * 86400;
		}
		if ((num = seconds / 3600) > 0) {
			s << num << " hours, ";
			seconds -= num * 3600;
		}
		if ((num = seconds / 60) > 0) {
			s << num << " min, ";
			seconds -= num * 60;
		}
		s << seconds << " seconds";
	}

	void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, int bytes)
	{
		std::string state;
		switch (eState) {
			case i2p::tunnel::eTunnelStateBuildReplyReceived :
			case i2p::tunnel::eTunnelStatePending     : state = "building"; break;
			case i2p::tunnel::eTunnelStateBuildFailed :
			case i2p::tunnel::eTunnelStateTestFailed  :
			case i2p::tunnel::eTunnelStateFailed      : state = "failed";   break;
			case i2p::tunnel::eTunnelStateExpiring    : state = "expiring"; break;
			case i2p::tunnel::eTunnelStateEstablished : state = "established"; break;
			default: state = "unknown"; break;
		}
		s << "<span class=\"tunnel " << state << "\"> " << state << "</span>, ";
		s << " " << (int) (bytes / 1024) << "&nbsp;KiB<br>\r\n";
	}

	void ShowPageHead (std::stringstream& s)
	{
		s <<
			"<!DOCTYPE html>\r\n"
			"<html lang=\"en\">\r\n" /* TODO: Add support for locale */
			"  <head>\r\n"
			"  <meta charset=\"UTF-8\">\r\n" /* TODO: Find something to parse html/template system. This is horrible. */
			"  <link rel=\"shortcut icon\" href=\"" << itoopieFavicon << "\">\r\n"
			"  <title>Purple I2P " VERSION " Webconsole</title>\r\n"
			<< cssStyles <<
			"</head>\r\n";
		s <<
			"<body>\r\n"
			"<div class=header><b>i2pd</b> webconsole</div>\r\n"
			"<div class=wrapper>\r\n"
			"<div class=left>\r\n"
			"  <a href=\"/\">Main page</a><br>\r\n<br>\r\n"
			"  <a href=\"/?page=" << HTTP_PAGE_COMMANDS << "\">Router commands</a><br>\r\n"
			"  <a href=\"/?page=" << HTTP_PAGE_LOCAL_DESTINATIONS << "\">Local destinations</a><br>\r\n"
			"  <a href=\"/?page=" << HTTP_PAGE_TUNNELS << "\">Tunnels</a><br>\r\n"
			"  <a href=\"/?page=" << HTTP_PAGE_TRANSIT_TUNNELS << "\">Transit tunnels</a><br>\r\n"
			"  <a href=\"/?page=" << HTTP_PAGE_TRANSPORTS << "\">Transports</a><br>\r\n"
			"  <a href=\"/?page=" << HTTP_PAGE_I2P_TUNNELS << "\">I2P tunnels</a><br>\r\n"
			"  <a href=\"/?page=" << HTTP_PAGE_JUMPSERVICES << "\">Jump services</a><br>\r\n"
			"  <a href=\"/?page=" << HTTP_PAGE_SAM_SESSIONS << "\">SAM sessions</a><br>\r\n"
			"</div>\r\n"
			"<div class=right>";
	}

	void ShowPageTail (std::stringstream& s)
	{
		s <<
			"</div></div>\r\n"
			"</body>\r\n"
			"</html>\r\n";
	}

	void ShowError(std::stringstream& s, const std::string& string)
	{
		s << "<b>ERROR:</b>&nbsp;" << string << "<br>\r\n";
	}

	void ShowStatus (std::stringstream& s)
	{
		s << "<b>Uptime:</b> ";
		ShowUptime(s, i2p::context.GetUptime ());
		s << "<br>\r\n";
		s << "<b>Status:</b> ";
		switch (i2p::context.GetStatus ())
		{
			case eRouterStatusOK: s << "OK"; break;
			case eRouterStatusTesting: s << "Testing"; break;
			case eRouterStatusFirewalled: s << "Firewalled"; break; 
			default: s << "Unknown";
		} 
		s << "<br>\r\n";
		auto family = i2p::context.GetFamily ();
		if (family.length () > 0)
			s << "<b>Family:</b> " << family << "<br>\r\n";
		s << "<b>Tunnel creation success rate:</b> " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%<br>\r\n";
		s << "<b>Received:</b> ";
		s << std::fixed << std::setprecision(2);
		auto numKBytesReceived = (double) i2p::transport::transports.GetTotalReceivedBytes () / 1024;
		if (numKBytesReceived < 1024)
			s << numKBytesReceived << " KiB";
		else if (numKBytesReceived < 1024 * 1024)
			s << numKBytesReceived / 1024 << " MiB";
		else
			s << numKBytesReceived / 1024 / 1024 << " GiB";
		s << " (" << (double) i2p::transport::transports.GetInBandwidth () / 1024 << " KiB/s)<br>\r\n";
		s << "<b>Sent:</b> ";
		auto numKBytesSent = (double) i2p::transport::transports.GetTotalSentBytes () / 1024;
		if (numKBytesSent < 1024)
			s << numKBytesSent << " KiB";
		else if (numKBytesSent < 1024 * 1024)
			s << numKBytesSent / 1024 << " MiB";
		else
			s << numKBytesSent / 1024 / 1024 << " GiB";
		s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " KiB/s)<br>\r\n";
		s << "<b>Data path:</b> " << i2p::fs::GetDataDir() << "<br>\r\n<br>\r\n";
		s << "<b>Our external address:</b>" << "<br>\r\n" ;
		for (auto address : i2p::context.GetRouterInfo().GetAddresses())
		{
			switch (address->transportStyle)
			{
				case i2p::data::RouterInfo::eTransportNTCP:
					if (address->host.is_v6 ())
						s << "NTCP6&nbsp;&nbsp;";
					else
						s << "NTCP&nbsp;&nbsp;";
				break;
				case i2p::data::RouterInfo::eTransportSSU:
					if (address->host.is_v6 ())
						s << "SSU6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
					else
						s << "SSU&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
				break;
				default:
					s << "Unknown&nbsp;&nbsp;";
			}
			s << address->host.to_string() << ":" << address->port << "<br>\r\n";
		}
		s << "<br>\r\n<b>Routers:</b> " << i2p::data::netdb.GetNumRouters () << " ";
		s << "<b>Floodfills:</b> " << i2p::data::netdb.GetNumFloodfills () << " ";
		s << "<b>LeaseSets:</b> " << i2p::data::netdb.GetNumLeaseSets () << "<br>\r\n";

		size_t clientTunnelCount = i2p::tunnel::tunnels.CountOutboundTunnels();
		clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels();
		size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels();
		
        s << "<b>Client Tunnels:</b> " << std::to_string(clientTunnelCount) << " ";
        s << "<b>Transit Tunnels:</b> " << std::to_string(transitTunnelCount) << "<br>\r\n";
	}

	void ShowJumpServices (std::stringstream& s, const std::string& address)
	{
		s << "<form method=\"get\" action=\"/\">";
		s << "<input type=\"hidden\" name=\"page\" value=\"jumpservices\">";
		s << "<input type=\"text\" name=\"address\" value=\"" << address << "\">";
		s << "<input type=\"submit\" value=\"Update\">";
		s << "</form><br>\r\n";
		s << "<b>Jump services for " << address << "</b>\r\n<ul>\r\n";
		for (auto & js : jumpservices) {
			s << "  <li><a href=\"" << js.second << address << "\">" << js.first << "</a></li>\r\n";
		}
		s << "</ul>\r\n";
	}

	void ShowLocalDestinations (std::stringstream& s)
	{
		s << "<b>Local Destinations:</b><br>\r\n<br>\r\n";
		for (auto& it: i2p::client::context.GetDestinations ())
		{
			auto ident = it.second->GetIdentHash ();; 
			s << "<a href=\"/?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">"; 
			s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "</a><br>\r\n" << std::endl;
		}
	}

	void  ShowLocalDestination (std::stringstream& s, const std::string& b32)
	{
		s << "<b>Local Destination:</b><br>\r\n<br>\r\n";
		i2p::data::IdentHash ident;
		ident.FromBase32 (b32);
		auto dest = i2p::client::context.FindLocalDestination (ident);
		if (dest)
		{
			s << "<b>Base64:</b><br>\r\n<textarea readonly=\"readonly\" cols=\"64\" rows=\"11\" wrap=\"on\">";
			s << dest->GetIdentity ()->ToBase64 () << "</textarea><br>\r\n<br>\r\n";
			s << "<b>LeaseSets:</b> <i>" << dest->GetNumRemoteLeaseSets () << "</i><br>\r\n";
			auto pool = dest->GetTunnelPool ();
			if (pool)
			{
				s << "<b>Inbound tunnels:</b><br>\r\n";
				for (auto & it : pool->GetInboundTunnels ()) {
					it->Print(s);
					ShowTunnelDetails(s, it->GetState (), it->GetNumReceivedBytes ());
				}
				s << "<br>\r\n";
				s << "<b>Outbound tunnels:</b><br>\r\n";
				for (auto & it : pool->GetOutboundTunnels ()) {
					it->Print(s);
					ShowTunnelDetails(s, it->GetState (), it->GetNumSentBytes ());
				}
			}	
			s << "<br>\r\n";
			s << "<b>Tags</b><br>Incoming: " << dest->GetNumIncomingTags () << "<br>Outgoing:<br>" << std::endl;
			for (auto it: dest->GetSessions ())
			{
				s << i2p::client::context.GetAddressBook ().ToAddress(it.first) << " ";
				s << it.second->GetNumOutgoingTags () << "<br>" << std::endl;
			}	
			s << "<br>" << std::endl;
			// s << "<br>\r\n<b>Streams:</b><br>\r\n";
			// for (auto it: dest->GetStreamingDestination ()->GetStreams ())
			// {	
				// s << it.first << "->" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetRemoteIdentity ()) << " ";
				// s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
				// s << " [out:" << it.second->GetSendQueueSize () << "][in:" << it.second->GetReceiveQueueSize () << "]";
				// s << "[buf:" << it.second->GetSendBufferSize () << "]";
				// s << "[RTT:" << it.second->GetRTT () << "]";
				// s << "[Window:" << it.second->GetWindowSize () << "]";
				// s << "[Status:" << (int)it.second->GetStatus () << "]"; 
				// s << "<br>\r\n"<< std::endl; 
			// }	
			s << "<br>\r\n<table><caption>Streams</caption><tr>";
			s << "<th>StreamID</th>";
			s << "<th>Destination</th>";
			s << "<th>Sent</th>";
			s << "<th>Received</th>";
			s << "<th>Out</th>";
			s << "<th>In</th>";
			s << "<th>Buf</th>";
			s << "<th>RTT</th>";
			s << "<th>Window</th>";
			s << "<th>Status</th>";
			s << "</tr>";

			for (auto it: dest->GetAllStreams ())
			{	
				s << "<tr>";
				s << "<td>" << it->GetSendStreamID () << "</td>";
				s << "<td>" << i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ()) << "</td>";
				s << "<td>" << it->GetNumSentBytes () << "</td>";
				s << "<td>" << it->GetNumReceivedBytes () << "</td>";
				s << "<td>" << it->GetSendQueueSize () << "</td>";
				s << "<td>" << it->GetReceiveQueueSize () << "</td>";
				s << "<td>" << it->GetSendBufferSize () << "</td>";
				s << "<td>" << it->GetRTT () << "</td>";
				s << "<td>" << it->GetWindowSize () << "</td>";
				s << "<td>" << (int)it->GetStatus () << "</td>";
				s << "</tr><br>\r\n" << std::endl; 
			}
		}	
	}

	void ShowTunnels (std::stringstream& s)
	{
		s << "<b>Queue size:</b> " << i2p::tunnel::tunnels.GetQueueSize () << "<br>\r\n";

		s << "<b>Inbound tunnels:</b><br>\r\n";
		for (auto & it : i2p::tunnel::tunnels.GetInboundTunnels ()) {
			it->Print(s);
			ShowTunnelDetails(s, it->GetState (), it->GetNumReceivedBytes ());
		}
		s << "<br>\r\n";
		s << "<b>Outbound tunnels:</b><br>\r\n";
		for (auto & it : i2p::tunnel::tunnels.GetOutboundTunnels ()) {
			it->Print(s);
			ShowTunnelDetails(s, it->GetState (), it->GetNumSentBytes ());
		}
		s << "<br>\r\n";
	}	

	void ShowCommands (std::stringstream& s)
	{
		/* commands */
		s << "<b>Router Commands</b><br>\r\n";
		s << "  <a href=\"/?cmd=" << HTTP_COMMAND_RUN_PEER_TEST << "\">Run peer test</a><br>\r\n";
		//s << "  <a href=\"/?cmd=" << HTTP_COMMAND_RELOAD_CONFIG << "\">Reload config</a><br>\r\n";
		if (i2p::context.AcceptsTunnels ())
			s << "  <a href=\"/?cmd=" << HTTP_COMMAND_STOP_ACCEPTING_TUNNELS << "\">Stop accepting tunnels</a><br>\r\n";
		else	
			s << "  <a href=\"/?cmd=" << HTTP_COMMAND_START_ACCEPTING_TUNNELS << "\">Start accepting tunnels</a><br>\r\n";
#if (!defined(WIN32) && !defined(QT_GUI_LIB)) 
		if (Daemon.gracefullShutdownInterval) {
			s << "  <a href=\"/?cmd=" << HTTP_COMMAND_SHUTDOWN_CANCEL << "\">Cancel gracefull shutdown (";
			s << Daemon.gracefullShutdownInterval;
			s << " seconds remains)</a><br>\r\n";
		} else {
			s << "  <a href=\"/?cmd=" << HTTP_COMMAND_SHUTDOWN_START << "\">Start gracefull shutdown</a><br>\r\n";
		}
		s << "  <a href=\"/?cmd=" << HTTP_COMMAND_SHUTDOWN_NOW << "\">Force shutdown</a><br>\r\n";
#endif
	}

	void ShowTransitTunnels (std::stringstream& s)
	{
		s << "<b>Transit tunnels:</b><br>\r\n<br>\r\n";
		for (auto it: i2p::tunnel::tunnels.GetTransitTunnels ())
		{
			if (std::dynamic_pointer_cast<i2p::tunnel::TransitTunnelGateway>(it))
				s << it->GetTunnelID () << " ⇒ ";
			else if (std::dynamic_pointer_cast<i2p::tunnel::TransitTunnelEndpoint>(it))
				s << " ⇒ " << it->GetTunnelID ();
			else
				s << " ⇒ " << it->GetTunnelID () << " ⇒ ";
			s << " " << it->GetNumTransmittedBytes () << "<br>\r\n";
		}
	}

	void ShowTransports (std::stringstream& s)
	{
		s << "<b>Transports:</b><br>\r\n<br>\r\n";
		auto ntcpServer = i2p::transport::transports.GetNTCPServer (); 
		if (ntcpServer)
		{	
			s << "<b>NTCP</b><br>\r\n";
			for (auto it: ntcpServer->GetNTCPSessions ())
			{
				if (it.second && it.second->IsEstablished ())
				{
					// incoming connection doesn't have remote RI
					if (it.second->IsOutgoing ()) s << " ⇒ ";
					s << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) <<  ": "
						<< it.second->GetSocket ().remote_endpoint().address ().to_string ();
					if (!it.second->IsOutgoing ()) s << " ⇒ ";
					s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
					s << "<br>\r\n" << std::endl;
				}
			}
		}	
		auto ssuServer = i2p::transport::transports.GetSSUServer ();
		if (ssuServer)
		{
			s << "<br>\r\n<b>SSU</b><br>\r\n";
			for (auto it: ssuServer->GetSessions ())
			{
				auto endpoint = it.second->GetRemoteEndpoint ();
				if (it.second->IsOutgoing ()) s << " ⇒ ";
				s << endpoint.address ().to_string () << ":" << endpoint.port ();
				if (!it.second->IsOutgoing ()) s << " ⇒ ";
				s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
				if (it.second->GetRelayTag ())
					s << " [itag:" << it.second->GetRelayTag () << "]";
				s << "<br>\r\n" << std::endl;
			}
			s << "<br>\r\n<b>SSU6</b><br>\r\n";
			for (auto it: ssuServer->GetSessionsV6 ())
			{
				auto endpoint = it.second->GetRemoteEndpoint ();
				if (it.second->IsOutgoing ()) s << " ⇒ ";
				s << endpoint.address ().to_string () << ":" << endpoint.port ();
				if (!it.second->IsOutgoing ()) s << " ⇒ ";
				s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
				s << "<br>\r\n" << std::endl;
			}
		}
	}
	
	void ShowSAMSessions (std::stringstream& s)
	{
		auto sam = i2p::client::context.GetSAMBridge ();
		if (!sam) {
			ShowError(s, "SAM disabled");
			return;
		}
		s << "<b>SAM Sessions:</b><br>\r\n<br>\r\n";
		for (auto& it: sam->GetSessions ())
		{
			s << "<a href=\"/?page=" << HTTP_PAGE_SAM_SESSION << "&sam_id=" << it.first << "\">";
			s << it.first << "</a><br>\r\n" << std::endl;
		}	
	}	

	void ShowSAMSession (std::stringstream& s, const std::string& id)
	{
		s << "<b>SAM Session:</b><br>\r\n<br>\r\n";
		auto sam = i2p::client::context.GetSAMBridge ();
		if (!sam) {
			ShowError(s, "SAM disabled");
			return;
		}
		auto session = sam->FindSession (id);
		if (!session) {
			ShowError(s, "SAM session not found");
			return;
		}
		auto& ident = session->localDestination->GetIdentHash();
		s << "<a href=\"/?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
		s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "</a><br>\r\n";
		s << "<br>\r\n";
		s << "<b>Streams:</b><br>\r\n";
		for (auto it: session->ListSockets())
		{
			switch (it->GetSocketType ())
			{
				case i2p::client::eSAMSocketTypeSession  : s << "session";  break;
				case i2p::client::eSAMSocketTypeStream   : s << "stream";   break;
				case i2p::client::eSAMSocketTypeAcceptor : s << "acceptor"; break;
				default: s << "unknown"; break;
			}
			s << " [" << it->GetSocket ().remote_endpoint() << "]";
			s << "<br>\r\n";
		}	
	}	

	void ShowI2PTunnels (std::stringstream& s)
	{
		s << "<b>Client Tunnels:</b><br>\r\n<br>\r\n";
		for (auto& it: i2p::client::context.GetClientTunnels ())
		{
			auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
			s << "<a href=\"/?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">"; 
			s << it.second->GetName () << "</a> ⇐ ";			
			s << i2p::client::context.GetAddressBook ().ToAddress(ident);
			s << "<br>\r\n"<< std::endl;
		}	
		s << "<br>\r\n<b>Server Tunnels:</b><br>\r\n<br>\r\n";
		for (auto& it: i2p::client::context.GetServerTunnels ())
		{
			auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
			s << "<a href=\"/?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">"; 
			s << it.second->GetName () << "</a> ⇒ ";
			s << i2p::client::context.GetAddressBook ().ToAddress(ident);
			s << ":" << it.second->GetLocalPort ();
			s << "</a><br>\r\n"<< std::endl;
		}	
	}	

	HTTPConnection::HTTPConnection (std::shared_ptr<boost::asio::ip::tcp::socket> socket):
				m_Socket (socket), m_Timer (socket->get_io_service ()), m_BufferLen (0)
	{
		/* cache options */
		i2p::config::GetOption("http.auth", needAuth);
		i2p::config::GetOption("http.user", user);
		i2p::config::GetOption("http.pass", pass);
	}

	void HTTPConnection::Receive ()
	{
		m_Socket->async_read_some (boost::asio::buffer (m_Buffer, HTTP_CONNECTION_BUFFER_SIZE),
			 std::bind(&HTTPConnection::HandleReceive, shared_from_this (),
				 std::placeholders::_1, std::placeholders::_2));
	}

	void HTTPConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred)
	{
		if (ecode) {
			if (ecode != boost::asio::error::operation_aborted)
				Terminate (ecode);
			return;
		}
		m_Buffer[bytes_transferred] = '\0';
		m_BufferLen = bytes_transferred;
		RunRequest();
		Receive ();
	}

	void HTTPConnection::RunRequest ()
	{
		HTTPReq request;
		int ret = request.parse(m_Buffer);
		if (ret < 0) {
			m_Buffer[0] = '\0';
			m_BufferLen = 0;
			return; /* error */
		}
		if (ret == 0)
			return; /* need more data */

		HandleRequest (request);
	}

	void HTTPConnection::Terminate (const boost::system::error_code& ecode)
	{
		if (ecode == boost::asio::error::operation_aborted)
			return;
		boost::system::error_code ignored_ec;
		m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec);
		m_Socket->close ();
	}

	bool HTTPConnection::CheckAuth (const HTTPReq & req) {
		/* method #1: http://user:pass@127.0.0.1:7070/ */
		if (req.uri.find('@') != std::string::npos) {
			URL url;
			if (url.parse(req.uri) && url.user == user && url.pass == pass)
				return true;
		}
		/* method #2: 'Authorization' header sent */
		if (req.headers.count("Authorization") > 0) {
			std::string provided = req.headers.find("Authorization")->second;
			std::string expected = user + ":" + pass;
			char b64_creds[64];
			std::size_t len = 0;
			len = i2p::data::ByteStreamToBase64((unsigned char *)expected.c_str(), expected.length(), b64_creds, sizeof(b64_creds));
			b64_creds[len] = '\0';
			expected = "Basic ";
			expected += b64_creds;
			if (provided == expected)
				return true;
		}

		LogPrint(eLogWarning, "HTTPServer: auth failure from ", m_Socket->remote_endpoint().address ());
		return false;
	}

	void HTTPConnection::HandleRequest (const HTTPReq & req)
	{
		std::stringstream s;
		std::string content;
		HTTPRes res;

		LogPrint(eLogDebug, "HTTPServer: request: ", req.uri);

		if (needAuth && !CheckAuth(req)) {
			res.code = 401;
			res.add_header("WWW-Authenticate", "Basic realm=\"WebAdmin\"");
			SendReply(res, content);
			return;
		}

		// Html5 head start
		ShowPageHead (s);
		if (req.uri.find("page=") != std::string::npos) {
			HandlePage (req, res, s);
		} else if (req.uri.find("cmd=") != std::string::npos) {
			HandleCommand (req, res, s);
		} else {
			ShowStatus (s);
			res.add_header("Refresh", "10");
		}
		ShowPageTail (s);

		res.code = 200;
		content = s.str ();
		SendReply (res, content);
	}

	void HTTPConnection::HandlePage (const HTTPReq& req, HTTPRes& res, std::stringstream& s)
  {
		std::map<std::string, std::string> params;
		std::string page("");
		URL url;

		url.parse(req.uri);
		url.parse_query(params);
		page = params["page"];

		if (page == HTTP_PAGE_TRANSPORTS)
			ShowTransports (s);
		else if (page == HTTP_PAGE_TUNNELS)
			ShowTunnels (s);
		else if (page == HTTP_PAGE_COMMANDS)
			ShowCommands (s);
		else if (page == HTTP_PAGE_JUMPSERVICES)
			ShowJumpServices (s, params["address"]);
		else if (page == HTTP_PAGE_TRANSIT_TUNNELS)
			ShowTransitTunnels (s);
		else if (page == HTTP_PAGE_LOCAL_DESTINATIONS)
			ShowLocalDestinations (s);	
		else if (page == HTTP_PAGE_LOCAL_DESTINATION)
			ShowLocalDestination (s, params["b32"]);
		else if (page == HTTP_PAGE_SAM_SESSIONS)
			ShowSAMSessions (s);
		else if (page == HTTP_PAGE_SAM_SESSION)
			ShowSAMSession (s, params["sam_id"]);
		else if (page == HTTP_PAGE_I2P_TUNNELS)
			ShowI2PTunnels (s);
		else {
			res.code = 400;
			ShowError(s, "Unknown page: " + page);
			return;
		}
  }

	void HTTPConnection::HandleCommand (const HTTPReq& req, HTTPRes& res, std::stringstream& s)
	{
		std::map<std::string, std::string> params;
		std::string cmd("");
		URL url;

		url.parse(req.uri);
		url.parse_query(params);
		cmd = params["cmd"];

		if (cmd == HTTP_COMMAND_RUN_PEER_TEST)
			i2p::transport::transports.PeerTest ();
		else if (cmd == HTTP_COMMAND_RELOAD_CONFIG)
			i2p::client::context.ReloadConfig ();
		else if (cmd == HTTP_COMMAND_START_ACCEPTING_TUNNELS)
			i2p::context.SetAcceptsTunnels (true);
		else if (cmd == HTTP_COMMAND_STOP_ACCEPTING_TUNNELS)
			i2p::context.SetAcceptsTunnels (false);
		else if (cmd == HTTP_COMMAND_SHUTDOWN_START) {
			i2p::context.SetAcceptsTunnels (false);
#if (!defined(WIN32) && !defined(QT_GUI_LIB)) 
			Daemon.gracefullShutdownInterval = 10*60;
#endif
		} else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) {
			i2p::context.SetAcceptsTunnels (true);
#if (!defined(WIN32) && !defined(QT_GUI_LIB)) 
			Daemon.gracefullShutdownInterval = 0;
#endif
		} else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) {
			Daemon.running = false;
		} else {
			res.code = 400;
			ShowError(s, "Unknown command: " + cmd);
			return;
		}
		s << "<b>SUCCESS</b>:&nbsp;Command accepted<br><br>\r\n";
		s << "<a href=\"/?page=commands\">Back to commands list</a><br>\r\n";
		s << "<p>You will be redirected in 5 seconds</b>";
		res.add_header("Refresh", "5; url=/?page=commands");
	}	

	void HTTPConnection::SendReply (HTTPRes& reply, std::string& content)
	{
		reply.add_header("Content-Type", "text/html");
		reply.body = content;

		std::string res = reply.to_string();
		boost::asio::async_write (*m_Socket, boost::asio::buffer(res),
			std::bind (&HTTPConnection::Terminate, shared_from_this (), std::placeholders::_1));
	}

	HTTPServer::HTTPServer (const std::string& address, int port):
		m_Thread (nullptr), m_Work (m_Service),
		m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port))
	{
	}

	HTTPServer::~HTTPServer ()
	{
		Stop ();
	}

	void HTTPServer::Start ()
	{
		bool needAuth;    i2p::config::GetOption("http.auth", needAuth);
		std::string user; i2p::config::GetOption("http.user", user);
		std::string pass; i2p::config::GetOption("http.pass", pass);
		/* generate pass if needed */
		if (needAuth && pass == "") {
			char alnum[] = "0123456789"
			  "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
			  "abcdefghijklmnopqrstuvwxyz";
			pass.resize(16);
			for (size_t i = 0; i < pass.size(); i++) {
				pass[i] = alnum[rand() % (sizeof(alnum) - 1)];
			}
			i2p::config::SetOption("http.pass", pass);
			LogPrint(eLogInfo, "HTTPServer: password set to ", pass);
		}
		m_Thread = std::unique_ptr<std::thread>(new std::thread (std::bind (&HTTPServer::Run, this)));
		m_Acceptor.listen ();
		Accept ();
	}

	void HTTPServer::Stop ()
	{
		m_Acceptor.close();
		m_Service.stop ();
		if (m_Thread) {
			m_Thread->join ();
			m_Thread = nullptr;
		}
	}

	void HTTPServer::Run ()
	{
		m_Service.run ();
	}

	void HTTPServer::Accept ()
	{
		auto newSocket = std::make_shared<boost::asio::ip::tcp::socket> (m_Service);
		m_Acceptor.async_accept (*newSocket, boost::bind (&HTTPServer::HandleAccept, this,
			boost::asio::placeholders::error, newSocket));
	}

	void HTTPServer::HandleAccept(const boost::system::error_code& ecode, 
		std::shared_ptr<boost::asio::ip::tcp::socket> newSocket)
	{
		if (ecode)
			return;
		CreateConnection(newSocket);
		Accept ();
	}

	void HTTPServer::CreateConnection(std::shared_ptr<boost::asio::ip::tcp::socket> newSocket)
	{
		auto conn = std::make_shared<HTTPConnection> (newSocket);
		conn->Receive ();
	}
} // http
} // i2p
