#include "common.h"
#include "cache.h"
#include "debug.h"

#include <algorithm>
#include <numeric>
#include <iterator>

void htmlencode(std::string& data) {
	std::string tmp;
	tmp.reserve(data.size() + 20);

	for (size_t i = 0; i != data.size(); i++) {
		switch (data[i]) {
			case '&':
				tmp += "&amp;";
				break;
			case '\"':
				tmp += "&quot;";
				break;
			case '\'':
				tmp += "&apos;";
				break;
			case '<':
				tmp += "&lt;";
				break;
			case '>':
				tmp += "&gt;";
				break;
			default:
				tmp += data[i];
				break;
		}
	}

	data.swap(tmp);
}

constexpr static std::string string_mangle(const std::string& str) {
	std::string tmp;
	tmp.reserve(str.size());

	for (size_t i = 0; i != str.size(); i++) {
		if (str[i] == ' ') continue;
		if (std::isupper(str[i]))
			tmp.push_back(std::tolower(str[i]));
		else
			tmp.push_back(str[i]);
	}
	return tmp;
}

void show_histogram(const pCaches& cc, std::string Cache::*ptr, const std::string& caption, bool html, bool sort_by_val) {
	if (cc.empty()) return;

	std::map<std::string, int> histogram;

	for (auto el : cc)
		histogram[el->*ptr]++;

	show_histogram(histogram, caption, html, sort_by_val);
}

void show_histogram(const Caches& cc, std::string Cache::*ptr, const std::string& caption, bool html, bool sort_by_val) {
	if (cc.empty()) return;

	std::map<std::string, int> histogram;

	for (auto el : cc)
		histogram[el.*ptr]++;

	show_histogram(histogram, caption, html, sort_by_val);
}

// void show_histogram(const pPowertrails& tt, const std::string& caption, bool html, bool sort_by_val) {
// 	if (tt.empty()) return;
//
// 	std::map<std::string, int> histogram;
//
// 	for (auto el : tt) {
// 		std::string link = "<a href=\"https://opencaching.pl/powerTrail.php?ptAction=showSerie&ptrail=" + std::to_string(el.second->number) + "\">" + el.second->name + "</a>";
// 		histogram[link] = 100 * el.second->found / el.second->caches.size();
// 	}
//
// 	show_histogram(histogram, caption, html, sort_by_val);
// }

void show_histogram(const std::map<std::string, int>& data, const std::string& caption, bool html, bool sort_by_val) {
	if (data.empty()) return;

	int HIST_MAX = 20;

	std::vector<std::pair<std::string, int>> pairs;

	if (!sort_by_val) HIST_MAX = 1000;

	std::copy(data.begin(), data.end(), std::back_inserter(pairs));

	if (sort_by_val)
		sort(pairs.begin(), pairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.second > b.second; });
	else
		sort(pairs.begin(), pairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.first < b.first; });

	if (html) {
		int max;
		if (sort_by_val)
			max = pairs[0].second;
		else
			max = std::max_element(pairs.begin(), pairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.second < b.second; })->second;
		int i = 0;
		std::cout << "<h2>" << caption << "</h2>\n";
		std::cout << "<div class=\"histogram " << string_mangle(caption) << "\">\n";
		for (auto own : pairs) {
			if (!own.first.starts_with("<a href=")) htmlencode(own.first);
			if (own.first.empty()) own.first = "[unknown]";

			if (i < HIST_MAX)
				std::cout << "<div class=\"bar\" style=\"--percent: " << 100 * own.second / max << "%;\"><span class=\"text\">" << own.first << ": " << own.second << "</span></div>\n";
			else if (i == HIST_MAX) {
				std::cout << "</div>\n";
				std::cout << "<details class=\"histogram_others " << string_mangle(caption) << "\">\n<summary>See more</summary>\n<p>\n";
				std::cout << own.first << " (" << own.second << ")<br>";
			}
			if (i > HIST_MAX)
				std::cout << own.first << " (" << own.second << ")<br>";
			i++;
		}
		if (i <= HIST_MAX)
			std::cout << "</div>\n";
		else
			std::cout << "</p></details>\n";
	} else {
		std::cout << caption << '\n';
		for (auto own : pairs)
			std::cout << own.first << ": " << own.second << '\n';
	}
}

