#include "debug.h"
#include "heat.h"
#include "common.h"
#include "user.h"

#include <iostream>
#include <algorithm>
#include <numeric>
#include <unistd.h>
#include <cmath>

void show_usage() {
	std::cout << "Usage: geostat [options]\n";
	std::cout << "Generate HTML stats from Opencaching data.\n\n";
	std::cout << " * Data sources:\n";
	std::cout << "\t-o[user]\tuse Opencaching; if specified user will be a user name used for all OC instances\n";
	std::cout << "\t-p user\t\tuser for opencaching.pl\n";
	std::cout << "\t-d user\t\tuser for opencaching.de\n";
	std::cout << "\t-u user\t\tuser for opencaching.us\n";
	std::cout << "\t-n user\t\tuser for opencaching.nl\n";
	std::cout << "\t-r user\t\tuser for opencaching.ro\n";
	std::cout << "\t-k user\t\tuser for opencaching.uk\n";
// 	std::cout << "\t-g file\t\tuse specified gpx file\n";
	std::cout << "\t-q\t\tuse local SQLite file with dump of OC database\n";
	std::cout << "\t-i timestamp\tstart date\n";
	std::cout << "\t-f timestamp\tfinish date\n";
	std::cout << " * Output:\n";
	std::cout << "\t-N\t\tcompute stats for unfound caches (works only with SQLite)\n";
	std::cout << "\t-O\t\tcompute stats for owned caches (works only with SQLite)\n";
	std::cout << "\t-Q\t\texclude quiz caches from unfound caches\n";
	std::cout << "\t-H file\t\trender a heat map to a file\n";
	std::cout << "\t-s n\t\tstamp size for a heat map (default = 15)\n";
	std::cout << "\t-e\t\tuse exponential to flatten the heat map\n";
	std::cout << "\t-t\t\tdraw trail instead of heat map\n";
	std::cout << "\t-a\t\tdraw animated map instead of heat map\n";
	std::cout << "\t-m map\t\tchosen map: Poland, Poland_relief, Poland_big, Europe, World or a name of voivodeship (default = Poland)\n";
	std::cout << "\t-h\t\tdisplay this help screen\n";
	std::exit(EXIT_FAILURE);
}

