/*!
 * 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/ustream.h>
#include <libubox/blobmsg_json.h>
}

#include <json-c/json.h>

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

#include "http-reason.h"

#include "http-header.h"

const char *const g_http_versions[] = {
	[UH_HTTP_VER_0_9] = "HTTP/0.9",
	[UH_HTTP_VER_1_0] = "HTTP/1.0",
	[UH_HTTP_VER_1_1] = "HTTP/1.1",
};

/*!  ----------------------------------------------------------------------------
 *  @fn HttpHeader
 *
 *  @brief constructor of the http header.
 */
HttpHeader::HttpHeader(void) : m_origin("*"),
							   m_timeout(0)
{
}

/*!  ----------------------------------------------------------------------------
 *  @fn HttpHeader
 *
 *  @brief constructor of the http header.
 */
HttpHeader::HttpHeader(uint16_t a_timeout) : m_origin("*"),
											 m_timeout(a_timeout)
{
}

/*!  ----------------------------------------------------------------------------
 *  @fn send
 *
 *  @brief send an http header response.
 */
void HttpHeader::send(uhttpd_ops *an_ops, client *a_client, uint16_t a_code, bool a_chunked)
{
	prepare_header(a_client, a_code, HttpReason::get(a_code));
	ustream_printf(a_client->us, "Access-Control-Allow-Origin: %s\r\n", m_origin.c_str());

	if (!m_etag.empty())
	{
		ustream_printf(a_client->us, "ETag: %s\r\n", m_etag.c_str());
	}

	ustream_printf(a_client->us, "Content-Type: application/json\r\n");

	if (!a_chunked)
		ustream_printf(a_client->us, "\r\n");
}

/*!  ----------------------------------------------------------------------------
 *  @fn send_option
 *
 *  @brief send an http header option response.
 */
void HttpHeader::send_option(uhttpd_ops *an_ops, client *a_client, uint16_t a_code)
{
	prepare_header(a_client, a_code, HttpReason::get(a_code));
	ustream_printf(a_client->us, "Access-Control-Allow-Headers: Accept, Content-Type, If-None-Match, Prefer\r\n");
	ustream_printf(a_client->us, "Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS\r\n");
	ustream_printf(a_client->us, "Access-Control-Allow-Origin: %s\r\n", m_origin.c_str());
	ustream_printf(a_client->us, "Content-Length: 0\r\n\r\n");

	an_ops->request_done(a_client);
}

/*!  ----------------------------------------------------------------------------
 *  @fn add_origin
 *
 *  @brief add an etag to the header request.
 */
void HttpHeader::add_origin(const std::string &an_origin)
{
	m_origin = an_origin;
}

/*!  ----------------------------------------------------------------------------
 *  @fn add_etag
 *
 *  @brief add an etag to the header request.
 */
void HttpHeader::add_etag(const std::string &an_etag)
{
	m_etag = an_etag;
}

/*!  ----------------------------------------------------------------------------
 *  @fn add_timeout
 *
 *  @brief change the time out of the header request.
 */
void HttpHeader::add_timeout(uint16_t a_timeout)
{
	m_timeout = a_timeout;
}

/*!  ----------------------------------------------------------------------------
 *  @fn use_chunked
 *
 *  @brief return true if the connection could use a chunked response.
 */
bool HttpHeader::use_chunked(struct client *a_client)
{
	if (a_client->request.version != UH_HTTP_VER_1_1)
		return false;

	if (a_client->request.method == UH_HTTP_MSG_HEAD || a_client->request.method == UH_HTTP_MSG_OPTIONS)
		return false;

	/* RFC2616 10.2.5, 10.3.5 */
	if (a_client->http_code == 204 || a_client->http_code == 304)
		return false;

	return !a_client->request.disable_chunked;
}

/*!  ----------------------------------------------------------------------------
 *  @fn prepare_header
 *
 *  @brief prepare and fill the first part of the http header.
 */
void HttpHeader::prepare_header(struct client *a_client, int a_code, const std::string &a_summary)
{
	struct http_request *the_req = &a_client->request;
	const char *the_enc = "Transfer-Encoding: chunked\r\n";
	const char *the_conn;

	a_client->http_code = a_code;

	if (!use_chunked(a_client))
		the_enc = "";

	if (the_req->connection_close)
		the_conn = "Connection: close";
	else
		the_conn = "Connection: Keep-Alive";

	ustream_printf(a_client->us, "%s %03i %s\r\n%s\r\n%s",
				   g_http_versions[a_client->request.version],
				   a_code, a_summary.c_str(), the_conn, the_enc);

	if (!the_req->connection_close)
		ustream_printf(a_client->us, "Keep-Alive: timeout=%d\r\n", m_timeout);
}
