/*!
 * 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: 28/06/2017
 */

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

#include <stdlib.h>

#include <json-c/json.h>

#include <ubus-cpp/ubus-call.h>

//#include <mozart/event/messages.h>

#include "etag-rest/etag-rest-controller.h"

#include "core/http-reason.h"
#include "core/http-parameter.h"

#include "etag-rest-connection.h"

#include <uhttpd/uhttpd.h>
#include <uhttpd/plugin.h>

#define kdefaultTimeout 30000

/*!  ----------------------------------------------------------------------------
 *  @fn EtagRestConnection
 *
 *  @brief constructor of the etag rest connection object.
 */
EtagRestConnection::EtagRestConnection(struct uhttpd_ops *an_ops, struct client *a_client, EtagRestController *a_controller, const std::string &a_parameters) : WebConnection(an_ops, a_client),
																																								m_controller(a_controller),
																																								m_http_header(kdefaultTimeout),
																																								m_ctx(NULL),
																																								m_wait_response_timeout(kdefaultTimeout)
{
	uh_client_ref(a_client);
	// printf("%s\n", __PRETTY_FUNCTION__);
}

/*!  ----------------------------------------------------------------------------
 *  @fn ~EtagRestConnection
 *
 *  @brief destructor of the etag rest connection object.
 */
EtagRestConnection::~EtagRestConnection(void)
{
	// printf("%s for client: %d\n", __PRETTY_FUNCTION__, m_client->id);
	// Stop Timers.
	stop();
	// Stop receive async events.
	if (m_ctx != NULL)
	{
		unregister_event(m_ctx);
		abort(m_ctx);
	}

	// printf("%s-done\n", __PRETTY_FUNCTION__);
	uh_client_unref(m_client);
}

/*!  ----------------------------------------------------------------------------
 *  @fn invoke
 *
 *  @brief invoke the etag rest connection.
 */
void EtagRestConnection::invoke(struct ubus_context *a_ctx)
{
	uint32_t the_id = 0;
	UBusCall the_cmd;
	std::string the_method;

	m_ctx = a_ctx;

	the_method = m_controller->get_method(m_client->request.method);

	parse_parameter();

	// printf("%s (object: %s)....\n", __PRETTY_FUNCTION__, m_controller->get_path().c_str());
	if (!ubus_lookup_id(a_ctx, m_controller->get_path().c_str(), &the_id))
	{
		// printf ("%s launch async call (%s)(%s)....\n", __PRETTY_FUNCTION__, the_method.c_str(), m_parameters.c_str());
		the_cmd.exec_async(a_ctx, the_id, the_method, m_parameters, this);
	}

	if (m_client->request.method == UH_HTTP_MSG_GET)
	{
		// printf("start timer (%d)....\n", m_wait_response_timeout);
		start(m_wait_response_timeout, false);
	}
}

/*!  ----------------------------------------------------------------------------
 *  @fn complete
 *
 *  @brief method called when an async exec result is arrived for this connection.
 */
void EtagRestConnection::complete(void)
{
	// printf("%s.\n", __PRETTY_FUNCTION__);
	manage_response(get_data());
}

/*!  ----------------------------------------------------------------------------
 *  @fn handle_event
 *
 *  @brief method called when a registered event is arrived for this connection.
 */
void EtagRestConnection::handle_event(const char *a_type, const char *a_json_msg)
{
	// printf("%s.\n", __PRETTY_FUNCTION__);
	manage_response(a_json_msg);
}

/*!  ----------------------------------------------------------------------------
 *  @fn parse_parameter
 *
 *  @brief Extract parameters from the http header to get the wanted parameters by this controller.
 */
