/* ************************************************************************** */
/*                                                                            */
/*                                                        :::      ::::::::   */
/*   ConfigParser.cpp                                   :+:      :+:    :+:   */
/*                                                    +:+ +:+         +:+     */
/*   By: lperrey <lperrey@student.42.fr>            +#+  +:+       +#+        */
/*                                                +#+#+#+#+#+   +#+           */
/*   Created: 2022/07/13 22:11:17 by me                #+#    #+#             */
/*   Updated: 2022/08/03 17:51:35 by lperrey          ###   ########.fr       */
/*                                                                            */
/* ************************************************************************** */

#include "ConfigParser.hpp"

// Default
ConfigParser::ConfigParser()
{
	std::cout << "Default Constructor\n";
	// don't use yet, you have no idea what the defaults are
}

ConfigParser::ConfigParser(const char*  path)
{
	std::cout << "Param Constructor\n";

	std::ifstream	file;
	std::string		buf;
	size_t 			comment;

	_content.clear();
	file.open(path);
	if (file.is_open())
	{
	// are there more throws i need to add in case of errors, what would
		// those errors be?
		while (!file.eof())
		{
			getline(file, buf);
			// remove # comments here.
			if ((comment = buf.find_first_of("#")) == std::string::npos)
			{
				// remove empty lines, i think...
				if ((buf.find_first_not_of(" \t")) != std::string::npos)
					_content.append(buf + '\n');
			}
			else if (comment > 0 && (buf.find_first_not_of(" \t")) < comment)
			{
				// is there a comment at the end of the line
				std::string	tmp = buf.substr(0, comment - 1);
				_content.append(tmp + '\n');
			}
		}
		file.close();
	}
	else
		throw std::invalid_argument("open config");
}

ConfigParser::~ConfigParser()
{
	// do i need to destroy anything, won't it handle itself?
}

/*
ConfigParser &	ConfigParser::operator=(const ConfigParser& rhs)
{
	if (this == rhs)	// * & ?
		return (*this);	// * ?

	// make some stuff equal
	return (*this);
}
*/


std::vector<ServerConfig> *	ConfigParser::parse()
{
	std::vector<ServerConfig> *	ret = new std::vector<ServerConfig>();
//	std::vector<ServerConfig>	ret;

	size_t	start = 0;
	size_t	curr = _content.find_first_not_of(" \t\n", 0);

	if (curr == std::string::npos)
		throw std::invalid_argument("empty config file");
	while (curr != std::string::npos)
	{
// why no checks here
// if not here do i need them elsewhere?
		start = _content.find_first_not_of(" \t\n", curr);
		curr = _content.find_first_of(" \t\n", start);
		std::string	key = _content.substr(start, curr - start);
		if (key != "server")
			throw std::invalid_argument("bad config file arguments 1");
		ret->push_back(_parse_server(&curr));
	}
	_post_processing(ret);
	return (ret);
}

ServerConfig	ConfigParser::_parse_server(size_t *start)
{
	ServerConfig	ret;
	size_t			curr = _content.find_first_not_of(" \t\n", *start);

	ret.client_body_limit = 0;
	ret.autoindex = false;
	if (curr == std::string::npos || _content[curr] != '{')
		throw std::invalid_argument("bad config file syntax 1");

	curr = _content.find_first_of(" \t\n", curr + 1);
//	if (curr == std::string::npos)	// are there other things to check for?
//		throw std::invalid_argument("bad config file syntax");
	while (curr != std::string::npos)	// here curr == { + 1
	{
	// so this moves curr to past the word...
		std::string key = _get_first_word(&curr);
	// now curr is on space after 1st word.
		if (key == "}")
		{
			// why +1 curr is already after it no?
			*start = _content.find_first_not_of(" \t\n", curr + 1);
			break ;
		}
		else if (key == "location")
			ret.locations.push_back(_parse_location(&curr));
		else
		{
			std::string	values = _get_rest_of_line(&curr);
		// curr now should be \n
			_set_server_values(&ret, key, values);
		}
	}
	return (ret);
}



