
#include "Webserv.hpp"
/*
	CGI RFC:
	https://www.rfc-editor.org/rfc/rfc3875.html
*/

bool	Webserv::_is_cgi(Client *client, std::string path)
{
	std::string	script_path;
	size_t		file_type;
	size_t		file_mode = client->status;
	size_t		pos = 0;

	while (pos != NPOS)
	{
		pos = _cgi_pos(client, path, pos);
		if (pos == NPOS)
			break;
		client->fill_script_path(path, pos);
		script_path = client->get_rq_script_path();
		file_type = ::eval_file_type(script_path);
		if (file_type == IS_DIR) // but what if it's a symlink ?
			continue;
		if (file_type == IS_FILE)
		{
			file_mode = ::eval_file_access( script_path, X_OK );
			if (!file_mode)
				return true;
		}
	}
	client->clear_script();
	client->status = file_mode; // 404 not_found OR 403 forbidden
	return false;
}

size_t	Webserv::_cgi_pos(Client *client, std::string &path, size_t pos)
{
	std::vector<std::string>					v_ext;
	std::vector<std::string>::const_iterator	it;
	std::vector<std::string>::const_iterator	it_end;
	size_t										len;
	std::locale									loc; // for isalpha()

	v_ext = client->assigned_location->cgi_ext;
	if (v_ext.empty())
		return NPOS;
	it_end = client->assigned_location->cgi_ext.end();
	while (pos < path.size())
	{
		if (path.compare(pos, 2, "./") == 0)
			pos += 2;
		pos = path.find('.', pos);
		if (pos == NPOS)
			return pos;
		it = client->assigned_location->cgi_ext.begin();
		for ( ; it != it_end; ++it)
		{
			len = (*it).size();
			if (path.compare(pos + 1, len, *it) == 0)
				if ( !std::isalpha(path[pos + 1 + len], loc) )
					return pos + 1 + len;
		}
		pos++;
	}
	return NPOS;
}

void	Webserv::_exec_cgi(Client *client)
{
	char*						env_cstr[19] = {NULL};
	std::vector<std::string>	env_vector;
	env_vector.reserve(18);
	int i = 0;

	_set_env_vector(client, env_vector);
	try {
		_set_env_cstr(env_cstr, env_vector);
		_exec_script(client, env_cstr);

		while (env_cstr[i] != NULL)
			delete[] env_cstr[i++];
		return;
	}
	catch (const Webserv::ExecFail& e)
	{
		while (env_cstr[i] != NULL)
			delete[] env_cstr[i++];
		throw;
	}
}

std::string	Webserv::_dup_env(std::string var, std::string val = "")
{
	std::string str;

	str = var + "=" + val;
	return (str);
}

std::string	Webserv::_dup_env(std::string var, int i)
{
	std::string str;
	std::string val;

	val = ::itos(i);
	str = var + "=" + val;
	return (str);
}

// TODO : verifier que les variables sont corrects
/*
	https://www.rfc-editor.org/rfc/rfc3875#section-4.1
*/
void	Webserv::_set_env_vector(Client *client, std::vector<std::string> &env_vector)
{
	env_vector.push_back(_dup_env("AUTH_TYPE"));		// authentification not supporte
	env_vector.push_back(_dup_env("CONTENT_LENGTH"		, client->get_rq_body().size()));
	env_vector.push_back(_dup_env("CONTENT_TYPE"		, client->get_rq_headers("Content-Type")));
	env_vector.push_back(_dup_env("GATEWAY_INTERFACE"	, "CGI/1.1")); // https://www.rfc-editor.org/rfc/rfc3875#section-4.1.4
	env_vector.push_back(_dup_env("PATH_INFO"			, client->get_rq_script_info())); // LUKE: To Check
	env_vector.push_back(_dup_env("PATH_TRANSLATED"));	// not supported // LUKE: Why not supported ?
	env_vector.push_back(_dup_env("QUERY_STRING"		, client->get_rq_query()));
	env_vector.push_back(_dup_env("REMOTE_ADDR"			, client->get_cl_ip()));
	env_vector.push_back(_dup_env("REMOTE_HOST"			, client->get_cl_ip())); // equal to REMOTE_ADDR or empty
	env_vector.push_back(_dup_env("REMOTE_IDENT"));		// authentification not supported
	env_vector.push_back(_dup_env("REMOTE_USER"));		// authentification not supported
	env_vector.push_back(_dup_env("REQUEST_METHOD"		, client->get_rq_method_str()));
	env_vector.push_back(_dup_env("SCRIPT_NAME"			, "/" + client->get_rq_script_path())); // LUKE: To Check
	env_vector.push_back(_dup_env("SERVER_NAME"			, client->get_cl_lsocket()->host));
	env_vector.push_back(_dup_env("SERVER_PORT"			, client->get_cl_lsocket()->port));
	env_vector.push_back(_dup_env("SERVER_PROTOCOL"		, "HTTP/1.1"));
	env_vector.push_back(_dup_env("SERVER_SOFTWARE"		, "Webserv/0.1"));
	env_vector.push_back(_dup_env("REDIRECT_STATUS"		, "200"));
}