void show_nested_histogram(const pCaches& fcc, std::string Cache::*ptr, std::string Cache::*ptr2, const std::string& caption, bool html, bool sort_by_val) {
	if (fcc.empty()) return;

	std::map<std::string, int> histogram;
	std::vector<std::pair<std::string, int>> pairs;

	for (auto el : fcc)
		histogram[el->*ptr]++;
	std::copy(histogram.begin(), histogram.end(), std::back_inserter(pairs));

	if (sort_by_val)
		sort(pairs.begin(), pairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.second > b.second; });
	else
		sort(pairs.begin(), pairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.first < b.first; });

	if (html) {
		int HIST_MAX = 20;
		int max;

		if (sort_by_val)
			max = pairs[0].second;
		else
			max = std::max_element(pairs.begin(), pairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.second < b.second; })->second;
		int i = 0;
		std::cout << "<h2>" << caption << "</h2>\n";
		std::cout << "<div class=\"histogram nested " << string_mangle(caption) << "\">\n";
		for (auto own : pairs) {
			htmlencode(own.first);
			if (own.first.empty()) own.first = "[unknown]";
			if (i < HIST_MAX) {
				std::map<std::string, int> subhistogram;
				for (auto el : fcc)
					if (el->*ptr == own.first && !(el->*ptr2).empty()) //TODO problem with [unknown]
						subhistogram[el->*ptr2]++;
				if (!subhistogram.empty()) {
					std::vector<std::pair<std::string, int>> subpairs;
					std::cout << "<details class=\"nested\"><summary class=\"bar\" style=\"--percent: " << 100 * own.second / max << "%;\"><span class=\"text\">" << own.first << ": " << own.second << "</span></summary>\n";
					std::copy(subhistogram.begin(), subhistogram.end(), std::back_inserter(subpairs));
					if (sort_by_val)
						sort(subpairs.begin(), subpairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.second > b.second; });
					else
						sort(subpairs.begin(), subpairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.first < b.first; });

					std::cout << "<div class=\"histogram inside\">\n";
					for (auto own : subpairs) {
						htmlencode(own.first);
						if (own.first.empty()) own.first = "[unknown]";
						std::cout << "<div class=\"bar\" style=\"--percent: " << 100 * own.second / max << "%;\"><span class=\"text\">" << own.first << ": " << own.second << "</span></div>\n";
					}
					std::cout << "</div>\n";
					std::cout << "</details>\n";
				} else {
					std::cout << "<div class=\"bar\" style=\"--percent: " << 100 * own.second / max << "%;\"><span class=\"text\">" << own.first << ": " << own.second << "</span></div>\n";
				}
			} else if (i == HIST_MAX) {
				std::cout << "</div>\n";
				std::cout << "<details class=\"histogram_others " << string_mangle(caption) << "\">\n<summary>See more</summary>\n<p>\n";
				std::cout << own.first << " (" << own.second << ")";
			}
			if (i > HIST_MAX)
				std::cout << ", " << own.first << " (" << own.second << ")";
			i++;
		}
		if (i <= HIST_MAX)
			std::cout << "</div>\n";
		else
			std::cout << "</p></details>\n";
	} else { //TODO text-only version not implemented, displays only first-level list
		std::cout << caption << '\n';
		for (auto own : pairs)
			std::cout << own.first << ": " << own.second << '\n';
	}
}

uint find_streak(const Date_Caches& cc, std::time_t& start) {
	int max_str = 0;
	int cur_str = 0;

	int cur = 0;
	int cur_start = cur;
	int max_start = cur;

	for (auto i : cc) {
		if (cur == i.first / 86400)
			continue;
		if (i.first / 86400 - cur == 1) {
			cur_str++;
			cur = i.first / 86400;
			continue;
		}

		// streak ended

		if (max_str < cur_str) {
			max_str = cur_str;
			max_start = cur_start;
		}
		cur_str = 1;
		cur = cur_start = i.first / 86400;
		if (max_str == 0) {
			max_str = 1;
			max_start = cur;
		}
	}

	if (max_str < cur_str) {
		max_str = cur_str;
		max_start = cur_start;
	}

	start = max_start * 86400; // convert back from month count to time_t
	return max_str;
}

uint get_num(char c, char* opt) {
	try {
		if (std::stoi(opt) > 0) {
			return std::stoi(opt);
		} else {
			std::cout << "Option \"-" << c << "\" requires a valid number as an argument\n";
			std::exit(EXIT_FAILURE);
		}
	}
	catch (...) {
		std::cout << "Option \"-" << c << "\" requires a valid number as an argument\n";
		std::exit(EXIT_FAILURE);
	}
}