LocationConfig	ConfigParser::_parse_location(size_t *start)
{
	LocationConfig	ret;
	size_t			curr = *start;
		// start is after the 1st word aka "location"

	ret.client_body_limit = 0;
	ret.redirect_status = 0;
	ret.path = _get_first_word(&curr);
	// in theory now curr should be right after the "path"

	curr = _content.find_first_not_of(" \t\n", curr);

	if (curr == std::string::npos || _content[curr] != '{')
		throw std::invalid_argument("bad config file syntax 2");

	curr = _content.find_first_of(" \t\n", curr + 1);
//	if (curr == std::string::npos)	// are there other things to check for?
//		throw std::invalid_argument("bad config file syntax");
	while (curr != std::string::npos)
	{
	// so this moves curr to past the word...
		std::string key = _get_first_word(&curr);
	// now curr is on space after 1st word.
		if (key == "}")
		{
			*start = curr;
			break ;
		}
		else
		{
			std::string	values = _get_rest_of_line(&curr);
		// curr now should be \n
			_set_location_values(&ret, key, values);
		}
	}
	return (ret);
}





void	ConfigParser::_set_server_values(ServerConfig *server, \
			const std::string key, std::string value)
{
	value = _pre_set_val_check(key, value);

	std::vector<std::string> tmp_val = ::split(value, ' ');
	size_t	size = tmp_val.size();

	if (size < 1)
		throw std::invalid_argument("missing value");
	else if (key == "server_name" && size == 1)
	{
		for (size_t i = 0; i < server->server_name.size(); i++)
		{
			if (server->server_name[i].compare(tmp_val[0]) == 0)
				throw std::invalid_argument("server_name already exists");
		}
		server->server_name.push_back(tmp_val[0]);
	}
	else if (key == "listen" && size == 1 && server->host == "" \
			&& server->port == "")
	{
		if (tmp_val[0].find_first_of(":") == std::string::npos)
		{
			// should i limit which ports can be used?
			if (!::isNumeric(tmp_val[0]))
				throw std::invalid_argument("bad port number");
			server->host = "0.0.0.0";
			server->port = tmp_val[0];
		}
		else
		{
			std::vector<std::string> tmp2 = ::split(tmp_val[0], ':');

			std::vector<std::string> ip = ::split(tmp2[0], '.');
			if (ip.size() != 4)
				throw std::invalid_argument("bad host ip");
			for (size_t i = 0; i < ip.size(); i++)
			{
				if (!::isNumeric_btw(0, 255, ip[i]))
					throw std::invalid_argument("bad host ip");
			}
			if (!::isNumeric(tmp2[1]))
				throw std::invalid_argument("bad port number");
			server->host = tmp2[0];
			server->port = tmp2[1];
		}
	}
	else if (key == "root" && size == 1 && server->root == "")
	{
		DIR* dir = opendir(tmp_val[0].c_str());
		if (dir)
		    closedir(dir);
		else
			throw std::invalid_argument("root dir could not be opened");
		server->root = tmp_val[0];
	}
	else if (key == "autoindex" && size == 1)
	{
		// autoindex is a bool, there's no good way for me to see if it has
		// bet set already
		server->autoindex = (tmp_val[0] == "on" ? true : false);
	}
	else if (key == "client_body_limit" && size == 1 \
			&& server->client_body_limit == 0)
	{
		if (!::isNumeric(tmp_val[0]))
			throw std::invalid_argument("client_body_limit not a number");
		server->client_body_limit = atoi(tmp_val[0].c_str());
	}
	else if (key == "index")
	{
		// i think you can call index several times...
		// should i be doing an access?
	// since index is at the root, but root might not yet be defined
	// will check index later in post
		for (unsigned long i = 0; i != tmp_val.size(); i++)
			server->index.push_back(tmp_val[i]);
	}
	else if (key == "allow_methods" && server->allow_methods.empty())
	{
		for (unsigned long i = 0; i != tmp_val.size(); i++)
		{
			http_method	m = ::str_to_http_method(tmp_val[i]);
			if (m == UNKNOWN)
				throw std::invalid_argument("not a valid method");
			server->allow_methods.push_back(m);
		}
	}
	else if (key == "error_page")
	{

	// so it can either be just a /here/is/the/repo
	// or it can be http://some_domain.com/here
	// wtf... how should we handle...



		// you can definitely call Error_pages several times, i think
		std::string	path = tmp_val[tmp_val.size() - 1];
		for (unsigned long i = 0; i != tmp_val.size() - 1; i++)
		{
			// what are the bounds for Error codes? 
			if (!(isNumeric_btw(0, 600, tmp_val[i])))
				throw std::invalid_argument("value not a valid number");
			int	status_code = atoi(tmp_val[i].c_str());

	// yea cuz here we continue.. why suddenly flexible not throw ?
			if (server->error_pages.find(status_code) != server->error_pages.end())
				continue ;
			server->error_pages[status_code] = path;
		}
	}
/*	else if (key == "recv_timeout" && size == 1 && server->server_name == "")
	{
		// what is tv_sec and do i need it?
// ok so i don't fully understand this part but ok, keep for now...
		server->recv_timeout.tv_sec = atoi(tmp_val[0].c_str());
	}
	else if (key == "send_timeout" && size == 1 && server->server_name == "")
	{
		server->send_timeout.tv_sec = atoi(tmp_val[0].c_str());
	}
*/	else
	{
		// means either you didn't write the right key, or the value is
		// missing, or the value has already been filled.
		throw std::invalid_argument("bad key value pair");
	}
}