void	Webserv::_set_env_cstr(char *env_cstr[], std::vector<std::string> &env_vector)
{
	std::vector<std::string>::const_iterator it = env_vector.begin();
	std::vector<std::string>::const_iterator it_end = env_vector.end();
	int i = 0;
	while (it != it_end)
	{
		env_cstr[i] = new char[it->size()+1]; 
		std::strcpy(env_cstr[i], it->c_str());
		++it;
		++i;
	}
	env_cstr[i] = NULL;
}


void	Webserv::_exec_script(Client *client, char *env[])
{
	#define RD 0
	#define WR 1
	#define FD_WR_TO_CHLD fd_in[WR]
	#define FD_WR_TO_PRNT fd_out[WR]
	#define FD_RD_FR_CHLD fd_out[RD]
	#define FD_RD_FR_PRNT fd_in[RD]

	pid_t			pid;
	char * const nll[1]	= {NULL};
	std::string		script_output;
	std::string		body = client->get_rq_body();
	int				fd_in[2];
	int				fd_out[2];
	std::string		path;

	::pipe(fd_in);
	::pipe(fd_out);

	pid = fork();
	if (pid == -1)
		std::perror("err fork()");
	else if (pid == 0) // child
	{
		std::signal(SIGPIPE, SIG_DFL);
		std::signal(SIGINT, SIG_DFL);

		_close_all_clients_fd();
		::close(_epfd);
		::close(FD_WR_TO_CHLD);
		::close(FD_RD_FR_CHLD);
		if (dup2(FD_RD_FR_PRNT, STDIN_FILENO) == -1)
		{
			std::perror("err dup2()");
			::close(FD_RD_FR_PRNT); // Valgind debug, not essential
			::close(FD_WR_TO_PRNT); // Valgind debug, not essential
			throw ExecFail();
		}
		if (dup2(FD_WR_TO_PRNT, STDOUT_FILENO) == -1)
		{
			std::perror("err dup2()");
			::close(FD_RD_FR_PRNT); // Valgind debug, not essential
			::close(FD_WR_TO_PRNT); // Valgind debug, not essential
			throw ExecFail();
		}

		::close(FD_RD_FR_PRNT);
		::close(FD_WR_TO_PRNT);

		path = client->get_rq_script_path(); // Wut ? Only relative path ?
/*DEBUG*/std::cerr << "execve:[" << path << "]\n";
		if (::execve(path.c_str(), nll, env) == -1) // replace path for debug error forcing
		{
			std::perror("err execve()");
			::close(STDIN_FILENO); // Valgind debug, not essential
			::close(STDOUT_FILENO); // Valgind debug, not essential
			throw ExecFail();
		}
	}
	else //parent
	{
		::close(FD_RD_FR_PRNT);
		::close(FD_WR_TO_PRNT);
		::write(FD_WR_TO_CHLD, body.c_str(), body.size()); // move this before the fork ?
		::close(FD_WR_TO_CHLD);

		// add FD_RD_FR_CHLD to epoll,
		_epoll_update(FD_RD_FR_CHLD, EPOLLIN, EPOLL_CTL_ADD);
		// stop monitoring client->fd until the cgi-script as done is job
		_epoll_update(client->get_cl_fd(), 0, EPOLL_CTL_DEL);

		client->cgi_pipe_rfd = FD_RD_FR_CHLD;
		client->cgi_pid = pid;
	}
}

#define STATUS_500 std::string("Status: 500" CRLF CRLF);

void	Webserv::_check_script_output(Client *client, std::string & output)
{
	size_t pos;
	pos = client->cgi_output.find(CRLF CRLF);
	if (pos == 0 || pos == NPOS)
	{
		client->status = 500;;
		return;
	}
	_check_script_status(client, output);
	if (client->status >= 400 && client->status < 600)
		return;
/*DEBUG*/ std::cout << "\n" B_PURPLE "[script status]:\n" << "client->status:[" << client->status << "]" RESET "\n"; ::print_special(output); std::cout << B_PURPLE "-----------" RESET "\n\n";
	client->status = _check_script_fields(output, client->status);
/*DEBUG*/ std::cout << "\n" B_PURPLE "[script fields]:\n" << "client->status:[" << client->status << "]" RESET "\n"; ::print_special(output); std::cout << B_PURPLE "-----------" RESET "\n\n";
	_check_fields_duplicates(client, output);
/*DEBUG*/ std::cout << "\n" B_PURPLE "[fields duplicates]:\n" << "client->status:[" << client->status << "]" RESET "\n"; ::print_special(output); std::cout << B_PURPLE "-----------" RESET "\n\n";
	_remove_body_leading_empty_lines(output);
/*DEBUG*/ std::cout << "\n" B_PURPLE "[script empty lines]:\n" << "client->status:[" << client->status << "]" RESET "\n"; ::print_special(output); std::cout << B_PURPLE "-----------" RESET "\n\n";
	_add_script_body_length_header(output);
/*DEBUG*/ std::cout << "\n" B_PURPLE "[script content length]:\n" << "client->status:[" << client->status << "]" RESET "\n"; ::print_special(output); std::cout << B_PURPLE "-----------" RESET "\n\n";
}