int EtagRestConnection::parse_parameter(void)
{
	HttpParameter the_http_params;
	std::string the_origin;
	std::string the_etag;
	int32_t the_wait_response_timeout = -1;
	struct json_object *the_response_node;

	if (the_http_params.parse(m_client))
	{
		the_etag = the_http_params.get_etag();
		the_wait_response_timeout = the_http_params.get_request_timeout();
		the_origin = the_http_params.get_origin();
		if (!the_origin.empty())
			m_http_header.add_origin(the_origin);
	}
	// printf("EtagRestConnection::parse_parameter timeout: %d\n", the_wait_response_timeout);
	if (the_wait_response_timeout != -1)
	{
		// Convert it as milli seconds.
		m_wait_response_timeout = the_wait_response_timeout * 1000;
	}

	// printf("%s - etag: %s, keep alive: %d\n", __PRETTY_FUNCTION__, the_etag.c_str(), the_wait_response_timeout);

	if (m_client->request.method == UH_HTTP_MSG_GET)
	{
		the_response_node = json_object_new_object();

		if (!the_etag.empty())
			json_object_object_add(the_response_node, m_controller->get_etag_key().c_str(), json_object_new_string(the_etag.c_str()));

		m_parameters = json_object_to_json_string(the_response_node);
		json_object_put(the_response_node);
	}
	else
	{
		struct json_object *the_content_node, *the_root_node;

		if (!m_connection_data.empty())
		{
			the_root_node = json_object_new_object();
			the_content_node = json_tokener_parse(m_connection_data.c_str());
			if (the_content_node != NULL)
			{
				json_object_object_add(the_root_node, kEvent_content_entry, the_content_node);
			}

			m_parameters = json_object_to_json_string(the_root_node);
			json_object_put(the_root_node);
		}
	}

	return 0;
}

/*!  ----------------------------------------------------------------------------
 *  @fn manage_response
 *
 *  @brief manage the response rerceived by an event or an async response.
 */
void EtagRestConnection::manage_response(std::string a_response_data)
{
	std::string the_etag;
	struct json_object *the_root_node, *the_status_node, *the_content_node, *the_etag_node;
	int the_result_code = 500;

	// printf("receive notification client id: %d!!!!\n", m_client->id);
	// printf("\tdata: (%s)\n", a_response_data.c_str());

	the_root_node = json_tokener_parse(a_response_data.c_str());
	if (the_root_node == NULL)
	{
		// response couldn't be parsed.
		m_http_header.send(m_ops, m_client, 500);
		m_ops->chunk_printf(m_client, "0\r\n\r\n");
		// printf("response couldn't be parsed.\n");
		return;
	}

	// get status
	if (json_object_object_get_ex(the_root_node, kEvent_status_entry, &the_status_node))
	{
		the_result_code = json_object_get_int(the_status_node);
	}

	// get etag.
	if (json_object_object_get_ex(the_root_node, kEvent_version_entry, &the_etag_node))
	{
		enum json_type the_json_type;

		the_json_type = json_object_get_type(the_etag_node);
		if (the_json_type == json_type_int)
		{
			int the_tmp_value;
			the_tmp_value = json_object_get_int(the_etag_node);
			the_etag = std::to_string(the_tmp_value);
		}
		else if (the_json_type == json_type_string)
		{
			the_etag = json_object_get_string(the_etag_node);
		}

		m_http_header.add_etag(the_etag);
	}

	// get contents
	the_content_node = NULL;
	if (!json_object_object_get_ex(the_root_node, kEvent_content_entry, &the_content_node))
	{
		the_content_node = the_root_node;
	}

	if (the_result_code == 204)
	{
		json_object_put(the_root_node);
		register_event(m_ctx, m_controller->get_event_name());
		return;
	}

	// stop the timer.
	stop();

	m_http_header.send(m_ops, m_client, the_result_code);

	m_ops->chunk_printf(m_client, "%s", json_object_to_json_string(the_content_node));

	json_object_put(the_root_node);

	m_ops->request_done(m_client);
}

/*!  ----------------------------------------------------------------------------
 *  @fn expire
 *
 *  @brief method called when the connection time has expired. we will close the connection 
 *         with a 204
 */
int EtagRestConnection::expire(void)
{
	// printf("%s client id: %d, path:%s\n", __PRETTY_FUNCTION__, m_client->id, m_controller->get_path().c_str());
	stop();

	m_http_header.send(m_ops, m_client, 204);
	uh_client_ref(m_client);
	m_ops->request_done(m_client);

	return 0;
}
