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

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

#include <string>
#include <algorithm>

#include <fmt/format.h>

#include <dirent.h>

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

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

#include <json-c/json.h>

#include "core/web-connection.h"
#include "core/http-header.h"

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

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

#include "notification/notification-controller.h"

#include "uhttp-server.h"

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

/*------------------------------- GLOBALS ----------------------------------*/

#define kControllerKey "controller"
#define kEtagControllerKey "etag_controller"

#define kNotificationKey "notification"

#define kControllerPathKey "path"
#define kControllerModelNameKey "model_name"
#define kControllerEventNameKey "event_name"
#define kControllerEtagKey "etag_key"
#define kControllerGetMethodKey "get_method"
#define kControllerSetMethodKey "set_method"
#define kControllerRawResponseKey "raw_response"

#define kdefaultGetMethod "get"
#define kdefaultSetMethod "set"

#define kJsonControlerNotFound "{{\n"                                     \
							   "  \"id\": \"{}\",\n"                      \
							   "  \"status\": \"failed\",\n"              \
							   "  \"error\": \"Controller Not Found\",\n" \
							   "  \"response_code\": 404\n}}"

#define DEBUG_REQUEST 1
#define BUFFER_SIZE 100

#ifdef DEBUG_REQUEST
/*!  ----------------------------------------------------------------------------
 *  @fn log_with_timestamp
 *
 *  @brief method to log with time stamp.
 */
void log_with_timestamp(const char *a_msg)
{
	char the_buffer[512];
	time_t the_time;
	struct timeval the_timeval;

	the_time = time(NULL);
	strftime(the_buffer, sizeof(the_buffer), "%m/%d/%Y %H:%M:%S", localtime(&the_time));

	gettimeofday(&the_timeval, NULL);
	fprintf(stdout, "%s.%09ld - %s\n", the_buffer, the_timeval.tv_usec * 1000, a_msg);
}

double time_diff(struct timeval x, struct timeval y)
{
	double x_ms, y_ms, diff;

	x_ms = (double)x.tv_sec * 1000000 + (double)x.tv_usec;
	y_ms = (double)y.tv_sec * 1000000 + (double)y.tv_usec;

	diff = (double)y_ms - (double)x_ms;

	return diff;
}
#endif

/*!  ----------------------------------------------------------------------------
 *  @fn get_uhttp_server_ctx
 *
 *  @brief method to return ubux context to c plugin.
 */
extern "C" struct ubus_context *get_uhttp_server_ctx(void)
{
	return UhttpServer::get_instance().get_context();
}

/*!  ----------------------------------------------------------------------------
 *  @fn set_uhttp_server_ctx
 *
 *  @brief init method from c plugin to uhttp server.
 */
extern "C" int uhttp_server_setup(struct ubus_context *a_ctx, const struct uhttpd_ops *an_ops, const char *a_config_dir_path)
{
	return UhttpServer::get_instance().setup(a_ctx, an_ops, a_config_dir_path);
}

/*!  ----------------------------------------------------------------------------
 *  @fn uhttp_server_check_url
 *
 *  @brief Check if the url is managed by this plugin
 */
extern "C" bool uhttp_server_check_url(const char *an_url)
{
	return UhttpServer::get_instance().check_url(an_url);
}

/*!  ----------------------------------------------------------------------------
 *  @fn uhttp_server_handle_request
 *
 *  @brief Handle the request.
 */
extern "C" void uhttp_server_handle_request(struct client *a_cl, char *an_url, struct path_info *a_pi)
{
#ifdef DEBUG_REQUEST
	gettimeofday(&a_cl->start_request, NULL);
	char buffer[BUFFER_SIZE];
	snprintf(buffer, sizeof(buffer), "uhttp handle_request : client id: %d - %s", a_cl->id, an_url);
	log_with_timestamp(buffer);
#endif
	UhttpServer::get_instance().handle_request(a_cl, an_url, a_pi);
}

/*!  ----------------------------------------------------------------------------
 *  @fn uhttp_server_data_send
 *
 *  @brief callback called when data should .
 */