void	ConfigParser::_set_location_values(LocationConfig *location, \
			const std::string key, std::string value)
{
	value = _pre_set_val_check(key, value);

	std::vector<std::string> tmp_val = ::split(value, ' ');
	size_t	size = tmp_val.size();

	if (size < 1)
		throw std::invalid_argument("missing value");
	else if (key == "root" && size == 1 && location->root == "")
	{
		DIR* dir = opendir(tmp_val[0].c_str());
		if (dir)
		    closedir(dir);
		else
			throw std::invalid_argument("root dir could not be opened");
		location->root = tmp_val[0];
	}
	else if (key == "client_body_limit" && size == 1 \
			&& location->client_body_limit == 0)
	{
		if (!::isNumeric(tmp_val[0]))
			throw std::invalid_argument("client_body_limit not a number");
		location->client_body_limit = atoi(tmp_val[0].c_str());
	}
	else if (key == "index")
	{
		// you can definitely call Index several times, i think
		for (unsigned long i = 0; i != tmp_val.size(); i++)
			location->index.push_back(tmp_val[i]);
	}
	else if (key == "allow_methods" && location->allow_methods.empty())
	{
		for (unsigned long i = 0; i != tmp_val.size(); i++)
		{
			http_method	m = ::str_to_http_method(tmp_val[i]);
			if (m == UNKNOWN)
				throw std::invalid_argument("not a valid method");
			location->allow_methods.push_back(m);
		}
	}
	else if (key == "cgi_info")
	{
		// you can call cgi_info several times i think.
// ok wtf is all this even doing, figure that out
		unsigned long	i = value.find_first_of(" ");
		if (i == std::string::npos)
			throw std::invalid_argument("bad config file arguments 8");
	// ok why an int now, we gotta be more consistent!
		int	j = value.find_first_not_of(" ", i);
		location->cgi_info[value.substr(0, i)] = value.substr(j, value.length());
	}
	else if (key == "return" && location->redirect_status == 0 \
			&& location->redirect_uri == "")
	{
// actually i think there can only be one per location...
		// you can definitely call return several times, i think
		if (tmp_val.size() != 2)
			throw std::invalid_argument("wrong number of values");
		// and tmp_val[0] should be a number and tmp_val[1] a string?
		if (!(::isNumeric(tmp_val[0])))
			throw std::invalid_argument("value not a number");

	// somehow check that tmp_val[1] is a string? or valid? how?
		// something about using access() to see if 
		location->redirect_status = atoi(tmp_val[0].c_str());
		location->redirect_uri = tmp_val[1];
	}
	else
	{
		// means either you didn't write the right key, or the value is
		// missing, or the value has already been filled.
		throw std::invalid_argument("bad key value pair");
	}
}











