
#include "Client.hpp"

/*********************************************
 * COPLIENS
 *********************************************/

Client::Client()
	: status(0),
	header_complete(false),
	body_complete(false),
	request_complete(false),
	assigned_server(NULL),
	assigned_location(NULL),
	cgi_pipe_rfd(0),
	cgi_pid(0),
	_fd(0),
	_port(""),
	_ip(""),
	_lsocket(NULL)
{
	return;
}

Client::Client(int afd, listen_socket *lsocket, std::string aport, std::string aip)
	: status(0),
	header_complete(false),
	body_complete(false),
	request_complete(false),
	assigned_server(NULL),
	assigned_location(NULL),
	cgi_pipe_rfd(0),
	cgi_pid(0),
	_fd(afd),
	_port(aport),
	_ip(aip),
	_lsocket(lsocket)
{
	return;
}

Client::~Client() {
	return;
}

/* // WIP not sure fo what is more logic here
Client::Client( Client const & src )
	: status			( src.status ),
	header_complete		( src.header_complete ),
	assigned_server		( src.assigned_server ),
	assigned_location	( src.assigned_location ),
	_fd					( src._fd ),
	_port				( src._port ),
	_ip					( src._ip ),
	_lsocket			( src._lsocket )
{
	raw_request = src.raw_request;
	response = src.response;
//	buf = strdup(src.buf); // TODO: this doesn't work
	return;
}
 */

/* // WIP placeholder because of const values
Client &	Client::operator=( Client const & rhs )
{
	if ( this != &rhs )
	{
		// stuff
	}
	return *this;
}
 */

/*********************************************
 * PUBLIC MEMBER FUNCTIONS
 *********************************************/

/* 	HTTP Headers :
	https://www.iana.org/assignments/http-fields/http-fields.xhtml
	https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
	https://www.ibm.com/docs/en/cics-ts/5.3?topic=protocol-http-requests
	https://www.tutorialspoint.com/http/http_requests.htm
*/
void	Client::parse_request_headers(std::vector<ServerConfig> &servers)
{
	if (raw_request.find(CRLF CRLF) == NPOS)
		return ;
	header_complete = true;
	clear_request(); // not mandatory

	_parse_request_line();
	if (status)
		return;
	_parse_request_fields();

// DEBUG
// print_client("headers");

	if (status)
		return;
	assigned_server = _determine_process_server(this, servers);
	assigned_location = _determine_location(*assigned_server, _request.abs_path);
	_check_request_errors();
	if (status)
		return;
	_parse_port_hostname(get_rq_headers("Host")); // use getter for headers because it works case insensitive

// DEBUG
// std::cerr << get_rq_method_str() << " " << get_rq_target() << " " << get_rq_version() << "\n";

//	dont clear raw_request, we need it for future reparsing of body
//	see call of parse_request() in _read_request()
	// raw_request.clear(); 
}

void	Client::parse_request_body()
{
	std::cerr << "parse_request_body()\n";
	size_t	pos;

	pos = raw_request.find(CRLF CRLF);
	if (pos == NPOS)
	{
		std::cerr << "parse_request_body() bad call, header incomplete\n";
		// QUESTION from hugo : don't we change the status here ?
		// RESPONSE from luke : C'est vrai. Peut-être mettre un 500, c'etait plus du debug à la base.
		// C'est seulement si on appelle la fonction au mauvais endroit, avant d'avoir un header complet, que ça arrive.
		return;
	}

	if (!get_rq_headers("Transfer-Encoding").empty()
	&& get_rq_headers("Transfer-Encoding") == "chunked")
	{
		// Chunked decoding WIP. How to test this ? dont know how to send chunks with telnet.
		_parse_chunked_body(pos + CRLF_SIZE*2);
	}
	else if (raw_request.size() - pos >= std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10))
	{
	std::cerr << "Content-Length = " << std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10) << "\n";
		if (get_rq_headers("Content-Type").find("multipart/form-data") != NPOS)
			_parse_multipart_body(pos);
		else
			_request.body = raw_request.substr(pos + CRLF_SIZE*2);
		body_complete = true;
	}

	std::cerr << "Content-Length = " << std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10) << "\n";
	std::cerr << "raw_request.size() - pos = " << raw_request.size() - pos << "\n";
	_request.body = raw_request.substr(pos);
	std::cerr << "_request.body.size() = " << _request.body.size() << "\n";