static int uhttp_server_data_send(struct client *a_cl, const char *a_data, int a_len)
{
#ifdef DEBUG_REQUEST
	log_with_timestamp("uhttp_server_data_send");
#endif
	return UhttpServer::get_instance().data_send(a_cl, a_data, a_len);
}

/*!  ----------------------------------------------------------------------------
 *  @fn uhttp_server_data_done
 *
 *  @brief callback called when the server is ready to launch the request.
 */
static void uhttp_server_data_done(struct client *a_cl)
{
	UhttpServer::get_instance().invoke_request(a_cl);
}

/*!  ----------------------------------------------------------------------------
 *  @fn uhttp_server_request_free
 *
 *  @brief callback call to free the request.
 */
static void uhttp_server_request_free(struct client *a_cl)
{
#ifdef DEBUG_REQUEST
	struct timeval the_current_timeval;
	gettimeofday(&the_current_timeval, NULL);
	char buffer[BUFFER_SIZE];
	snprintf(buffer, sizeof(buffer), "client id: %d duree: %.0lf ms", a_cl->id, time_diff(a_cl->start_request, the_current_timeval) / 1000);
	log_with_timestamp(buffer);
#endif
	UhttpServer::get_instance().request_free(a_cl);
}

/*!  ----------------------------------------------------------------------------
 *  @fn uhttp_server_close_fds
 *
 *  @brief callback call when the process close. (needed ? to be dfefined).
 */
static void uhttp_server_close_fds(struct client *cl)
{
	// printf("uhttp_server_close_fds(%p)... client id: %d\n", cl, cl->id);
}

/*!  ----------------------------------------------------------------------------
 *  @fn get_instance
 *
 *  @brief singleton to return the instance of UhttpServer.
 */
UhttpServer &UhttpServer::get_instance(void)
{
	static UhttpServer m_ptr;
	return m_ptr;
}

/*!  ----------------------------------------------------------------------------
 *  @fn UhttpServer
 *
 *  @brief Constructor of the Uhttp Server
 */
UhttpServer::UhttpServer(void) : m_ctx(NULL),
								 m_ops(NULL)
{
}

/*!  ----------------------------------------------------------------------------
 *  @fn ~UhttpServer
 *
 *  @brief Destructor of the Uhttp Server
 */
UhttpServer::~UhttpServer(void)
{
	ConnectionIterator the_cnx_it;

	for (std::vector< WebController *>::iterator the_it = m_controllers.begin();
		the_it != m_controllers.end(); ++the_it)
	{
		delete *the_it;
	}

	m_controllers.clear();

	for (the_cnx_it = m_connections.begin(); the_cnx_it != m_connections.end(); ++the_cnx_it)
	{
		delete the_cnx_it->second;
	}

	ubus_free(m_ctx);
}

/*!  ----------------------------------------------------------------------------
 *  @fn setup
 *
 *  @brief setup the http server.
 */
int UhttpServer::setup(struct ubus_context *a_ctx, const struct uhttpd_ops *an_ops, const char *a_config_dir_path)
{
	m_ctx = a_ctx;
	m_ops = const_cast<struct uhttpd_ops *>(an_ops);

	return load_config_dir(a_config_dir_path);
}

/*!  ----------------------------------------------------------------------------
 *  @fn get_context
 *
 *  @brief return the ubus context.
 */
struct ubus_context *UhttpServer::get_context(void)
{
	return m_ctx;
}

/*!  ----------------------------------------------------------------------------
 *  @fn check_url
 *
 *  @brief check if the url is managed by this plugin.
 */
bool UhttpServer::check_url(const std::string &an_url)
{
	std::vector<WebController *>::iterator the_it;
	printf("UhttpServer::check_url: %s\n", an_url.c_str());

	for (the_it = m_controllers.begin(); the_it != m_controllers.end(); ++the_it)
	{
		if ( (*the_it)->find(an_url))
		{
			return true;
		}
	}

	return false;
}

/*!  ----------------------------------------------------------------------------
 *  @fn handle_request
 *
 *  @brief Handle the Request.
 */
