#include "powertrail.h"
#include "debug.h"
#include "common.h"

#include <regex>
#include <fstream>

#include <curl/curl.h>
#include <time.h>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

static const std::string url = "https://opencaching.pl/powerTrail.php?ptAction=showSerie&ptrail=";
static const std::string url2 = "https://opencaching.pl/powerTrail/ajaxGetPowerTrailCaches.php?ptAction=showSerie&ptrail=";

static const std::regex regex_name("<span id=\"powerTrailName\">(.*?)</span>");
static const std::regex regex_type("<span id=\"ptTypeName\">(.*?)</span>");
static const std::regex regex_date("<span id=\"powerTrailDateCreated\">(.*?)</span>");
static const std::regex regex_perc("<span id=\"powerTrailpercent\">(.*?)</span>");
static const std::regex regex_cache("<a href=\"(OP.*?)\">.*?<img src=\"/images/log/(.*?)\\.png\"");

void to_json(json& j, const Powertrail& item) {
	j = json{ { "name", item.name }, { "date", item.date_str }, { "treshold", item.treshold_perc }, { "active", item.active_caches }, { "caches", item.caches }, { "type", item.type } };
}

void from_json(const json& j, Powertrail& item) {
	j.at("name").get_to(item.name);
	j.at("date").get_to(item.date_str);
	j.at("treshold").get_to(item.treshold_perc);
	j.at("active").get_to(item.active_caches);
	j.at("caches").get_to(item.caches);
	j.at("type").get_to(item.type);
	strptime(item.date_str.c_str(), "%d-%m-%Y", &item.date);
	item.treshold = (item.caches.size() * item.treshold_perc + 99) / 100; // Integer division rounding up
	item.active_caches_not_found = item.active_caches;
}

std::string Powertrail::link() const {
	return "https://opencaching.pl/powerTrail.php?ptAction=showSerie&ptrail=" + std::to_string(number);
}

std::string Powertrail::link_name() const {
	return "<a href=\"" + link() + "\">" + safe_name() + "</a>";
}

std::string Powertrail::safe_name() const {
	std::string tmp = name;
	htmlencode(tmp);
	return tmp;
}
std::string Powertrail::get_type() const {
	return types[type];
}

PowertrailDB::PowertrailDB() {
	curl = curl_easy_init();
	if (!curl) {
		curl_global_cleanup();
		throw 0;
	}

	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&curl_output);
	curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5);
}

PowertrailDB::~PowertrailDB() {
	curl_easy_cleanup(curl);
}

void PowertrailDB::get_trail(uint n) {
	CURLcode curl_res;
	std::smatch res;

	Powertrail T;
	T.number = n;

	curl_output.clear();
	curl_easy_setopt(curl, CURLOPT_URL, (url + std::to_string(n)).c_str());
	curl_res = curl_easy_perform(curl);
	if (curl_res != CURLE_OK) {
		Debug(1) << "Connection failed: " << curl_easy_strerror(curl_res) << '\n';
		curl_easy_cleanup(curl);
		std::exit(EXIT_FAILURE);
	}

	if (std::regex_search(curl_output, res, regex_name) && res.size() == 2) {
		T.name = res[1].str();
		if (T.name.empty()) return;
		Debug(2) << "Read name: " << T.name;
	} else
		return;
	if (std::regex_search(curl_output, res, regex_type) && res.size() == 2) {
		if (res[1].str() == "Geo-szkic")
			T.type = geoszkic;
		else if (res[1].str() == "Krajoznawcza")
			T.type = krajoznawcza;
		else if (res[1].str() == "Przyrodnicza")
			T.type = przyrodnicza;
		else if (res[1].str() == "Tematyczna")
			T.type = tematyczna;
		else
			T.type = unknown_type;
	}
	if (std::regex_search(curl_output, res, regex_date) && res.size() == 2) {
		strptime(res[1].str().c_str(), "%d-%m-%Y", &T.date);
		T.date_str = res[1].str();
		Debug(2) << "Read time: " << T.date_str;
	}
	if (std::regex_search(curl_output, res, regex_perc) && res.size() == 2) {
		T.treshold_perc = std::stoi(res[1].str());
		Debug(2) << "Read percent: " << T.treshold_perc;
	}

	curl_output.clear();
	curl_easy_setopt(curl, CURLOPT_URL, (url2 + std::to_string(n)).c_str());
	curl_res = curl_easy_perform(curl);
	if (curl_res != CURLE_OK) {
		Debug(1) << "Connection failed: " << curl_easy_strerror(curl_res) << '\n';
		curl_easy_cleanup(curl);
		std::exit(EXIT_FAILURE);
	}

	auto caches_begin = std::sregex_iterator(curl_output.begin(), curl_output.end(), regex_cache);
	auto caches_end = std::sregex_iterator();

	Debug(2) << "Found " << std::distance(caches_begin, caches_end) << " caches.\n";

	for (std::sregex_iterator i = caches_begin; i != caches_end; i++) {
		std::smatch match = *i;
		T.caches.insert(match[1].str());
		if (match[2].str() == "16x16-published")
			T.active_caches++;
	}

	T.treshold = (T.caches.size() * T.treshold_perc + 99) / 100; // Integer division rounding up

	data.insert({ n, T });
}

// Callback for cURL easy interface used to save received output to std::string object
size_t PowertrailDB::write_cb(char* ptr, __attribute__((unused)) size_t size, size_t nmemb, void* userdata) {
	std::string* str = reinterpret_cast<std::string*>(userdata);
	str->append(ptr, nmemb);
	return nmemb;
}

void PowertrailDB::read_from_json(std::string file) {
	std::ifstream datafile(file);

	try {
		json j;
		datafile >> j;
		data = j.get<std::unordered_map<uint, Powertrail>>();
	}
	catch (...) {
		Debug(1) << "Cache file: " << file << " corrupted.";
		std::exit(EXIT_FAILURE);
	}

	for (auto& p : data)
		p.second.number = p.first;
};

void PowertrailDB::save_to_json(std::string file) {
	std::ofstream datafile(file);
	json j(data);
	datafile << j;
};

Caches_in_Powertrails::Caches_in_Powertrails(const PowertrailDB& db) {
	for (auto& t : db.data) {
		for (auto& c : t.second.caches) {
			data.insert({ c, t.second.number });
		}
	}
}

void Caches_in_Powertrails::read_from_json(std::string file) {
	std::ifstream datafile(file);

	try {
		json j;
		datafile >> j;
		data = j.get<std::unordered_map<std::string, int>>();
	}
	catch (...) {
		Debug(1) << "Cache file: " << file << " corrupted.";
		std::exit(EXIT_FAILURE);
	}
};

void Caches_in_Powertrails::save_to_json(std::string file) {
	std::ofstream datafile(file);
	json j(data);
	datafile << j;
};