template void average_html(const Caches& cc, float Cache::*ptr, const std::string& caption);
template void average_html(const Caches& cc, uint Cache::*ptr, const std::string& caption);
template void average_html(const Caches& cc, int Cache::*ptr, const std::string& caption);

template <typename T>
void average_html(const Caches& cc, T Cache::*ptr, const std::string& caption) {
	std::cout << "<div class=\"basic_stats\">\n";
	std::cout << "Average " << caption << ": <span class=\"value\">" << average(cc, ptr) << "</span><br>\n";
	std::cout << "</div>\n";
}

template float average(const Caches& cc, float Cache::*ptr);
template float average(const Caches& cc, int Cache::*ptr);
template float average(const Caches& cc, uint Cache::*ptr);

template <typename T>
float average(const Caches& cc, T Cache::*ptr) {
	return 1.0 * std::accumulate(cc.begin(), cc.end(), 0., [&](const float& a, const Cache& b) { return std::move(a) + b.*ptr; }) / cc.size();
}

template void sum_html(const Caches& cc, float Cache::*ptr, const std::string& caption);
template void sum_html(const Caches& cc, int Cache::*ptr, const std::string& caption);
template void sum_html(const Caches& cc, uint Cache::*ptr, const std::string& caption);

template <typename T>
void sum_html(const Caches& cc, T Cache::*ptr, const std::string& caption) {
	std::cout << "<div class=\"basic_stats\">\n";
	std::cout << "Total " << caption << ": <span class=\"value\">" << sum(cc, ptr) << "</span><br>\n";
	std::cout << "</div>\n";
}

template float sum(const Caches& cc, float Cache::*ptr);
template int sum(const Caches& cc, int Cache::*ptr);
template uint sum(const Caches& cc, uint Cache::*ptr);

template <typename T>
T sum(const Caches& cc, T Cache::*ptr) {
	return std::accumulate(cc.begin(), cc.end(), 0, [&](const int& a, const Cache& b) { return std::move(a) + b.*ptr; });
}

int count_caches(const Caches& cc, std::string& codes, std::function<bool(const Cache& c)> test) {
	int count = 0;
	codes.clear();
	for (auto& c : cc) {
		if (test(c)) {
			count++;
			codes += c.code;
			codes += ' ';
		}
	}
	codes.pop_back();
	return count;
}

void header_html() {
	std::cout << "<!DOCTYPE html>\n";
	std::cout << "<html lang=\"en\">\n";
	std::cout << " <head>\n";
	std::cout << "  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n";
	std::cout << "  <link rel=\"stylesheet\" type=\"text/css\" href=\"geo.css?ver=16\">\n";
	std::cout << "  <title>Geocaching stats</title>\n";
	std::cout << "</head>\n";
	std::cout << "<body>\n";
}

void footer_html() {
	std::cout << "<footer>\n";
	std::cout << "Stats generated by <a href=\"https://gitlab.com/tomaszg/geostat\">GeoStat</a>.<br>\n";
	std::cout << "Data obtained from Opencaching sites via <a href=\"https://github.com/opencaching/okapi/\">OKAPI</a>:<br>\n";
	std::cout << "<a href=\"https://www.opencaching.pl/\">Opencaching.pl</a>, ";
	std::cout << "<a href=\"https://www.opencaching.de/\">Opencaching.de</a>, ";
	std::cout << "<a href=\"http://www.opencaching.us/\">Opencaching.us</a>, ";
	std::cout << "<a href=\"http://www.opencaching.nl/\">Opencaching.nl</a>, ";
	std::cout << "<a href=\"http://www.opencaching.ro/\">Opencaching.ro</a>, ";
	std::cout << "<a href=\"https://www.opencache.uk/\">Opencaching.uk</a>.";
	std::cout << "</footer>\n";
	std::cout << "</body>\n</html>\n";
}

Position Cache::home;

Position::Position(std::string loc) {
	int pos = loc.find("|");
	lat = stof(loc.substr(0, pos));
	lon = stof(loc.substr(pos + 1));
}

float cache_distance(const Cache& a, const Cache& b) {
	return 2 * Earth_radius * asin(sqrt(pow(sin(degtorad((a.pos.lat - b.pos.lat) / 2)), 2) + cos(degtorad(a.pos.lat)) * cos(degtorad(b.pos.lat)) * pow(sin(degtorad((a.pos.lon - b.pos.lon) / 2)), 2)));
}