///////////////
// Body checks
	if (_request.body.size() > assigned_server->client_body_limit)
		status = 413; // HTTP Client Errors
}

void	Client::_parse_chunked_body(size_t pos)
{
		size_t	chunk_size = 1;
		size_t	chunk_field_end = 0;
		char	*endptr = NULL;
		char	*endptr_copy = NULL;
		/* TODO: verify if last chunk in raw_request (to avoid multiples complete parsing)
			but how ? with "raw_request.rfind("0" CRLF CRLF)", there no confirmation
			that we have found the last last-chunk OR just some data */

		_request.body = raw_request.substr(pos);

		std::cerr << "______Chunked\n" << _request.body << "\n______\n";
		pos = 0;
		while (chunk_size != 0)
		{
/* 			if (pos > _request.body.size())
			{
				std::cerr << "parse_request_body(), pos > size()\n";
				// status = 400;
				return;
			} */
/* 
			if (pos == _request.body.size())
			{
				std::cerr << "parse_request_body(), will reread till last chunk\n";
				return;
			} */

			/* endptr_copy = endptr; */
			(void)endptr_copy;
			chunk_size = std::strtoul(&_request.body[pos], &endptr, 16);
			if (errno == ERANGE)
			{
				status = 413;
				return ;
			}
		/* 	if (endptr == endptr_copy)
			{
				std::cerr << "parse_request_body(), no conversion possible\n";
				return;
			} */


			chunk_field_end = _request.body.find(CRLF, pos);
			if (chunk_field_end == NPOS)
			{
				std::cerr << "parse_request_body(), chunk_field no CRLF\n";
				// status = 400;
				return;
			}

			chunk_field_end += CRLF_SIZE;
			_request.body.erase(pos, chunk_field_end);
			pos += chunk_size + CRLF_SIZE;
		}

		_request.headers.erase("Transfer-Encoding");
		body_complete = true;
}



void 	Client::fill_script_path(std::string &path, size_t pos)
{
	std::string	tmp;

	_request.script.path = path.substr(0, pos);
	_request.script.info = path.substr(pos);
}

void	Client::clear()
{
	clear_request();
	raw_request.clear();
	response.clear();
	status = 0;
	header_complete = false;
	body_complete = false;
	request_complete = false;
	assigned_server = NULL;
	assigned_location = NULL;
	cgi_pipe_rfd = 0;
	cgi_pid = 0;
	cgi_output.clear();
}

void	Client::clear_request()
{
	clear_script();
	_request.method = UNKNOWN;
	_request.target.clear();
	_request.version.clear();
	_request.headers.clear();
	_request.body.clear();
	_request.multi_bodys.clear();
	_request.abs_path.clear();
	_request.query.clear();
	_request.port.clear();
	_request.hostname.clear();
}

void	Client::clear_script()
{
	_request.script.path.clear();
	_request.script.info.clear();
}

// debug
void	Client::print_client(std::string message)
{
	std::map<std::string, std::string>::iterator	it;
std::cout << "\n=== DEBUG PRINT CLIENT ===\n";
	std::cout << message << ":\n----------\n\n" << "raw_request:\n__\n";
	::print_special(raw_request);
	std::cout << "\n__\n"
		<< "get_cl_fd()          : [" << get_cl_fd()			<< "]\n"
		<< "get_cl_port()        : [" << get_cl_port()			<< "]\n"
		<< "get_cl_ip()          : [" << get_cl_ip()			<< "]\n"
		<< "get_rq_method_str()  : [" << get_rq_method_str()	<< "]\n"
		<< "get_rq_target()      : [" << get_rq_target()		<< "]\n"
		<< "get_rq_abs_path()    : [" << get_rq_abs_path()		<< "]\n"
		<< "get_rq_query()       : [" << get_rq_query()			<< "]\n"
		<< "get_rq_version()     : [" << get_rq_version()		<< "]\n"
		<< "get_rq_body()        : [" << get_rq_body()			<< "]\n"
		<< "get_rq_port()        : [" << get_rq_port()			<< "]\n"
		<< "get_rq_hostname()    : [" << get_rq_hostname()		<< "]\n"
		<< "get_rq_script_path() : [" << get_rq_script_path()	<< "]\n"
		<< "get_rq_script_info() : [" << get_rq_script_info()	<< "]\n"
		<< "headers              :\n";
	for (it = _request.headers.begin(); it != _request.headers.end(); it++)
		std::cout << "   " << it->first << ": [" << it->second << "]\n";
	std::cout << "\n=== END PRINT CLIENT ===\n\n";
}