void	Webserv::_check_script_status(Client *client, std::string & output)
{
	size_t	pos;
	int		status_pos;

	pos = output.find("Status:");
	if (pos != NPOS)
	{
		status_pos = pos + std::string("Status:").size();
		client->status = std::strtoul(output.c_str() + status_pos, NULL, 10);
		::extract_line(output, pos, CRLF);
	}
	else
		client->status = 200;
}

size_t	Webserv::_check_script_fields(const std::string & output, size_t status)
{
	std::string	headers;
	std::string body;
	size_t		pos;

std::cerr << "0\n";
	pos = output.find(CRLF CRLF);
	if (pos == NPOS) // there is not empty line
		return 500;
std::cerr << "1\n";
	headers = output.substr(0, pos);
	body = output.substr(pos + CRLF_SIZE * 2);
	headers = str_tolower(headers);
	pos = headers.find("content-type");
	if (pos == NPOS) // there is no content-type field
	{
		if (!body.empty()) // there is body
			return 500;
std::cerr << "2\n";
		if (headers.find("location") == NPOS) // there is no location field
			return 500;
std::cerr << "3\n";
	}
	else if (headers.find("location") != NPOS) // there is a location field
	{
		if (body.empty()) // there is no body
			return 500;
std::cerr << "4\n";
	}
std::cerr << "5\n";
	return status;
}

void	Webserv::_check_fields_duplicates(Client *client, std::string & output)
{
	std::map<std::string, std::string>				srv_fld;	// server_field
	std::map<std::string, std::string>				scr_fld;	// script_field
	std::map<std::string, std::string>::iterator	it_srv;
	std::map<std::string, std::string>::iterator	it_scr;
	std::string										tmp;
	size_t											pos;

	// put server headers in map
	tmp = client->response;
	pos = tmp.find(CRLF CRLF);
	if (pos != NPOS)
		tmp.erase(pos);
	::parse_http_headers(tmp, srv_fld);
	// put script headers in map
	tmp = output;
	pos = tmp.find(CRLF CRLF);
	if (pos != NPOS)
		tmp.erase(pos);
	::parse_http_headers(tmp, scr_fld);
	// compare both map to supress duplicates
	for (it_srv = srv_fld.begin(); it_srv != srv_fld.end(); it_srv++)
	{
		for (it_scr = scr_fld.begin(); it_scr != scr_fld.end(); it_scr++)
		{
			if (str_tolower(it_srv->first) == str_tolower(it_scr->first))
			{
				pos = client->response.find(it_srv->first);
				::extract_line(client->response, pos, CRLF);
			}
		}
	}
}

void	Webserv::_remove_body_leading_empty_lines(std::string & output)
{
	size_t		pos;
	size_t		pos_empty;

	pos = output.find(CRLF CRLF);
	if (pos == NPOS)
		return;
	pos += CRLF_SIZE * 2;
	pos_empty = pos;
	while (pos_empty == pos)
	{
		pos = output.find(CRLF, pos);
		if (pos == pos_empty)
			extract_line(output, pos, CRLF);
	}
}

void	Webserv::_add_script_body_length_header(std::string & output)
{
	std::map<std::string, std::string>				field;
	std::map<std::string, std::string>::iterator	it;
	std::stringstream								str_len;
	std::string										tmp;
	size_t											pos;
	size_t											len;

	pos = output.find(CRLF CRLF);
	if (pos != NPOS)
		tmp = output.substr(pos + (CRLF_SIZE * 2));
	len = tmp.size();
	str_len << len;
	
	// put script headers in map
	tmp = output;
	pos = tmp.find(CRLF CRLF);
	if (pos != NPOS)
		tmp.erase(pos);
	::parse_http_headers(tmp, field);
	// case insensitive search in map for "Content-Length"
	tmp = "Content-Length";
	for (it = field.begin(); it != field.end(); ++it)
	{
		if (str_tolower(it->first) == str_tolower(tmp))
		{
			pos = output.find(it->first);
			::extract_line(output, pos, CRLF);
		}
	}
	tmp += ": ";
	tmp += str_len.str();
	tmp += CRLF;
	output.insert(0, tmp);
}