void UhttpServer::handle_request(struct client *a_cl, const std::string &an_url, struct path_info *a_pi)
{
	std::string the_url, the_parameters;
	std::size_t the_pos;
	WebController *the_controller;
	printf("uhttp_server_handle_request : url: <%s> client: %p\n", an_url.c_str(), a_cl);

	// Check if parameters are present on the url.
	the_pos = an_url.find_first_of("?", 0);
	if (the_pos != std::string::npos)
	{
		// parameters are present.
		the_url = an_url.substr(0, the_pos);
		the_parameters = an_url.substr(the_pos + 1);
		// printf("url: <%s> | parameters: <%s>\n", the_url.c_str(), the_parameters.c_str());
	}
	else
	{
		the_url = an_url;
	}

#warning todo
//	the_controller = m_controller_array[the_url];
	if (the_controller == NULL)
	{
		std::string the_msg;
		the_msg = fmt::format(kJsonControlerNotFound, the_url);
		send_error(a_cl, 404, "Not Found", the_msg);
		return;
	}

	// We found the controller.
	// printf("method: %d\n", a_cl->request.method);
	struct dispatch *d = &a_cl->dispatch;
	switch (a_cl->request.method)
	{
	case UH_HTTP_MSG_GET:
	case UH_HTTP_MSG_PUT:
	case UH_HTTP_MSG_POST:
	case UH_HTTP_MSG_DELETE:
		d->data_send = uhttp_server_data_send;
		d->data_done = uhttp_server_data_done;
		d->close_fds = uhttp_server_close_fds;
		d->free = uhttp_server_request_free;
		m_connections[a_cl->id] = the_controller->new_connection(m_ops, a_cl, the_parameters);
		break;

	case UH_HTTP_MSG_OPTIONS:
	{
		HttpHeader the_http_header;
		the_http_header.send_option(m_ops, a_cl, 200);
	}
	break;

	default:
		// printf("Invalid Request\n");
		send_error(a_cl, 400, "Bad Request", "Invalid Request");
	}

	// printf("uhttp_server_handle_request -done.\n");
}

/*!  ----------------------------------------------------------------------------
 *  @fn invoke_request
 *
 *  @brief This method to invoke the request.
 */
void UhttpServer::invoke_request(struct client *a_client)
{
	uh_client_ref(a_client);
	// printf("UhttpServer::invoke_request\n");
	m_connections[a_client->id]->invoke(m_ctx);

	uh_client_unref(a_client);
}

/*!  ----------------------------------------------------------------------------
 *  @fn request_free
 *
 *  @brief This method free the complete request.
 */
void UhttpServer::request_free(struct client *a_client)
{
	ConnectionIterator the_it;
	// printf("UhttpServer::request_free id:%d nb connection: %ld\n", a_client->id, m_connections.size());
	uh_client_ref(a_client);

	the_it = m_connections.find(a_client->id);
	if (the_it != m_connections.end())
	{
		if ((the_it->second)->should_be_removed())
		{
			delete the_it->second;
			m_connections.erase(the_it);
		}
	}

	uh_client_unref(a_client);
	// printf("UhttpServer::request_free-done\n");
}

/*!  ----------------------------------------------------------------------------
 *  @fn data_send
 *
 *  @brief This method called when data arrived from the client.
 */
int UhttpServer::data_send(struct client *a_client, const char *a_data, int a_len)
{
	return m_connections[a_client->id]->add_data(a_data, a_len);
}

/*!  ----------------------------------------------------------------------------
 *  @fn load_config_dir
 *
 *  @brief Load config from the configuration directory
 */
