/* Copyright 2020, Michele Santullo
 * This file is part of orotool.
 *
 * Orotool is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Orotool 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Orotool.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "private/page_fetch.hpp"
#include "private/http_header_parse.hpp"
#include <curl_easy.h>
#include <curl_pair.h>
#include <curl_ios.h>
#include <curl_header.h>
#include <sstream>
#include <algorithm>
#include <cassert>
#include <tuple>

namespace nap {
namespace {
	bool is_https (std::string_view parUrl) {
		const char protocol[] = "https://";
		const size_t protocolLen = sizeof(protocol) / sizeof(protocol[0]) - 1;
		if (parUrl.size() < protocolLen)
			return false;

		return std::equal(protocol, protocol + protocolLen, parUrl.begin());
	}

	std::tuple<std::unique_ptr<char[]>, std::string_view, std::string_view> make_raw_string (
		const std::ostringstream& head,
		const std::ostringstream& body,
		std::size_t body_padding
	) {
		//TODO: use .view() with c++20
		auto head_str = head.str();
		auto body_str = body.str();

		const std::size_t size = head_str.size() + 1 + body_str.size() + body_padding;
		auto ret = std::make_unique<char[]>(size);

		std::copy(head_str.begin(), head_str.end(), ret.get());
		ret[head_str.size()] = '\n';
		std::copy(body_str.begin(), body_str.end(), ret.get() + head_str.size() + 1);
		std::fill(ret.get() + size - body_padding, ret.get() + size, ' ');

		const char* const buff = ret.get();
		return std::make_tuple(
			std::move(ret),
			std::string_view(buff, head_str.size()),
			std::string_view(buff + head_str.size() + 1, body_str.size())
		);
	}
} //unnamed namespace

HttpResponse page_fetch (
	const std::string& url,
	const std::string& user_agent,
	const PageFetchHeaders& headers,
	std::size_t body_padding
) {
	using curl::curl_pair;

	std::ostringstream body_oss;
	std::ostringstream header_oss;
	curl::curl_ios<std::ostringstream> body(body_oss);
	curl::curl_ios<std::ostringstream> header(header_oss);
	curl::curl_easy easy;

	easy.add<CURLOPT_WRITEFUNCTION>(header.get_function());
	easy.add<CURLOPT_HEADERDATA>(header.get_stream());
	easy.add<CURLOPT_WRITEDATA>(body.get_stream());

	easy.add(curl_pair<CURLoption, std::string>(CURLOPT_URL, url));
	easy.add<CURLOPT_FOLLOWLOCATION>(1L);
	if (is_https(url)) {
		easy.add<CURLOPT_SSL_VERIFYPEER>(true);
		easy.add<CURLOPT_SSL_VERIFYHOST>(true);
	}
	easy.add<CURLOPT_ACCEPT_ENCODING>("gzip");
	easy.add<CURLOPT_HTTP_CONTENT_DECODING>(1L);
	easy.add<CURLOPT_VERBOSE>(0L);
	easy.add(curl_pair<CURLoption, std::string>(CURLOPT_USERAGENT, user_agent));

	curl::curl_header ch;
	ch.add(headers.begin(), headers.end());
	easy.add<CURLOPT_HTTPHEADER>(ch.get());

	easy.perform();

	HttpResponse resp;
	resp.code = easy.get_info<CURLINFO_RESPONSE_CODE>().get();

	{
		auto [raw, head_tmp, body] = make_raw_string(header_oss, body_oss, body_padding);
		resp.raw = std::move(raw);
		resp.header = head_tmp;
		resp.body = body;
	}

	auto parsed_header = header_parse(resp.header);
	resp.header_list = std::move(parsed_header.fields);
	assert(resp.code == parsed_header.code);
	resp.http_ver = parsed_header.version;
	resp.code_desc = parsed_header.message;
	return resp;
}
} //namespace nap
