//
// connection.cpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#include "connection.hpp"
#include <utility>
#include <vector>
#include "connection_manager.hpp"
#include "request_handler.hpp"


#include <iostream>
#include <boost/lexical_cast.hpp>
#include <thread>
#include <array>
#include <iomanip>

#include "boost/algorithm/string/split.hpp"

#include "boost/algorithm/string/classification.hpp"
#include "boost/algorithm/string/predicate.hpp"
class ConnectRequestRecord
{
public:
	
	std::string address;

	uint16_t port;
};


namespace http {
namespace server {

connection::connection(std::shared_ptr<ssl_socket> newSocketPtr,
    connection_manager& manager, request_handler& handler, boost::asio::io_context& io_context)
  : socketPtr(newSocketPtr),
    connection_manager_(manager),
    request_handler_(handler),
	io_context_(io_context),
	outsideSocket_(io_context_)
{
}

void connection::start()
{
	doHandshake();
}

void connection::stop()
{
  socketPtr->lowest_layer().close();
  //socketPtr.reset();
}

void connection::doHandshake()
{
	std::cout << "ProxySession::asyncHandshake" << std::endl;
	auto self(shared_from_this());

	socketPtr->async_handshake(boost::asio::ssl::stream_base::server,
		[this, self](boost::system::error_code ec) {

		std::cout << "ProxySession::asyncHandshake inner" << std::endl;

		if (!ec)
		{
			std::cout << "ProxySession::asyncHandshake inner2" << std::endl;
			do_read();
		}
		else
		{
			if (ec != boost::asio::error::operation_aborted)
			{
				connection_manager_.stop(shared_from_this());
			}
		}

	});
}

void connection::do_read()
{
  auto self(shared_from_this());
  socketPtr->async_read_some(boost::asio::buffer(buffer_),
      [this, self](boost::system::error_code ec, std::size_t bytes_transferred)
      {
        if (!ec)
        {
          request_parser::result_type result;
          std::tie(result, std::ignore) = request_parser_.parse(
              request_, buffer_.data(), buffer_.data() + bytes_transferred);

		  std::string addressAndPort = request_.uri;

		  using namespace boost::algorithm;
		  using namespace boost;

		  for (auto& header : request_.headers)
		  {
			  if (header.name == "Host" && header.value == "browser.fishrungames.com")
			  {
				  using namespace boost::algorithm;
				  using namespace boost;

				  if (istarts_with(addressAndPort, "/wololo?id="))
				  {
					  addressAndPort.erase(addressAndPort.begin(), addressAndPort.begin() + 11);

					  std::vector<std::string> splitVal;

					  split(splitVal, addressAndPort, is_any_of(":"), token_compress_on);

					  if (splitVal.size() >= 2)
					  {
						  do_try_connect_remote(splitVal[0], splitVal[1]);
						  return;
					  }

				  }

				  break;
			  }
		  }


		  

          if (result == request_parser::good)
          {
			  
            
			

			request_handler_.handle_request(request_, reply_);


            do_write();
			
          }
          else if (result == request_parser::bad)
          {
            reply_ = reply::stock_reply(reply::bad_request);
            do_write();
          }
          else
          {
            do_read();
          }
        }
        else if (ec != boost::asio::error::operation_aborted)
        {
          connection_manager_.stop(shared_from_this());
        }
      });
}

void connection::do_write()
{
  auto self(shared_from_this());
  boost::asio::async_write((*socketPtr), reply_.to_buffers(),
      [this, self](boost::system::error_code ec, std::size_t)
      {
        if (!ec)
        {

          // Initiate graceful connection closure.
          boost::system::error_code ignored_ec;
		  socketPtr->shutdown(ignored_ec);
        }

        if (ec != boost::asio::error::operation_aborted)
        {
          connection_manager_.stop(shared_from_this());
        }
      });
}


static const std::array<char, 5> okReply = {'o', 'k', 'l', 'o', 'l'};

void connection::do_try_connect_remote(std::string host, std::string port)
{
	auto self(shared_from_this());

	boost::asio::ip::tcp::resolver resolver(io_context_);
	boost::asio::ip::tcp::resolver::iterator endpointIterator = resolver.resolve({ host, port });


	boost::asio::async_connect(outsideSocket_, endpointIterator,
		[this, self](boost::system::error_code ec, boost::asio::ip::tcp::resolver::iterator)
	{
		if (!ec)
		{
			auto self(shared_from_this());
			boost::asio::async_write((*socketPtr), boost::asio::buffer(okReply),
				[this, self](boost::system::error_code ec, std::size_t)
			{
				if (!ec)
				{
					transferDataForward();
					transferDataBackward();
				
				}
				else
				{

					if (ec != boost::asio::error::operation_aborted)
					{
						connection_manager_.stop(shared_from_this());
					}

					outsideSocket_.close();

				}
			});

		}
		else
		{
			connection_manager_.stop(shared_from_this());

			outsideSocket_.close();
		}
	});
}

void connection::transferDataForward()
{
	auto self(shared_from_this());

	socketPtr->async_read_some(boost::asio::buffer(forwardBuffer),
		[this, self](boost::system::error_code ec, std::size_t length)
	{
		if (!ec)
		{

			std::shared_ptr<std::vector<unsigned char>> data = std::make_shared<std::vector<unsigned char>>();
			data->resize(length);

			std::copy(&forwardBuffer[0], &forwardBuffer[0] + length, &(*data)[0]);

			std::cout << "Forward Received " << length << " Bytes, sending\n";
			std::cout << boost::lexical_cast<std::string>(int(forwardBuffer[0])) << "\n";
			std::cout << boost::lexical_cast<std::string>(int(forwardBuffer[length-1])) << "\n";

			boost::asio::async_write(outsideSocket_,
				boost::asio::buffer(*data),
				[this, self, data](boost::system::error_code ec, std::size_t length)
			{

				if (!ec)
				{
					std::cout << "Forward Sent, now read again!\n";
					transferDataForward();
				}
				else
				{
					connection_manager_.stop(shared_from_this());
					outsideSocket_.close();
				}
			});
		}
		else if (ec == boost::asio::error::eof)
		{
			std::cout << "transferDataForward read end of file" << std::endl;
			if (length > 0)
			{
				std::cout << "but length is positive" << std::endl;
			}

			return;
		}
		else
		{
			if (ec != boost::asio::error::operation_aborted)
			{
				connection_manager_.stop(shared_from_this());
			}

			outsideSocket_.close();
		}
	});
}

void connection::transferDataBackward()
{
	auto self(shared_from_this());

	outsideSocket_.async_read_some(boost::asio::buffer(backwardBuffer),
		[this, self](boost::system::error_code ec, std::size_t length)
	{
		if (!ec)
		{

			std::shared_ptr<std::vector<unsigned char>> data = std::make_shared<std::vector<unsigned char>>();
			data->resize(length);

			std::copy(&backwardBuffer[0], &backwardBuffer[0] + length, &(*data)[0]);


			std::cout << "Backward Received " << length << " Bytes, sending\n";
			std::cout << boost::lexical_cast<std::string>(int(backwardBuffer[0])) << "\n";
			std::cout << boost::lexical_cast<std::string>(int(backwardBuffer[length - 1])) << "\n";


			boost::asio::async_write((*socketPtr),
				boost::asio::buffer(*data),
				[this, self, data](boost::system::error_code ec, std::size_t length)
			{

				if (!ec)
				{
					std::cout << "Backward Sent, now read again!\n";
					transferDataBackward();
				}
				else
				{
					if (ec != boost::asio::error::operation_aborted)
					{
						connection_manager_.stop(shared_from_this());
					}

					outsideSocket_.close();
				}
			});
		}
		else if (ec == boost::asio::error::eof)
		{
			std::cout << "transferDataBackward read end of file" << std::endl;
			if (length > 0)
			{
				std::cout << "but length is positive" << std::endl;
			}

			return;
		}
		else
		{
			connection_manager_.stop(shared_from_this());
			outsideSocket_.close();
		}
	});
}

} // namespace server
} // namespace http