int UhttpServer::load_config_dir(const char *a_config_dir_path)
{
	DIR *the_rep = NULL;
	struct dirent *the_dir_ent = NULL;
	std::string the_path;
	struct json_object *the_root_node;
	the_rep = opendir(a_config_dir_path);
	if (the_rep == NULL) {
		fprintf (stderr, "Impossible to open the config directory (%s).\n", a_config_dir_path);
		return -1;
	}
	while ((the_dir_ent = readdir(the_rep)) != NULL)
	{
		if (strcmp(the_dir_ent->d_name, ".") != 0 && strcmp(the_dir_ent->d_name, "..") != 0)
		{
			// printf ("load: %s\n", the_dir_ent->d_name);
			the_path = a_config_dir_path;
			the_path += the_dir_ent->d_name;

			the_root_node = load(the_path);
			if (the_root_node == NULL)
			{
				fprintf(stderr, "Failed to load controller configuration file (%s).\n", the_path.c_str());
				return -1;
			}

			add_controller_from_json(the_root_node);
			json_object_put(the_root_node);
		}
	}

	closedir(the_rep);

	return 0;
}

/*!  ----------------------------------------------------------------------------
 *  @fn add_controller_from_json
 *
 *  @brief Load config from the configuration directory
 */
int UhttpServer::add_controller_from_json(struct json_object *a_root_node)
{
	struct json_object *the_ctr_array_node, *the_ctrl_node, *the_note_array_node, *the_etag_ctrl_array_node;

	std::string the_path, the_model_name, the_event_name, the_etag_key, the_get_method, the_set_method;
	bool the_raw_response = false;
	int the_len;
	int i;

	if (json_object_object_get_ex(a_root_node, kControllerKey, &the_ctr_array_node))
	{
		RestController *the_rest_controller;
		the_len = json_object_array_length(the_ctr_array_node);

		for (i = 0; i < the_len; i++)
		{
			the_ctrl_node = json_object_array_get_idx(the_ctr_array_node, i);
			the_rest_controller = new RestController;
			
			if (the_rest_controller->from_json(the_ctrl_node))
			{
				//new RestController(the_model_name, the_get_method, the_set_method, kDefaultTimeout, the_raw_response)
				add_controller(the_path, the_rest_controller);
			}
		}
	}
	if (json_object_object_get_ex(a_root_node, kEtagControllerKey, &the_etag_ctrl_array_node))
	{
		the_len = json_object_array_length(the_etag_ctrl_array_node);

		for (i = 0; i < the_len; i++)
		{
			the_ctrl_node = json_object_array_get_idx(the_etag_ctrl_array_node, i);

			if (get_controller_fields(the_ctrl_node, the_path, the_model_name, the_event_name, the_etag_key, the_get_method, the_set_method, the_raw_response))
			{
				EtagRestController *the_etag_rest_controller;
				the_etag_rest_controller = new EtagRestController(the_model_name, the_event_name, the_etag_key, the_get_method, the_set_method, kDefaultTimeout, the_raw_response);

				add_controller(the_path, the_etag_rest_controller);
			}
		}
	}
	if (json_object_object_get_ex(a_root_node, kNotificationKey, &the_note_array_node))
	{
		the_len = json_object_array_length(the_note_array_node);

		for (i = 0; i < the_len; i++)
		{
			the_ctrl_node = json_object_array_get_idx(the_note_array_node, i);

			if (get_controller_fields(the_ctrl_node, the_path, the_model_name, the_event_name, the_etag_key, the_get_method, the_set_method, the_raw_response))
			{
				NotificationController *the_notification_controller;
				the_notification_controller = new NotificationController(the_event_name);
				add_controller(the_path, the_notification_controller);
			}
		}
	}

	return 0;
}

/*!  ----------------------------------------------------------------------------
 *  @fn get_controller_fields
 *
 *  @brief This method extract the controller fields from a json node.
 */