int main(int argc, char** argv) {
	std::string heat_file;
	int heat_stamp_size = 15;
	std::string heat_map = "Poland";
	User user;
	bool heat_exp = 0;
	bool trail = 0;
	bool anim = 0;

	if (argc == 1) show_usage();

	int o;
	while ((o = getopt(argc, argv, "qNOQD:o::p:d:u:n:r:k:i:f:H:s:m:etah?")) != -1)
		switch (o) {
			case 'D':
				Debug::set_debug_level(get_num('d', optarg));
				break;
// 			case 'g':
// 				gpx_file = optarg;
// 				break;
			case 'o':
				user.use_oc = 1;
				if (optarg) {
					user.ocpl_user = optarg;
					user.ocde_user = optarg;
					user.ocus_user = optarg;
					user.ocnl_user = optarg;
					user.ocro_user = optarg;
					user.ocuk_user = optarg;
				}
				break;
			case 'p':
				user.ocpl_user = optarg;
				break;
			case 'd':
				user.ocde_user = optarg;
				break;
			case 'u':
				user.ocus_user = optarg;
				break;
			case 'n':
				user.ocnl_user = optarg;
				break;
			case 'r':
				user.ocro_user = optarg;
				break;
			case 'k':
				user.ocuk_user = optarg;
				break;
			case 'q':
				user.use_ocdb = 1;
				break;
			case 'i':
				user.start_time = get_num('i', optarg);
				user.time_filter = 1;
				break;
			case 'f':
				user.end_time = get_num('f', optarg);
				user.time_filter = 1;
				break;
			case 'N':
				user.get_not_found = 1;
				break;
			case 'O':
				user.get_owned = 1;
				break;
			case 'Q':
				user.exclude_quiz = 1;
				break;
			case 'H':
				heat_file = optarg;
				break;
			case 's':
				heat_stamp_size = get_num('s', optarg);
				break;
			case 'm':
				heat_map = optarg;
				break;
			case 'e':
				heat_exp = 1;
				break;
			case 't':
				trail = 1;
				break;
			case 'a':
				anim = 1;
				break;
			case 'h':
			case '?':
			default:
				show_usage();
		}

	if (user.get_not_found || user.get_owned) {
		user.use_oc = 0;
		user.use_ocdb = 1;
		trail = 0;
		if (user.ocpl_user_uuid.empty() && user.ocpl_user.empty() && user.ocde_user_uuid.empty() && user.ocde_user.empty()) {
			std::cout << "Options \"-N\" or \"-O\" work only with OCpl or OCde.\n";
			std::exit(EXIT_FAILURE);
		}
		if (user.get_not_found && user.get_owned) {
			std::cout << "Options \"-N\" and \"-O\" are mutually exclusive.\n";
			std::exit(EXIT_FAILURE);
		}
	}

	if (trail && anim) {
		std::cout << "Options \"-a\" and \"-t\" are mutually exclusive.\n";
		std::exit(EXIT_FAILURE);
	}

	user.get_caches();

	if (user.caches_count == 0) {
		std::cout << "No caches found, aborting.\n";
		std::exit(EXIT_FAILURE);
	}

	user.prepare_lists_of_caches();

	header_html();

	std::cout << "<header>\n";
	std::cout << "<h1><a href=\"/geo\">Geocaching stats</a> for user profiles:</h1>\n";
	user.header();

	if (user.time_filter) {
		std::cout << "for time range " << std::put_time(std::localtime(&user.start_time), "%x") << "&mdash;" << std::put_time(std::localtime(&user.end_time), "%x");
	}

	std::cout << "</header>\n";

	if (!heat_file.empty()) {
		const Map* chosen_map;
		if (maps.count(heat_map) > 0)
			chosen_map = &maps.at(heat_map);
		else {
			std::cout << "Map " << heat_map << " not found.\n";
			std::exit(EXIT_FAILURE);
		}
		Heat hmap(chosen_map);
		if (trail) {
			if (user.sorted_fcaches.size() < 2) {
				std::cout << "You need at least 2 caches for a trail.\n";
				std::exit(EXIT_FAILURE);
			}
			hmap.generate_path(heat_file, user.sorted_fcaches);
		} else if (anim)
			hmap.generate_anim(heat_file, user.sorted_fcaches, heat_stamp_size);
		else
			hmap.generate(heat_file, user.fcc, heat_stamp_size, (heat_exp == 1 ? "exp" : "soft"));
		if (!anim)
			std::cout << "<figure>\n<img class=\"heatmap\" src=\"" << heat_file << "\" alt=\"heat map\">\n</figure>\n";
		else {
			std::cout << "<script src=\"components/webcomponentsjs/webcomponents-loader.js\"></script>\n<script src=\"components/gif-player/dist/gif-player.es6.js\" defer async></script>";
			std::cout << "<figure class=\"heatmap\">\n<gif-player class=\"heatmap\" src=\"" << heat_file << "\" alt=\"heat map\" speed=\"1\" play prerender></gif-player>\n</figure>\n";
			std::cout << "<noscript>\n";
			std::cout << "<figure>\n<img class=\"heatmap\" src=\"" << heat_file << "\" alt=\"heat map\">\n</figure>\n";
			std::cout << "</noscript>\n";
		}
	}

	if (!user.get_not_found && !user.get_owned) {
		short count;
		std::string codes;
		int n;

		std::cout << "<div class=\"basic_stats\">\n";
		std::cout << "Number of caches found: <span class=\"value\">" << user.cc.size() << "</span><br>\n";

		auto best_day = std::max_element(user.dates.begin(), user.dates.end(), [&](const auto& a, const auto& b) { return a.second < b.second; });
		std::cout << "Number of caching days: <span class=\"value\">" << user.dates.size() << "</span><br>\n";
		std::cout << "Average caches per caching day: <span class=\"value\">" << std::setprecision(3) << (1.0 * user.cc.size()) / user.dates.size() << "</span><br>\n";
		std::cout << std::resetiosflags(std::cout.flags());
		std::cout << "Number of caches that are now archived: <span class=\"value\">" << std::count_if(user.cc.begin(), user.cc.end(), [&](const auto& a) { return a.status == archived; }) << "</span><br>\n";
		std::cout << "<details class=\"days\"><summary>Best caching day: <span class=\"value\">" << best_day->first << "</span>, found <span class=\"value\">" << best_day->second << "</span> caches</summary>\n";
		std::cout << "  <p>\n";
		for (const auto& i : user.dates)
			std::cout << i.first << ", found " << i.second << " caches<br>\n";
		std::cout << "  </p>\n</details>\n";

		std::time_t streak;
		int longest_str = find_streak(user.sorted_caches, streak);
		char str_tmp[20];
		std::tm* str_tm = std::localtime(&streak);
		std::strftime(str_tmp, 20, "%F", str_tm);
		std::cout << "Longest caching streak: <span class=\"value\">" << longest_str << "</span> starting on <span class=\"value\">" << str_tmp << "</span><br>\n";

		float tot_dist = 0;
		for (auto i = user.sorted_fcaches.begin(); i != std::prev(user.sorted_fcaches.end()); i++) {
			//std::cout << "Distance between " << i->second->name << " and " << std::next(i)->second->name << " is " << cache_distance(*i->second, *std::next(i)->second) << "<br>";
			tot_dist += cache_distance(*i->second, *std::next(i)->second);
		}
		std::cout << "Total distance between caches: <span class=\"value\">" << std::fixed << tot_dist << "</span> km (equivalent to <span class=\"value\">" << tot_dist / (2 * M_PI * Earth_radius) << "x</span> trips around Earth or <span class=\"value\">" << tot_dist / Moon_dist << "x</span> trips to the Moon)<br>\n";
		std::cout << std::resetiosflags(std::cout.flags());
		if (user.sorted_fcaches.size() > 1)
			std::cout << "Average distance between caches: <span class=\"value\">" << tot_dist / (user.sorted_fcaches.size() - 1) << "</span> km<br>\n";
		std::cout << "</div>\n";

		short y_min = std::min_element(user.cc.begin(), user.cc.end(), [&](const Cache& a, const Cache& b) { return a.date_tm.tm_year < b.date_tm.tm_year; })->date_tm.tm_year;
		short y_max = std::max_element(user.cc.begin(), user.cc.end(), [&](const Cache& a, const Cache& b) { return a.date_tm.tm_year < b.date_tm.tm_year; })->date_tm.tm_year;

		long max = 0; // maximal value for histogram
		for (int i = y_min; i <= y_max; i++)
			for (int j = 1; j <= 12; j++)
				max = std::max(max, std::count_if(user.cc.begin(), user.cc.end(), [i, j](Cache c) { return (c.date_tm.tm_year == i && c.date_tm.tm_mon == j - 1); }));

		if (max > 0) {
			std::cout << "<h2>Caching timeline</h2>\n";
			std::cout << "<table class=\"calendar_tab\">\n";
			std::cout << "<tr><th></th>";
			for (int j = 1; j <= 12; j++) { // print table month headers
				std::cout << "<th>" << j << "</th>";
			}
			std::cout << "</tr>\n";

			for (int i = y_min; i <= y_max; i++) { // i -> years in rows
				std::cout << "<tr><th>" << i + 1900 << "</th> ";
				for (int j = 1; j <= 12; j++) { // j -> months in cols
					count = std::count_if(user.cc.begin(), user.cc.end(), [i, j](Cache c) { return (c.date_tm.tm_year == i && c.date_tm.tm_mon == j - 1); });
					std::cout << "<td style=\"--percent: " << count * 100 / max << "%\"><span>" << count << "</span></td>";
				}
				std::cout << "</tr>\n";
			}
			std::cout << "</table>\n";
		}

		std::cout << "<h2>Caching days per month</h2>\n";
		std::cout << "<table class=\"calendar_tab\">\n";
		std::cout << "<tr><th></th>";
		for (int j = 1; j <= 12; j++) { // print table month headers
			std::cout << "<th>" << j << "</th>";
		}
		std::cout << "</tr>\n";

		max = 31; // maximal value for histogram

		for (int i = y_min; i <= y_max; i++) { // i -> years in rows
			std::cout << "<tr><th>" << i + 1900 << "</th> ";
			for (int j = 1; j <= 12; j++) { // j -> months in cols
				std::map<int, int> days_count;
				for (auto el : user.cc) {
					if (el.date_tm.tm_mon == j - 1 && el.date_tm.tm_year == i)
						days_count[el.date_tm.tm_mday]++;
				}
				std::cout << "<td style=\"--percent: " << days_count.size() * 100 / max << "%\"><span>" << days_count.size() << "</span></td>";
			}
			std::cout << "</tr>\n";
		}
		std::cout << "</table>\n";

// 		auto far = std::max_element(fcc.begin(), fcc.end(), [&](const Cache& a, const Cache& b) { return a.distance() < b.distance(); });
// 		auto near = std::min_element(fcc.begin(), fcc.end(), [&](const Cache& a, const Cache& b) { return a.distance() < b.distance(); });
//
// 		std::cout << "Nearest cache: " << near->distance() << " km\n";
// 		near->show();
// 		std::cout << "Furthest cache: " << far->distance() << " km\n";
// 		far->show();

// 		show_histogram(cc, &Cache::year_month, "Caching timeline", 1, 0);
		show_histogram(user.cc, &Cache::owner, "Cache owners", 1);
		show_histogram(user.cc, &Cache::type, "Cache types", 1);
		show_histogram(user.cc, &Cache::size, "Cache sizes", 1);
// 		show_histogram(fcc, &Cache::region, "Regions", 1);
// 		show_histogram(fcc, &Cache::subregion, "Subregions", 1);
		show_histogram(user.fcc, &Cache::country, "Countries", 1);
		show_nested_histogram(user.fcc, &Cache::region, &Cache::subregion, "Regions", 1);
		show_histogram(user.cc, &Cache::day_of_week, "Days of the week", 1, 0);
		show_histogram(user.cc, &Cache::mon, "Months", 1, 0);
		show_histogram(user.cc, &Cache::year, "Years", 1, 0);

		auto N = *std::max_element(user.fcc.begin(), user.fcc.end(), [&](const Cache* a, const Cache* b) { return a->pos.lat < b->pos.lat; });
		auto S = *std::min_element(user.fcc.begin(), user.fcc.end(), [&](const Cache* a, const Cache* b) { return a->pos.lat < b->pos.lat; });
		auto E = *std::max_element(user.fcc.begin(), user.fcc.end(), [&](const Cache* a, const Cache* b) { return a->pos.lon < b->pos.lon; });
		auto W = *std::min_element(user.fcc.begin(), user.fcc.end(), [&](const Cache* a, const Cache* b) { return a->pos.lon < b->pos.lon; });

		std::cout << "<h2>Geographically extreme caches</h2>\n";
		std::cout << "<table class=\"list\">\n";
		std::cout << "<tr>\n";
		std::cout << "  <th>North:</th><td>" << N->link_name() << "</td><td>" << N->region << "</td><td>" << N->pos.lat << "</td>\n";
		std::cout << "</tr><tr>\n";
		std::cout << "  <th>South:</th><td>" << S->link_name() << "</td><td>" << S->region << "</td><td>" << S->pos.lat << "</td>\n";
		std::cout << "</tr><tr>\n";
		std::cout << "  <th>East:</th> <td>" << E->link_name() << "</td><td>" << E->region << "</td><td>" << E->pos.lon << "</td>\n";
		std::cout << "</tr><tr>\n";
		std::cout << "  <th>West:</th> <td>" << W->link_name() << "</td><td>" << W->region << "</td><td>" << W->pos.lon << "</td>\n";
		std::cout << "</tr>\n";
		std::cout << "</table>\n";

		const int LIST_MAX = 15;
		n = 1;

		std::cout << "<h2>Oldest caches found</h2>\n";
		std::cout << "<table class=\"list\">\n";
		std::cout << "<tr><th></th>";
		std::cout << "<th>Cache</th>";
		std::cout << "<th>Type</th>";
		std::cout << "<th>Region</th>";
		std::cout << "<th>Date hidden</th>";
		std::cout << "<th>Date found</th>";
		std::cout << "</tr>\n";

		for (auto i = user.sorted_caches_by_hidden.begin(); i != user.sorted_caches_by_hidden.end(); i++) {
			std::cout << "<tr><th>" << n << "</th> ";
			std::cout << "<td>" << i->second->link_name() << "</td>";
			std::cout << "<td>" << i->second->type << "</td>";
			std::cout << "<td>" << i->second->region << "</td>";
			std::cout << "<td>" << i->second->date_hidden << "</td>";
			std::cout << "<td>" << i->second->date << "</td>";
			std::cout << "</tr>\n";
			n++;
			if (n > LIST_MAX) break;
		}
		std::cout << "</table>\n";

		n = 1;

		std::cout << "<h2>Newest caches found</h2>\n";
		std::cout << "<table class=\"list\">\n";
		std::cout << "<tr><th></th>";
		std::cout << "<th>Cache</th>";
		std::cout << "<th>Type</th>";
		std::cout << "<th>Region</th>";
		std::cout << "<th>Date hidden</th>";
		std::cout << "<th>Date found</th>";
		std::cout << "</tr>\n";

		for (auto i = user.sorted_caches_by_hidden.rbegin(); i != user.sorted_caches_by_hidden.rend(); i++) {
			std::cout << "<tr><th>" << n << "</th> ";
			std::cout << "<td>" << i->second->link_name() << "</td>";
			std::cout << "<td>" << i->second->type << "</td>";
			std::cout << "<td>" << i->second->region << "</td>";
			std::cout << "<td>" << i->second->date_hidden << "</td>";
			std::cout << "<td>" << i->second->date << "</td>";
			std::cout << "</tr>\n";
			n++;
			if (n > LIST_MAX) break;
		}
		std::cout << "</table>\n";

		std::cout << "<div class=\"basic_stats\">\n";
		std::cout << "Average age of cache at the moment of finding: <span class=\"value\">" << average(user.cc, &Cache::age_when_found) / 365 * 12 << "</span> months<br>\n";
		std::cout << "Number of caches found within first 24h: <span class=\"value\">" << std::count_if(user.cc.begin(), user.cc.end(), [&](const auto& a) { return a.age_when_found == 0; }) << "</span><br>\n";
		std::cout << "Number of caches found within first 48h: <span class=\"value\">" << std::count_if(user.cc.begin(), user.cc.end(), [&](const auto& a) { return a.age_when_found <= 1; }) << "</span><br>\n";
		std::cout << "Number of caches found during first week: <span class=\"value\">" << std::count_if(user.cc.begin(), user.cc.end(), [&](const auto& a) { return a.age_when_found < 7; }) << "</span><br>\n";
		std::cout << "Number of caches found during first month: <span class=\"value\">" << std::count_if(user.cc.begin(), user.cc.end(), [&](const auto& a) { return a.age_when_found < 30; }) << "</span><br>\n";
		std::cout << "Number of caches found after more than 5 years: <span class=\"value\">" << std::count_if(user.cc.begin(), user.cc.end(), [&](const auto& a) { return a.age_when_found > 365 * 5; }) << "</span><br>\n";
		std::cout << "Number of caches found after more than 10 years: <span class=\"value\">" << std::count_if(user.cc.begin(), user.cc.end(), [&](const auto& a) { return a.age_when_found > 365 * 10; }) << "</span><br>\n";
		std::cout << "</div>\n";

		n = 1;

		std::cout << "<h2>Caches with most recommendations</h2>\n";
		std::cout << "<table class=\"list\">\n";
		std::cout << "<tr><th></th>";
		std::cout << "<th>Cache</th>";
		std::cout << "<th>Type</th>";
		std::cout << "<th>Region</th>";
		std::cout << "<th>Rec.</th>";
		std::cout << "<th>Finds</th>";
		std::cout << "<th>%</th>";
		std::cout << "</tr>\n";

		for (auto i : user.caches_by_fav) {
			std::cout << "<tr><th>" << n << "</th> ";
			std::cout << "<td>" << i->link_name() << "</td>";
			std::cout << "<td>" << i->type << "</td>";
			std::cout << "<td>" << i->region << "</td>";
			std::cout << "<td>" << i->fav << "</td>";
			std::cout << "<td>" << i->founds << "</td>";
			std::cout << "<td>" << std::setprecision(3) << 100.0 * i->fav / i->founds << "%</td>";
			std::cout << "</tr>\n";
			n++;
			if (n > LIST_MAX) break;
		}
		std::cout << "</table>\n";

		average_html(user.cc, &Cache::fav, "number of recommendations");

		n = 1;

		std::cout << "<h2>Caches with most recommendations (percentage)</h2>\n";
		std::cout << "<table class=\"list\">\n";
		std::cout << "<tr><th></th>";
		std::cout << "<th>Cache</th>";
		std::cout << "<th>Type</th>";
		std::cout << "<th>Region</th>";
		std::cout << "<th>Rec.</th>";
		std::cout << "<th>Finds</th>";
		std::cout << "<th>%</th>";
		std::cout << "</tr>\n";

		for (auto i : user.caches_by_fav_perc) {
			std::cout << "<tr><th>" << n << "</th> ";
			std::cout << "<td>" << i->link_name() << "</td>";
			std::cout << "<td>" << i->type << "</td>";
			std::cout << "<td>" << i->region << "</td>";
			std::cout << "<td>" << i->fav << "</td>";
			std::cout << "<td>" << i->founds << "</td>";
			std::cout << "<td>" << std::setprecision(3) << 100.0 * i->fav / i->founds << "%</td>";
			std::cout << "</tr>\n";
			n++;
			if (n > LIST_MAX) break;
		}
		std::cout << "</table>\n";

		/*
		n = 1;

		std::cout << "<h2>Caches with highest rating</h2>\n";
		std::cout << "<table class=\"list\">\n";
		std::cout << "<tr><th></th>";
		std::cout << "<th>Cache</th>";
		std::cout << "<th>Type</th>";
		std::cout << "<th>Region</th>";
		std::cout << "<th>Rating</th>";
		std::cout << "<th>Finds</th>";
		std::cout << "</tr>\n";

		for (auto i : caches_by_rating) {
			std::cout << "<tr><th>" << n << "</th> ";
			std::cout << "<td>" << i->link_name() << "</td>";
			std::cout << "<td>" << i->type << "</td>";
			std::cout << "<td>" << i->region << "</td>";
			std::cout << "<td>" << i->rating << "</td>";
			std::cout << "<td>" << i->founds << "</td>";
			std::cout << "</tr>\n";
			n++;
			if (n > LIST_MAX) break;
		}
		std::cout << "</table>\n";

		average_html(cc, &Cache::rating, "rating");
*/

		n = 1;

		std::cout << "<h2>Caches with most finds</h2>\n";
		std::cout << "<table class=\"list\">\n";
		std::cout << "<tr><th></th>";
		std::cout << "<th>Cache</th>";
		std::cout << "<th>Type</th>";
		std::cout << "<th>Region</th>";
		std::cout << "<th>Date hidden</th>";
		std::cout << "<th>Finds</th>";
		std::cout << "</tr>\n";

		for (auto i : user.caches_by_finds) {
			std::cout << "<tr><th>" << n << "</th> ";
			std::cout << "<td>" << i->link_name() << "</td>";
			std::cout << "<td>" << i->type << "</td>";
			std::cout << "<td>" << i->region << "</td>";
			std::cout << "<td>" << i->date_hidden << "</td>";
			std::cout << "<td>" << i->founds << "</td>";
			std::cout << "</tr>\n";
			n++;
			if (n > LIST_MAX) break;
		}
		std::cout << "</table>\n";

		average_html(user.cc, &Cache::founds, "number of finds");

		n = 1;

		std::cout << "<h2>Caches with least finds</h2>\n";
		std::cout << "<table class=\"list\">\n";
		std::cout << "<tr><th></th>";
		std::cout << "<th>Cache</th>";
		std::cout << "<th>Type</th>";
		std::cout << "<th>Region</th>";
		std::cout << "<th>Date hidden</th>";
		std::cout << "<th>Finds</th>";
		std::cout << "</tr>\n";

		for (auto i = user.caches_by_finds.rbegin(); i != user.caches_by_finds.rend(); i++) {
			std::cout << "<tr><th>" << n << "</th> ";
			std::cout << "<td>" << (*i)->link_name() << "</td>";
			std::cout << "<td>" << (*i)->type << "</td>";
			std::cout << "<td>" << (*i)->region << "</td>";
			std::cout << "<td>" << (*i)->date_hidden << "</td>";
			std::cout << "<td>" << (*i)->founds << "</td>";
			std::cout << "</tr>\n";
			n++;
			if (n > LIST_MAX) break;
		}
		std::cout << "</table>\n";

//		D/T matrix
		n = 0;

		std::cout << "<h2>Difficulty / terrain matrix</h2>\n";
		std::cout << "<table class=\"dt\">\n";
		std::cout << "<tr><th></th>";
		for (int j = 2; j <= 10; j++) { // print table terr headers
			std::cout << "<th>" << j / 2.0 << "</th>";
		}
		std::cout << "</tr>\n";

		for (int i = 2; i <= 10; i++) { // i -> diff in rows
			std::cout << "<tr><th>" << i / 2.0 << "</th> ";
			for (int j = 2; j <= 10; j++) { // j -> terr in cols
				count = count_caches(user.cc, codes, [i, j](const Cache& c) -> bool { return (c.diff == i / 2.0 && c.terr == j / 2.0); });
				if (count == 0)
					std::cout << "<td class=\"dt_zero\">" << 0 << "</td>";
				else {
					std::cout << "<td codes=\"" << codes << "\">" << count << "</td>";
					n++;
				}
			}
			std::cout << "</tr>\n";
		}
		std::cout << "</table>\n";

		std::cout << "<div class=\"basic_stats\">\n";
		std::cout << "Total <span class=\"value\">" << n << "</span> combinations found out of 81 (" << std::setprecision(3) << n / 0.81 << "%).<br>\n";
		std::cout << "Average difficulty: <span class=\"value\">" << average(user.cc, &Cache::diff) << "</span><br>\n";
		std::cout << "Average terrain: <span class=\"value\">" << average(user.cc, &Cache::terr) << "</span><br>\n";
		std::cout << "</div>\n";

//		Days matrix
		n = 0;

		std::cout << "<h2>Caching days matrix</h2>\n";
		std::cout << "<table class=\"dt\">\n";
		std::cout << "<tr><th></th>";
		for (int j = 1; j <= 31; j++) { // print table day headers
			std::cout << "<th>" << j << "</th>";
		}
		std::cout << "<th></th></tr>\n";

		for (int i = 1; i <= 12; i++) { // i -> months in rows
			int m = 0;
			std::cout << "<tr><th>" << i << "</th> ";
			for (int j = 1; j <= 31; j++) { // j -> days in cols
				count = count_caches(user.cc, codes, [i, j](const Cache& c) -> bool { return (c.date_tm.tm_mon == i - 1 && c.date_tm.tm_mday == j); });
				if (count == 0) {
					if ((j == 31 && (i == 2 || i == 4 || i == 6 || i == 9 || i == 11)) || (j == 30 && i == 2))
						std::cout << "<td class=\"dt_null\"></td>";
					else
						std::cout << "<td class=\"dt_zero\">" << 0 << "</td>";
				} else {
					std::cout << "<td codes=\"" << codes << "\">" << count << "</td>";
					n++;
					m++;
				}
			}
			float perc;
			switch (i) {
				case 4:
				case 6:
				case 9:
				case 11:
					perc = m * 100.0 / 30;
					break;
				case 2:
					perc = m * 100.0 / 29;
					break;
				default:
					perc = m * 100.0 / 31;
			}
			std::cout << "<th>" << perc << "%</th>";
			std::cout << "</tr>\n";
		}
		std::cout << "</table>\n";

		std::cout << "<div class=\"basic_stats\">\n";
		std::cout << "Total <span class=\"value\">" << n << "</span> days out of 366 (" << std::setprecision(3) << n / 3.66 << "%).\n";
		std::cout << "</div>\n";

//		Hidden months matrix
		n = 0;

		y_min = std::min_element(user.cc.begin(), user.cc.end(), [&](const Cache& a, const Cache& b) { return a.date_hidden_tm.tm_year < b.date_hidden_tm.tm_year; })->date_hidden_tm.tm_year;
		y_max = std::max_element(user.cc.begin(), user.cc.end(), [&](const Cache& a, const Cache& b) { return a.date_hidden_tm.tm_year < b.date_hidden_tm.tm_year; })->date_hidden_tm.tm_year;

		std::cout << "<h2>Caches by hidden month matrix</h2>\n";
		std::cout << "<table class=\"dt\">\n";
		std::cout << "<tr><th></th>";
		for (int j = 1; j <= 12; j++) { // print table month headers
			std::cout << "<th>" << j << "</th>";
		}
		std::cout << "<th></th></tr>\n";

		std::time_t now = std::time(nullptr);
		std::tm* now_tm = std::localtime(&now);

		for (int i = y_min; i <= y_max; i++) { // i -> years in rows
			int m = 0;
			std::cout << "<tr><th>" << i + 1900 << "</th> ";
			for (int j = 1; j <= 12; j++) { // j -> months in cols
				count = count_caches(user.cc, codes, [i, j](const Cache& c) -> bool { return (c.date_hidden_tm.tm_year == i && c.date_hidden_tm.tm_mon == j - 1); });
				if (count == 0) {
					if ((!user.ocpl_user_uuid.empty() && user.ocde_user_uuid.empty() && i + 1900 == 2006 && j < 5) ||
						(!user.ocde_user_uuid.empty() && i + 1900 == 2005 && j < 8))
						std::cout << "<td class=\"dt_null\"></td>";
					else
						std::cout << "<td class=\"dt_zero\">" << 0 << "</td>";
				} else {
					std::cout << "<td codes=\"" << codes << "\">" << count << "</td>";
					n++;
					m++;
				}
			}
			if (now_tm) {
				if (i == now_tm->tm_year)
					std::cout << "<th>" << m * 100.0 / (now_tm->tm_mon + 1) << "%</th>";
				else
					std::cout << "<th>" << m * 100.0 / 12 << "%</th>";
			}
			std::cout << "</tr>\n";
		}
		std::cout << "</table>\n";

		if (now_tm) {
			if (!user.ocpl_user_uuid.empty() && user.ocde_user_uuid.empty()) {
				int m_count = (now_tm->tm_year - 106 - 1) * 12 + 8 + now_tm->tm_mon + 1; // 106 corresponds to 2006, 8 is no. of months since may till dec, +1 since tm_mon starts at 0
				std::cout << "<div class=\"basic_stats\">\n";
				std::cout << "Total <span class=\"value\">" << n << "</span> months out of  " << m_count << " (" << std::setprecision(3) << n * 100.0 / m_count << "%).\n";
				std::cout << "</div>\n";
			} else if (!user.ocde_user_uuid.empty()) {
				int m_count = (now_tm->tm_year - 105 - 1) * 12 + 5 + now_tm->tm_mon + 1; // 106 corresponds to 2005, 8 is no. of months since aug till dec, +1 since tm_mon starts at 0
				std::cout << "<div class=\"basic_stats\">\n";
				std::cout << "Total <span class=\"value\">" << n << "</span> months out of  " << m_count << " (" << std::setprecision(3) << n * 100.0 / m_count << "%).\n";
				std::cout << "</div>\n";
			}
		}

		if (!user.ocpl_user_uuid.empty()) {
			n = 1;

			std::cout << "<h2>Power trails (by completion percentage)</h2>\n";
			std::cout << "<table class=\"list\">\n";
			std::cout << "<tr><th></th>";
			std::cout << "<th>Trail</th>";
			std::cout << "<th>Type</th>";
			std::cout << "<th>Caches</th>";
			std::cout << "<th>Found</th>";
			std::cout << "<th>Treshold</th>";
			std::cout << "<th>Needed</th>";
			std::cout << "<th>Available</th>";
			std::cout << "</tr>\n";

			for (auto& i : user.tt) {
				std::cout << "<tr>";
				std::cout << "<th>" << n << "</th> ";
				std::cout << "<td>" << i->link_name() << "</td>";
				std::cout << "<td>" << i->get_type() << "</td>";
				std::cout << "<td>" << i->caches.size() << "</td>";
				std::cout << "<td>" << i->found << "</td>";
				if (i->completed)
					std::cout << "<td class=\"good\">";
				else if (i->active_caches_not_found < i->needed)
					std::cout << "<td class=\"bad\">";
				else
					std::cout << "<td>";
				std::cout << i->completed_perc << "% / " << i->treshold_perc << "%</td>";
				std::cout << "<td>";
				if (!i->completed)
					std::cout << i->needed;
				std::cout << "</td>";
				std::cout << "<td>";
				if (i->active_caches_not_found && i->active_caches_not_found < UINT_MAX - 1024)
					std::cout << i->active_caches_not_found;
				std::cout << "</td>";
				std::cout << "</tr>\n";
				n++;
				//				if (n > LIST_MAX) break;
			}
			std::cout << "</table>\n";

			//			show_histogram(tt, "Power trails (completion percentage)", 1);
			std::cout << "<div class=\"basic_stats\">\n";
			std::cout << "Number of completed power trails: <span class=\"value\">" << std::count_if(user.tt.begin(), user.tt.end(), [&](const auto& a) { return a->completed; }) << "</span><br>\n";
			std::cout << "Number of started power trails: <span class=\"value\">" << user.tt.size() << "</span><br>\n";
			std::cout << "</div>\n";

			std::cout << "<h2>FTF</h2>\n";
			std::cout << "<table class=\"list\">\n";
			std::cout << "<tr><th></th>";
			std::cout << "<th>Cache</th>";
			std::cout << "<th>Type</th>";
			std::cout << "<th>Region</th>";
			std::cout << "<th>Date found</th>";
			std::cout << "<th>Finds</th>";
			std::cout << "</tr>\n";

			n = 1;

			for (auto i : user.caches_ftf) {
				std::cout << "<tr><th>" << n << "</th> ";
				std::cout << "<td>" << i->link_name() << "</td>";
				std::cout << "<td>" << i->type << "</td>";
				std::cout << "<td>" << i->region << "</td>";
				std::cout << "<td>" << i->date << "</td>";
				std::cout << "<td>" << i->founds << "</td>";
				std::cout << "</tr>\n";
				n++;
				//				if (n > LIST_MAX) break;
			}
			std::cout << "</table>\n";
			std::cout << "<div class=\"basic_stats\">\n";
			std::cout << "Number of FTFs: <span class=\"value\">" << user.caches_ftf.size() << "</span><br>\n";
			std::cout << "</div>\n";
		}
	} // end of main if

	if (user.get_not_found) {
		std::cout << "<div class=\"basic_stats\">\n";
		std::cout << "Still <span class=\"value\">" << user.cc.size() << "</span> caches to be found...<br>\n";
		std::cout << "</div>\n";

		show_nested_histogram(user.fcc, &Cache::region, &Cache::subregion, "Regions", 1);

		std::map<std::string, int> region_found_count;
		for (auto el : user.fcc)
			region_found_count[el->region]++;
		for (auto& i : user.region_count)
			if (i.second != 0) i.second = region_found_count[i.first] * 100 / i.second;
		show_histogram(user.region_count, "Percentage", 1);
	} // end of "not found" if

	if (user.get_owned) {
		short count;
		std::string codes;
		int n;

		std::cout << "<div class=\"basic_stats\">\n";
		std::cout << "Number of caches created: <span class=\"value\">" << user.cc.size() << "</span><br>\n";
		std::cout << "Number of caches that are disabled: <span class=\"value\">" << std::count_if(user.cc.begin(), user.cc.end(), [&](const auto& a) { return a.status == disabled; }) << "</span><br>\n";
		std::cout << "Number of caches that are archived: <span class=\"value\">" << std::count_if(user.cc.begin(), user.cc.end(), [&](const auto& a) { return a.status == archived; }) << "</span><br>\n";
		std::cout << "Average finds per cache per day: <span class=\"value\">" << std::setprecision(3) << std::accumulate(user.cc.begin(), user.cc.end(), 0., [&](const float& a, const Cache& b) { return std::move(a) + 1.0 * b.founds / std::max(b.age_now, 1); }) / user.caches_count << "</span><br>\n";
		std::cout << std::resetiosflags(std::cout.flags());
		std::cout << "</div>\n";

		short y_min = std::min_element(user.cc.begin(), user.cc.end(), [&](const Cache& a, const Cache& b) { return a.date_hidden_tm.tm_year < b.date_hidden_tm.tm_year; })->date_hidden_tm.tm_year;
		short y_max = std::max_element(user.cc.begin(), user.cc.end(), [&](const Cache& a, const Cache& b) { return a.date_hidden_tm.tm_year < b.date_hidden_tm.tm_year; })->date_hidden_tm.tm_year;

		long max = 0; // maximal value for histogram
		for (int i = y_min; i <= y_max; i++)
			for (int j = 1; j <= 12; j++)
				max = std::max(max, std::count_if(user.cc.begin(), user.cc.end(), [i, j](Cache c) { return (c.date_hidden_tm.tm_year == i && c.date_hidden_tm.tm_mon == j - 1); }));

		if (max > 0) {
			std::cout << "<h2>Cache creation timeline</h2>\n";
			std::cout << "<table class=\"calendar_tab\">\n";
			std::cout << "<tr><th></th>";
			for (int j = 1; j <= 12; j++) { // print table month headers
				std::cout << "<th>" << j << "</th>";
			}
			std::cout << "</tr>\n";
			for (int i = y_min; i <= y_max; i++) { // i -> years in rows
				std::cout << "<tr><th>" << i + 1900 << "</th> ";
				for (int j = 1; j <= 12; j++) { // j -> months in cols
					count = std::count_if(user.cc.begin(), user.cc.end(), [i, j](Cache c) { return (c.date_hidden_tm.tm_year == i && c.date_hidden_tm.tm_mon == j - 1); });
					std::cout << "<td style=\"--percent: " << count * 100 / max << "%\"><span>" << count << "</span></td>";
				}
				std::cout << "</tr>\n";
			}
			std::cout << "</table>\n";
		}

// 		auto far = std::max_element(fcc.begin(), fcc.end(), [&](const Cache& a, const Cache& b) { return a.distance() < b.distance(); });
// 		auto near = std::min_element(fcc.begin(), fcc.end(), [&](const Cache& a, const Cache& b) { return a.distance() < b.distance(); });
//
// 		std::cout << "Nearest cache: " << near->distance() << " km\n";
// 		near->show();
// 		std::cout << "Furthest cache: " << far->distance() << " km\n";
// 		far->show();

		show_histogram(user.cc, &Cache::type, "Cache types", 1);
		show_histogram(user.cc, &Cache::size, "Cache sizes", 1);
		show_nested_histogram(user.fcc, &Cache::region, &Cache::subregion, "Regions", 1);

		auto N = *std::max_element(user.fcc.begin(), user.fcc.end(), [&](const Cache* a, const Cache* b) { return a->pos.lat < b->pos.lat; });
		auto S = *std::min_element(user.fcc.begin(), user.fcc.end(), [&](const Cache* a, const Cache* b) { return a->pos.lat < b->pos.lat; });
		auto E = *std::max_element(user.fcc.begin(), user.fcc.end(), [&](const Cache* a, const Cache* b) { return a->pos.lon < b->pos.lon; });
		auto W = *std::min_element(user.fcc.begin(), user.fcc.end(), [&](const Cache* a, const Cache* b) { return a->pos.lon < b->pos.lon; });

		std::cout << "<h2>Geographically extreme caches</h2>\n";
		std::cout << "<table class=\"list\">\n";
		std::cout << "<tr>\n";
		std::cout << "  <th>North:</th><td>" << N->link_name() << "</td><td>" << N->pos.lat << "</td>\n";
		std::cout << "</tr><tr>\n";
		std::cout << "  <th>South:</th><td>" << S->link_name() << "</td><td>" << S->pos.lat << "</td>\n";
		std::cout << "</tr><tr>\n";
		std::cout << "  <th>East:</th> <td>" << E->link_name() << "</td><td>" << E->pos.lon << "</td>\n";
		std::cout << "</tr><tr>\n";
		std::cout << "  <th>West:</th> <td>" << W->link_name() << "</td><td>" << W->pos.lon << "</td>\n";
		std::cout << "</tr>\n";
		std::cout << "</table>\n";

		const int LIST_MAX = 15;
		n = 1;

		std::cout << "<h2>Caches with most recommendations</h2>\n";
		std::cout << "<table class=\"list\">\n";
		std::cout << "<tr><th></th>";
		std::cout << "<th>Cache</th>";
		std::cout << "<th>Type</th>";
		std::cout << "<th>Rec.</th>";
		std::cout << "<th>Finds</th>";
		std::cout << "<th>%</th>";
		std::cout << "</tr>\n";

		for (auto i : user.caches_by_fav) {
			std::cout << "<tr><th>" << n << "</th> ";
			std::cout << "<td>" << i->link_name() << "</td>";
			std::cout << "<td>" << i->type << "</td>";
			std::cout << "<td>" << i->fav << "</td>";
			std::cout << "<td>" << i->founds << "</td>";
			if (i->founds > 0)
				std::cout << "<td>" << std::setprecision(3) << 100.0 * i->fav / i->founds << "%</td>";
			else
				std::cout << "<td>&ndash;</td>";
			std::cout << "</tr>\n";
			n++;
			if (n > LIST_MAX) break;
		}
		std::cout << "</table>\n";

		average_html(user.cc, &Cache::fav, "number of recommendations");
		sum_html(user.cc, &Cache::fav, "number of recommendations");

		n = 1;

		std::cout << "<h2>Caches with most recommendations (percentage)</h2>\n";
		std::cout << "<table class=\"list\">\n";
		std::cout << "<tr><th></th>";
		std::cout << "<th>Cache</th>";
		std::cout << "<th>Type</th>";
		std::cout << "<th>Rec.</th>";
		std::cout << "<th>Finds</th>";
		std::cout << "<th>%</th>";
		std::cout << "</tr>\n";

		for (auto i : user.caches_by_fav_perc) {
			std::cout << "<tr><th>" << n << "</th> ";
			std::cout << "<td>" << i->link_name() << "</td>";
			std::cout << "<td>" << i->type << "</td>";
			std::cout << "<td>" << i->fav << "</td>";
			std::cout << "<td>" << i->founds << "</td>";
			if (i->founds > 0)
				std::cout << "<td>" << std::setprecision(3) << 100.0 * i->fav / i->founds << "%</td>";
			else
				std::cout << "<td>&ndash;</td>";
			std::cout << "</tr>\n";
			n++;
			if (n > LIST_MAX) break;
		}
		std::cout << "</table>\n";

		/*
		n = 1;

		std::cout << "<h2>Caches with highest rating</h2>\n";
		std::cout << "<table class=\"list\">\n";
		std::cout << "<tr><th></th>";
		std::cout << "<th>Cache</th>";
		std::cout << "<th>Type</th>";
		std::cout << "<th>Rating</th>";
		std::cout << "<th>Finds</th>";
		std::cout << "</tr>\n";

		for (auto i : caches_by_rating) {
			std::cout << "<tr><th>" << n << "</th> ";
			std::cout << "<td>" << i->link_name() << "</td>";
			std::cout << "<td>" << i->type << "</td>";
			std::cout << "<td>" << i->rating << "</td>";
			std::cout << "<td>" << i->founds << "</td>";
			std::cout << "</tr>\n";
			n++;
			if (n > LIST_MAX) break;
		}
		std::cout << "</table>\n";

		average_html(cc, &Cache::rating, "rating");
*/

		n = 1;

		std::cout << "<h2>Caches with most finds</h2>\n";
		std::cout << "<table class=\"list\">\n";
		std::cout << "<tr><th></th>";
		std::cout << "<th>Cache</th>";
		std::cout << "<th>Type</th>";
		std::cout << "<th>Date hidden</th>";
		std::cout << "<th>Finds</th>";
		std::cout << "</tr>\n";

		for (auto i : user.caches_by_finds) {
			std::cout << "<tr><th>" << n << "</th> ";
			std::cout << "<td>" << i->link_name() << "</td>";
			std::cout << "<td>" << i->type << "</td>";
			std::cout << "<td>" << i->date_hidden << "</td>";
			std::cout << "<td>" << i->founds << "</td>";
			std::cout << "</tr>\n";
			n++;
			if (n > LIST_MAX) break;
		}
		std::cout << "</table>\n";

		average_html(user.cc, &Cache::founds, "number of finds");
		sum_html(user.cc, &Cache::founds, "number of finds");

		n = 1;

		std::cout << "<h2>Caches with least finds</h2>\n";
		std::cout << "<table class=\"list\">\n";
		std::cout << "<tr><th></th>";
		std::cout << "<th>Cache</th>";
		std::cout << "<th>Type</th>";
		std::cout << "<th>Date hidden</th>";
		std::cout << "<th>Finds</th>";
		std::cout << "</tr>\n";

		for (auto i = user.caches_by_finds.rbegin(); i != user.caches_by_finds.rend(); i++) {
			std::cout << "<tr><th>" << n << "</th> ";
			std::cout << "<td>" << (*i)->link_name() << "</td>";
			std::cout << "<td>" << (*i)->type << "</td>";
			std::cout << "<td>" << (*i)->date_hidden << "</td>";
			std::cout << "<td>" << (*i)->founds << "</td>";
			std::cout << "</tr>\n";
			n++;
			if (n > LIST_MAX) break;
		}
		std::cout << "</table>\n";

//		D/T matrix
		n = 0;

		std::cout << "<h2>Difficulty / terrain matrix</h2>\n";
		std::cout << "<table class=\"dt\">\n";
		std::cout << "<tr><th></th>";
		for (int j = 2; j <= 10; j++) { // print table terr headers
			std::cout << "<th>" << j / 2.0 << "</th>";
		}
		std::cout << "</tr>\n";

		for (int i = 2; i <= 10; i++) { // i -> diff in rows
			std::cout << "<tr><th>" << i / 2.0 << "</th> ";
			for (int j = 2; j <= 10; j++) { // j -> terr in cols
				count = count_caches(user.cc, codes, [i, j](const Cache& c) -> bool { return (c.diff == i / 2.0 && c.terr == j / 2.0); });
				if (count == 0)
					std::cout << "<td class=\"dt_zero\">" << 0 << "</td>";
				else {
					std::cout << "<td codes=\"" << codes << "\">" << count << "</td>";
					n++;
				}
			}
			std::cout << "</tr>\n";
		}
		std::cout << "</table>\n";

		std::cout << "<div class=\"basic_stats\">\n";
		std::cout << "Total <span class=\"value\">" << n << "</span> combinations created out of 81 (" << std::setprecision(3) << n / 0.81 << "%).<br>\n";
		std::cout << "Average difficulty: <span class=\"value\">" << average(user.cc, &Cache::diff) << "</span><br>\n";
		std::cout << "Average terrain: <span class=\"value\">" << average(user.cc, &Cache::terr) << "</span><br>\n";
		std::cout << "</div>\n";
	} // end of "owned" if

	footer_html();
}