/*********************************************
 * GETTERS
 *********************************************/

// client side
int					 Client::get_cl_fd()	const { return _fd; }
const std::string	&	Client::get_cl_ip() 		const { return _ip; }
const std::string	&	Client::get_cl_port()		const { return _port; }
const listen_socket *	Client::get_cl_lsocket()	const { return _lsocket; }

// requette
http_method 	Client::get_rq_method()		const { return _request.method; }
std::string  	Client::get_rq_method_str() const
	{ return ::http_methods_to_str(_request.method); }
std::string		Client::get_rq_target()		const { return _request.target; }
std::string		Client::get_rq_abs_path()	const { return _request.abs_path; }
std::string		Client::get_rq_query()		const { return _request.query; }
std::string		Client::get_rq_version()	const { return _request.version; }
std::string		Client::get_rq_body()		const { return _request.body; }
std::string		Client::get_rq_port()		const { return _request.port; }
std::string		Client::get_rq_hostname()	const { return _request.hostname; }
std::string		Client::get_rq_script_path()const { return _request.script.path; }
std::string		Client::get_rq_script_info()const { return _request.script.info; }

std::string		Client::get_rq_headers(const std::string & key) const
{
	std::map<std::string, std::string>::const_iterator	it;

	it = _request.headers.find(::str_tolower(key));
	if (it == _request.headers.end())
		return "";
	return it->second;
}


/*********************************************
 * PRIVATE MEMBER FUNCTIONS
 *********************************************/

void	Client::_parse_request_line()
{
	std::vector<std::string>	line;
	std::string					raw_line;

	raw_line = ::get_line(raw_request, 0, CRLF);
	line = ::split_trim(raw_line, " ");
	if (line.size() != 3)
	{
		std::cerr << "err _parse_first_line(): wrong number of elements (" << line.size() << " instead of 3)\n";
		status = 400; // "bad request"
		// if the header is fucked up, then this will be triggered, but for some reason in test_body.sh case another response 200 OK is sent first?
	}
	else
	{
		_request.method = str_to_http_method(line[0]);
		_request.target = line[1];
		_parse_request_target(line[1]);
		_request.version = line[2];
	}
}

void	Client::_parse_request_target( std::string target )
{
	size_t		pos;

	pos = target.find("?");
	if (pos != NPOS)
		_request.query = target.substr(pos + 1);
	else
		_request.query = "";
	_request.abs_path = target.substr(0, pos);
	if (_request.abs_path[_request.abs_path.size() - 1] == '/')
		_request.abs_path.erase(_request.abs_path.size() - 1);
}

void	Client::_parse_request_fields()
{
	std::string	headers;
	size_t		pos;
	int			ret;

	headers = raw_request;
	// delete first line
	pos = headers.find(CRLF);
	if (pos != NPOS)
		headers.erase(0, pos + CRLF_SIZE);
	// delete body part
	pos = headers.find(CRLF CRLF);
	if (pos != NPOS)
		headers.erase(pos);
	else {
		std::cerr << "err _parse_request_fields(): request header doesn't end with empty line\n";
		status = 400; // "bad request"
	}
	// copy result of parser into headers
	ret = ::parse_http_headers(headers, _request.headers);
	if (ret > 0) {
		std::cerr << "err _parse_request_fields(): " << ret << " fields are bad formated\n";
		status = 400; // "bad request"
	}
	::str_map_key_tolower(_request.headers);
}

// TODO : I think its now useless. Probably to delete.
void	Client::_parse_port_hostname(std::string host)
{
	size_t	pos;

	if (host == "")
		std::cerr << "no host\n";

	pos = host.find(':');
	// port :
	if (pos == NPOS)
		_request.port = "4040"; // TODO: make equal to default port in config
	else
		_request.port = host.substr(pos);
	if (_request.port == ":")
		_request.port = "";
	// hostname :
	_request.hostname = host.substr(0, pos);
}

void	Client::_check_request_errors()
{
	/* Debug */ std::cerr << "Content-Length=" << get_rq_headers("Content-Length") << "\n";
	/* Debug */ std::cerr << "strtoul=" << std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10) << "\n";
	/* Debug */ std::cerr << "client_body_limit=" <<  assigned_server->client_body_limit << "\n";