bool UhttpServer::get_controller_fields(struct json_object *a_node, std::string &a_path, std::string &a_model_name,
										std::string &an_event_name, std::string &an_etag_key,
										std::string &a_get_method, std::string &a_set_method, bool &a_raw_response)
{
	bool the_result = false;

	struct json_object *the_path_node, *the_ubus_model_node, *the_ubus_event_node, *the_get_method_node, *the_etag_key_node;
	struct json_object *the_set_method_node, *the_raw_response_node;

	// path
	if (json_object_object_get_ex(a_node, kControllerPathKey, &the_path_node))
	{
		a_path = json_object_get_string(the_path_node);
		the_result = true;
	}
	else
	{
		a_path.clear();
	}
	// model_name
	if (json_object_object_get_ex(a_node, kControllerModelNameKey, &the_ubus_model_node))
	{
		a_model_name = json_object_get_string(the_ubus_model_node);
	}
	else
	{
		a_model_name.clear();
	}
	// event name
	if (json_object_object_get_ex(a_node, kControllerEventNameKey, &the_ubus_event_node))
	{
		an_event_name = json_object_get_string(the_ubus_event_node);
	}
	else
	{
		an_event_name.clear();
	}
	// etag key
	if (json_object_object_get_ex(a_node, kControllerEtagKey, &the_etag_key_node))
	{
		an_etag_key = json_object_get_string(the_etag_key_node);
	}
	else
	{
		an_etag_key.clear();
	}
	// get_method
	if (json_object_object_get_ex(a_node, kControllerGetMethodKey, &the_get_method_node))
	{
		a_get_method = json_object_get_string(the_get_method_node);
	}
	else
	{
		a_get_method = kdefaultGetMethod;
	}
	// set_method
	if (json_object_object_get_ex(a_node, kControllerSetMethodKey, &the_set_method_node))
	{
		a_set_method = json_object_get_string(the_set_method_node);
	}
	else
	{
		a_set_method = kdefaultSetMethod;
	}
	// raw_response
	if (json_object_object_get_ex(a_node, kControllerRawResponseKey, &the_raw_response_node))
	{
		a_raw_response = json_object_get_boolean(the_raw_response_node);
	}
	else
	{
		a_raw_response = false;
	}

	return the_result;
}

/*!  ----------------------------------------------------------------------------
 *  @fn load
 *
 *  @brief This method load the restd controllers and file it into a json object.
 *         return null otherwise.
 */
struct json_object *UhttpServer::load(const std::string &a_file)
{
	struct json_object *the_root_node = NULL;
	struct stat the_stat;
	int the_fd;
	char *the_addr;

	the_fd = open(a_file.c_str(), O_RDONLY);
	if (the_fd == -1)
	{
		perror("open");
		return NULL;
	}
	if (fstat(the_fd, &the_stat) == -1)
	{
		perror("fstat");
		close(the_fd);
		return NULL;
	}

	the_addr = (char *)mmap(NULL, the_stat.st_size, PROT_READ, MAP_PRIVATE, the_fd, 0);
	if (the_addr == MAP_FAILED)
	{
		perror("mmap");
		close(the_fd);
		return NULL;
	}

	the_root_node = json_tokener_parse(the_addr);

	close(the_fd);

	return the_root_node;
}

/*!  ----------------------------------------------------------------------------
 *  @fn add_rest_controller
 *
 *  @brief add a rest controller.
 */
int UhttpServer::add_controller(const std::string &an_uri, WebController *a_controller)
{
	std::string the_path;
	std::size_t the_pos;
	if (a_controller == NULL)
		return -1;

	a_controller->set_name(an_uri);
	//m_controller_array[an_uri] = a_controller;

	printf("add: an_uri:%s\n", an_uri.c_str());

#if 0
	// Keep a list of the API root. to check if a controller is managed by this plugin or not.
	the_pos = an_uri.find_first_of("/", 1);
	if (the_pos != std::string::npos)
	{
		the_path = an_uri.substr(0, the_pos + 1);
		if (std::find(m_path_list.begin(), m_path_list.end(), the_path) == m_path_list.end())
		{
			// uniq version. we add it.
			m_path_list.push_back(the_path);
		}
	}
#endif
	return 0;
}

/*!  ----------------------------------------------------------------------------
 *  @fn send_error
 *
 *  @brief Send an error message to the client.
 */
void UhttpServer::send_error(struct client *a_cl, int a_code, const char *a_summary, const std::string &a_msg)
{
	m_ops->http_header(a_cl, a_code, a_summary);
	ustream_printf(a_cl->us, "Content-Type: application/json\r\n\r\n");

	m_ops->chunk_printf(a_cl, "%s", a_msg.c_str());

	m_ops->request_done(a_cl);
}
