/*!
 * Copyright (c) 2015-2018, NADAL Jean-Baptiste. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA
 *
 * @Author:  NADAL Jean-Baptiste
 * @Date: 30/06/2017
 */

/*------------------------------- INCLUDES ----------------------------------*/

extern "C" {
#include <libubox/blobmsg_json.h>
}

#include <json-c/json.h>

#include <uhttpd/uhttpd.h>

#include "core/uri-transform.h"

#include "http-parameter.h"

#define kIfNoneMatch "if-none-match"
#define kPrefer "prefer"
#define kOrigin "origin"

/*!  ----------------------------------------------------------------------------
 *  @fn HttpParameter
 *
 *  @brief constructor of the http parameter.
 */
HttpParameter::HttpParameter(void) : m_request_timeout(-1)
{
}

/*!  ----------------------------------------------------------------------------
 *  @fn parse_from_uri
 *
 *  @brief parse parameters from the uri.
 */
std::string HttpParameter::parse_from_uri(const std::string &a_parameters)
{
	std::string the_result;
	std::vector<std::string> the_list;
	std::vector<std::string>::iterator the_it;
	std::string the_key, the_value, the_value_decoded;
	std::size_t the_pos;
	struct json_object *the_root_node = NULL;
	the_root_node = json_object_new_object();

	the_list = split_params(a_parameters, '&');
	for (the_it = the_list.begin(); the_it != the_list.end(); ++the_it)
	{
		// printf("--> %s\n", (*the_it).c_str());
		the_pos = (*the_it).find_first_of("=", 0);
		if (the_pos != std::string::npos)
		{
			the_key = (*the_it).substr(0, the_pos);
			the_value = (*the_it).substr(the_pos + 1);
			the_value_decoded = UriTransform::decode(the_value);

#if 0
			printf("    - key = %s\n", the_key.c_str());
			printf("    - value = %s\n", the_value.c_str());
			printf("    - valuedecode = %s\n", the_value_decoded.c_str());
#endif
			if (is_number(the_value_decoded))
				json_object_object_add(the_root_node, the_key.c_str(), json_object_new_int(std::stoi(the_value_decoded)));
			else if (the_value_decoded == "true")
				json_object_object_add(the_root_node, the_key.c_str(), json_object_new_boolean(true));
			else if (the_value_decoded == "false")
				json_object_object_add(the_root_node, the_key.c_str(), json_object_new_boolean(false));
			else
				json_object_object_add(the_root_node, the_key.c_str(), json_object_new_string(the_value_decoded.c_str()));
		}
	}

	the_result = json_object_to_json_string(the_root_node);

	// Clean the json object.
	json_object_put(the_root_node);

	return the_result;
}

/*!  ----------------------------------------------------------------------------
 *  @fn parse
 *
 *  @brief parse the http header and save only the wanted headers.
 */
bool HttpParameter::parse(client *a_client)
{
	std::string the_prefer_string;
	char *the_string = blobmsg_format_json(a_client->hdr.head, true);
	struct json_object *the_root_node = NULL, *the_value_node;

	the_root_node = json_tokener_parse(the_string);
	// printf("HttpParameter::parse: <%s>\n", the_string);
	free(the_string);

	if (the_root_node == NULL)
	{
		return false;
	}

	// Get if-none-match
	if (json_object_object_get_ex(the_root_node, kIfNoneMatch, &the_value_node))
	{
		m_etag = json_object_get_string(the_value_node);
	}

	// Get Prefer - wait RFC 7240 // Prefer: wait=120
	if (json_object_object_get_ex(the_root_node, kPrefer, &the_value_node))
	{
		std::vector<std::string> the_list;
		the_prefer_string = json_object_get_string(the_value_node);
		the_list = split_params(the_prefer_string, '=');
		std::string the_token = the_list[1];
		//printf("the_prefer_string: %s\n", the_prefer_string.c_str());
		//printf("the_token: %s\n", the_token.c_str());

		if (is_number(the_token))
		{
			m_request_timeout = std::atoi(the_token.c_str());
		}
		// printf("timeout = %d\n", m_request_timeout);
	}

	// Get Origin
	if (json_object_object_get_ex(the_root_node, kOrigin, &the_value_node))
	{
		m_origin = json_object_get_string(the_value_node);
	}

	json_object_put(the_root_node);

	// printf("parse: E-tag: %s, timeout: %d, origin: %s\n", m_etag.c_str(), m_request_timeout, m_origin.c_str());

	return true;
}

/*!  ----------------------------------------------------------------------------
 *  @fn get_origin
 *
 *  @brief get the etag from the header request.
 */
std::string HttpParameter::get_origin(void)
{
	return m_origin;
}

/*!  ----------------------------------------------------------------------------
 *  @fn get_etag
 *
 *  @brief get the etag from the header request.
 */
std::string HttpParameter::get_etag(void)
{
	return m_etag;
}

/*!  ----------------------------------------------------------------------------
 *  @fn get_request_timeout
 *
 *  @brief extract the the keep-alive timeout from the header (as second)
 *         and return it as millisecond timeout.
 */
int16_t HttpParameter::get_request_timeout(void)
{
	return m_request_timeout;
}

/*!  ----------------------------------------------------------------------------
 *  @fn split_params
 *
 *  @brief method to split a string into sub string from a separator. and return a
 *         vector from all the substrings.
 */
std::vector<std::string> HttpParameter::split_params(const std::string &a_string, char a_seperator)
{
	std::vector<std::string> the_output;

	std::string::size_type the_prev_pos = 0, the_pos = 0;

	while ((the_pos = a_string.find(a_seperator, the_pos)) != std::string::npos)
	{
		std::string substring(a_string.substr(the_prev_pos, the_pos - the_prev_pos));
		the_output.push_back(substring);
		the_prev_pos = ++the_pos;
	}

	the_output.push_back(a_string.substr(the_prev_pos, the_pos - the_prev_pos)); // Last word

	return the_output;
}

/*!  ----------------------------------------------------------------------------
 *  @fn is_number
 *
 *  @brief return true if the string given has parameter is a number.
 */
bool HttpParameter::is_number(const std::string &a_string)
{
	char *p;
	strtod(a_string.c_str(), &p);
	return *p == 0;
}