///////////////////////
// Request line checks
	if (_request.method == UNKNOWN)
		status = 501; // HTTP Client Errors
	else if (_request.version.compare(0, sizeof("HTTP/1") - 1, "HTTP/1") != 0)
		status = 505; // HTTP Client Errors
	else if (!(assigned_location->allow_methods & _request.method))
	{
		status = 405; // HTTP Client Errors
		response.append("Allow: ");
		response.append(::http_methods_to_str(assigned_location->allow_methods));
		response.append(CRLF);
	}
	else if (assigned_location->redirect_status)
	{ // Weird behavior. Sometimes, the web browser seems to wait for a complete response until timeout.
		// (for codes 301, 302, 303, 307, and 308)
		status = assigned_location->redirect_status;
		response.append("Location: ");
		response.append(assigned_location->redirect_uri);
		response.append(CRLF CRLF);
	}

//////////////////
// Headers checks
	else if (!get_rq_headers("Content-Length").empty()
	&& std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10) > assigned_server->client_body_limit)
		status = 413;
	else if (!get_rq_headers("Transfer-Encoding").empty()
	&& get_rq_headers("Transfer-Encoding") != "chunked" )
		status = 501;
	else if (!get_rq_headers("Content-Encoding").empty())
	{
		status = 415;
		response.append("Accept-Encoding:"); // empty, no encoding accepted
		response.append(CRLF);
	}

	return;
}

ServerConfig	*Client::_determine_process_server(Client *client, std::vector<ServerConfig> &servers)
{
/*
	Behavior like this :
	http://nginx.org/en/docs/http/request_processing.html
*/

	std::string	server_name = client->get_rq_headers("Host");
	// /* Debug */ std::cerr << "server_name = " << server_name << "\n";
	size_t	pos = server_name.rfind(':');
	if (pos != NPOS)
		server_name.erase(pos);
	// /* Debug */ std::cerr << "server_name = " << server_name << "\n";

	std::vector<ServerConfig>::iterator	it = servers.begin();
	std::vector<ServerConfig>::iterator	default_server = servers.end();

	while (it != servers.end())
	{
		if (it->host == client->get_cl_lsocket()->host
		&&	it->port == client->get_cl_lsocket()->port)
		{
			if ( std::find(it->server_name.begin(), it->server_name.end(), server_name) != it->server_name.end() )
				break;
			else if (default_server == servers.end())
				default_server = it;
		}
		++it;
	}
	if (it != servers.end())
		return (&(*it));
	else
		return (&(*default_server));
}

// const?
const LocationConfig	*Client::_determine_location(const ServerConfig &server, const std::string &path)
{
/* RULES ***

If a path coresponds exactly to a location, use that one
if no path coresponds then use the most correct one
	most correct means the most precise branch that is still above
	the point we are aiming for

New Rule for location paths, they never end in /
Sooo
If we get a url that ends in / ignore the last /

*/

// TODO: technically i think this is useless...
	std::string	uri = path;
	if (uri[uri.size() - 1] == '/' && uri.size() != 1)
		uri.erase(uri.size() - 1);


	for (std::vector<LocationConfig>::const_iterator it = server.locations.begin(); it != server.locations.end(); it++)
	{
//		std::cout << it->path << "  --  ";
		if (it->path.size() > uri.size())
			continue ;

		if (uri.compare(0, it->path.size(), it->path) == 0)
		{
			if (it->path.size() == uri.size())
				return (&(*it));
			else if (uri[it->path.size()] == '/')
				return (&(*it));
	// this works cuz only ever looking for a / burried in a longer path
		}
	}
	return (&(server.locations.back()));


//	/test/mdr
//	/test/mdr/
//	/test/mdrBST

/////// More stuff to check this still works with

// /test/test_
// /test/test_/
// /test/test_deeper
// /test/test_deeper/
// /test/test_deepei
// /test/test_deepei/
// /test/test_deeperi
// /test/test_deeper/super_deep/
// /test/aaaaaaaaaaa/super_deep/

}

/*********************************************
 * OVERLOAD
 *********************************************/

bool	operator==(const Client& lhs, const Client& rhs)
	{ return lhs.get_cl_fd() == rhs.get_cl_fd(); }
bool	operator==(const Client& lhs, int fd)
	{ return lhs.get_cl_fd() == fd; }
bool	operator==(int fd, const Client& rhs)
	{ return fd == rhs.get_cl_fd(); }

