#include "okapi.h"
#include "gpx.h"
#include "cache.h"
#include "debug.h"
#include "heat.h"
#include "ocdb.h"
#include "common.h"

#include <iostream>
#include <iomanip>
#include <algorithm>
#include <unistd.h>

void show_usage() {
	std::cout << "Usage: geostat [options]\n";
	std::cout << "Generate HTML stats from Opencaching data or GPX files.\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 only for unfound 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-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";
	bool heat_exp = 0;
	bool trail = 0;
	bool use_oc = 0;
	bool use_ocpl_db = 0;
	bool get_not_found = 0;
	bool exclude_quiz = 0;
	std::time_t start_time = 0;
	std::time_t end_time = std::time(nullptr);
	bool time_filter = 0;

	std::string ocpl_user;
	std::string ocde_user;
	std::string ocus_user;
	std::string ocnl_user;
	std::string ocro_user;
	std::string ocuk_user;

	std::string ocpl_user_profile;
	std::string ocde_user_profile;
	std::string ocus_user_profile;
	std::string ocnl_user_profile;
	std::string ocro_user_profile;
	std::string ocuk_user_profile;

	std::string ocpl_url = "https://opencaching.pl/okapi/";
	std::string ocde_url = "https://www.opencaching.de/okapi/";
	std::string ocus_url = "http://www.opencaching.us/okapi/";
	std::string ocnl_url = "http://www.opencaching.nl/okapi/";
	std::string ocro_url = "http://www.opencaching.ro/okapi/";
	std::string ocuk_url = "https://opencache.uk/okapi/";

	const std::string Database = "ocpl.sqlite";

	std::string gpx_file;

#include "config_user.h"

	if (argc == 1) show_usage();

	int o;
	while ((o = getopt(argc, argv, "qNQg:o::p:d:u:n:r:k:i:f:H:s:m:eth?")) != -1)
		switch (o) {
// 			case 'd':
// 				Debug::set_debug_level(get_num('d',optarg));
// 				break;
			case 'g':
				gpx_file = optarg;
				break;
			case 'o':
				use_oc = 1;
				if (optarg) {
					ocpl_user = optarg;
					ocde_user = optarg;
					ocus_user = optarg;
					ocnl_user = optarg;
					ocro_user = optarg;
					ocuk_user = optarg;
				}
				break;
			case 'p':
				ocpl_user = optarg;
				break;
			case 'd':
				ocde_user = optarg;
				break;
			case 'u':
				ocus_user = optarg;
				break;
			case 'n':
				ocnl_user = optarg;
				break;
			case 'r':
				ocro_user = optarg;
				break;
			case 'k':
				ocuk_user = optarg;
				break;
			case 'q':
				use_ocpl_db = 1;
				break;
			case 'i':
				start_time = get_num('i',optarg);
				time_filter = 1;
				break;
			case 'f':
				end_time = get_num('f',optarg);
				time_filter = 1;
				break;
			case 'N':
				get_not_found = 1;
				break;
			case 'Q':
				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 'h':
			case '?':
			default:
				show_usage();
		}

	Caches cc;

	if (get_not_found) {
		use_oc = 0;
		use_ocpl_db = 1;
		trail = 0;
		if (ocpl_user_uuid.empty() && ocpl_user.empty()) {
			std::cout << "Option \"-N\" works only with OCpl\n";
			std::exit(EXIT_FAILURE);
		}
	}

	if (use_oc) {
		if (!ocpl_user_uuid.empty() || !ocpl_user.empty()) {
			Okapi OCpl(ocpl_url, ocpl_key);
			if (!ocpl_user.empty()) ocpl_user_uuid = OCpl.get_uuid(ocpl_user);
			Caches tmp = OCpl.get_user_caches(ocpl_user_uuid, 0);
			std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc));
			ocpl_user_profile = OCpl.get_profile_url(ocpl_user_uuid);
		}
		if (!ocde_user_uuid.empty() || !ocde_user.empty()) {
			Okapi OCde(ocde_url, ocde_key);
			if (!ocde_user.empty()) ocde_user_uuid = OCde.get_uuid(ocde_user);
			Caches tmp = OCde.get_user_caches(ocde_user_uuid, 0);
			std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc));
			ocde_user_profile = OCde.get_profile_url(ocde_user_uuid);
		}
		if (!ocus_user_uuid.empty() || !ocus_user.empty()) {
			Okapi OCus(ocus_url, ocus_key);
			if (!ocus_user.empty()) ocus_user_uuid = OCus.get_uuid(ocus_user);
			Caches tmp = OCus.get_user_caches(ocus_user_uuid, 0);
			std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc));
			ocus_user_profile = OCus.get_profile_url(ocus_user_uuid);
		}
		if (!ocnl_user_uuid.empty() || !ocnl_user.empty()) {
			Okapi OCnl(ocnl_url, ocnl_key);
			if (!ocnl_user.empty()) ocnl_user_uuid = OCnl.get_uuid(ocnl_user);
			Caches tmp = OCnl.get_user_caches(ocnl_user_uuid, 0);
			std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc));
			ocnl_user_profile = OCnl.get_profile_url(ocnl_user_uuid);
		}
		if (!ocro_user_uuid.empty() || !ocro_user.empty()) {
			Okapi OCro(ocro_url, ocro_key);
			if (!ocro_user.empty()) ocro_user_uuid = OCro.get_uuid(ocro_user);
			Caches tmp = OCro.get_user_caches(ocro_user_uuid, 0);
			std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc));
			ocro_user_profile = OCro.get_profile_url(ocro_user_uuid);
		}
		if (!ocuk_user_uuid.empty() || !ocuk_user.empty()) {
			Okapi OCuk(ocuk_url, ocuk_key);
			if (!ocuk_user.empty()) ocuk_user_uuid = OCuk.get_uuid(ocuk_user);
			Caches tmp = OCuk.get_user_caches(ocuk_user_uuid, 0);
			std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc));
			ocuk_user_profile = OCuk.get_profile_url(ocuk_user_uuid);
		}
	}

	if (!gpx_file.empty()) {
		GPX gpxfile(gpx_file);
		Caches tmp = gpxfile.get_user_caches();
		std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc));
	}

	if (use_ocpl_db) {
		OCdb db(Database);
		if (!ocpl_user.empty()) {
			Okapi OCpl(ocpl_url, ocpl_key);
			ocpl_user_uuid = OCpl.get_uuid(ocpl_user);
		}
		if (get_not_found) {
			Caches tmp = db.get_user_caches_not_found(ocpl_user_uuid);
			std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc));
		} else {
			Caches tmp = db.get_user_caches(ocpl_user_uuid, 0);
			std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc));
		}
	}

	// TODO: some cache deduplication is needed

	Debug(2) << "Caches read: " << cc.size() << '\n';
	if (cc.size() == 0) {
		std::cout << "No caches found, aborting.\n";
		std::exit(EXIT_FAILURE);
	}

	// Prepare sorted list of caches, excluding moving caches
	std::map<std::string, int> dates;
	Date_Caches sorted_caches;
	Date_Caches sorted_fcaches;
	Date_Caches sorted_caches_by_hidden;
	pCaches caches_by_fav;
	pCaches caches_by_fav_perc;
	pCaches caches_by_finds;
	pCaches fcc;

	if (!get_not_found) {
		for (auto i = cc.begin(); i != cc.end(); ) {
			if (time_filter && (i->date_t > end_time || i->date_t < start_time)) {
				i = cc.erase(i);
				continue;
			}
			dates[i->date]++;
			sorted_caches.insert({ i->date_t, &*i });
			if (i->type != "Moving" && i->type != "Own" && (!get_not_found || !exclude_quiz || i->type != "Quiz")) {
				sorted_fcaches.insert({ i->date_t, &*i });
				fcc.push_back(&*i);
			}
			sorted_caches_by_hidden.insert({ i->date_hidden_t, &*i });
			caches_by_fav.push_back(&*i);
			caches_by_fav_perc.push_back(&*i);
			caches_by_finds.push_back(&*i);

			i++;
		}
		std::sort(caches_by_fav.begin(), caches_by_fav.end(), [&](const Cache* a, const Cache* b) { return a->fav > b->fav; });
		std::sort(caches_by_fav_perc.begin(), caches_by_fav_perc.end(), [&](const Cache* a, const Cache* b) { return 1.0 * a->fav / a->founds > 1.0 * b->fav / b->founds; });
		std::sort(caches_by_finds.begin(), caches_by_finds.end(), [&](const Cache* a, const Cache* b) { return a->founds > b->founds; });
	} else {
		for (auto& i : cc) {
			if (i.type != "Moving" && i.type != "Own" && (!get_not_found || !exclude_quiz || i.type != "Quiz")) {
				fcc.push_back(&i);
			}
		}
	}

	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=7\">\n";
	std::cout << "  <title>Geocaching stats</title>\n";
	std::cout << "</head>\n";
	std::cout << "<body>\n";
	std::cout << "<h1>Geocaching stats for user profiles: <br>\n";
	if (!ocpl_user.empty())
		std::cout << "<img src=\"https://wiki.opencaching.eu/images/b/b7/Oc-pl.png\"> <a href=\"" << ocpl_user_profile << "\">" << ocpl_user << "</a><br>\n";
	if (!ocde_user.empty())
		std::cout << "<img src=\"https://wiki.opencaching.eu/images/c/c1/Oc-de.png\"> <a href=\"" << ocde_user_profile << "\">" << ocde_user << "</a><br>\n";
	if (!ocus_user.empty())
		std::cout << "<img src=\"https://wiki.opencaching.eu/images/f/fb/Oc-us.png\"> <a href=\"" << ocus_user_profile << "\">" << ocus_user << "</a><br>\n";
	if (!ocnl_user.empty())
		std::cout << "<img src=\"https://wiki.opencaching.eu/images/f/fb/Oc-us.png\"> <a href=\"" << ocnl_user_profile << "\">" << ocnl_user << "</a><br>\n";
	if (!ocro_user.empty())
		std::cout << "<img src=\"https://wiki.opencaching.eu/images/4/4f/Oc-ro.png\"> <a href=\"" << ocro_user_profile << "\">" << ocro_user << "</a><br>\n";
	if (!ocuk_user.empty())
		std::cout << "<img src=\"https://wiki.opencaching.eu/images/5/58/Oc-org-uk.png\"> <a href=\"" << ocuk_user_profile << "\">" << ocuk_user << "</a><br>\n";

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

	std::cout << "</h1>\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 (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, sorted_fcaches);
		} else
			hmap.generate(heat_file, fcc, heat_stamp_size, (heat_exp == 1 ? "exp" : "soft"));
		std::cout << "<img class=\"heatmap\" src=\"" << heat_file << "\" alt=\"heat map\">\n";
	}

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

		auto best_day = std::max_element(dates.begin(), dates.end(), [&](auto& a, auto& b) { return a.second < b.second; });
		std::cout << "Number of caching days: <span class=\"value\">" << dates.size() << "</span><br>\n";
		std::cout << "Average caches per caching day: <span class=\"value\">" << std::setprecision(3) << (1.0 * cc.size()) / dates.size() << "</span><br>\n";
		std::cout << "Best caching day: <span class=\"value\">" << best_day->first << "</span>, found <span class=\"value\">" << best_day->second << "</span> caches\n";
		std::cout << "  <input id=\"caching-days\" class=\"toggle\" type=\"checkbox\">\n  <label for=\"caching-days\" class=\"lbl-toggle\">Details</label>\n  <div class=\"collapsible-content\">\n    <div class=\"content-inner\">\n";
		for (auto& i : dates)
			std::cout << i.first << ", found " << i.second << " caches<br>\n";
		std::cout << "   </div>\n  </div>\n";

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

		int tot_dist = 0;
		for (auto i = sorted_fcaches.begin(); i != std::prev(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\">" << tot_dist << "</span> km (equivalent to <span class=\"value\">" << tot_dist / (2 * Pi * Earth_radius) << "x</span> trips around Earth)<br>\n";
		if (sorted_fcaches.size() > 1)
			std::cout << "Average distance between caches: <span class=\"value\">" << tot_dist / (sorted_fcaches.size() - 1) << "</span> km<br>\n";
		std::cout << "</div>\n";

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

// 		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::owner, "Cache owners", 1);
		show_histogram(cc, &Cache::type, "Cache types", 1);
		show_histogram(cc, &Cache::size, "Cache sizes", 1);
		show_histogram(cc, &Cache::region, "Regions", 1);
		show_histogram(cc, &Cache::day_of_week, "Days of the week", 1, 0);
		show_histogram(cc, &Cache::mon, "Months", 1, 0);
		show_histogram(cc, &Cache::year, "Years", 1, 0);

//		DT matrix
		short count;
		int 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 = std::count_if(cc.begin(), cc.end(), [i, j](Cache c) { 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>" << 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 << "%).\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 << "</tr>\n";

		for (int i = 1; i <= 12; i++) { // i -> months in rows
			std::cout << "<tr><th>" << i << "</th> ";
			for (int j = 1; j <= 31; j++) { // j -> days in cols
				count = std::count_if(cc.begin(), cc.end(), [i, j](Cache c) { return (c.date_tm.tm_mon == i - 1 && c.date_tm.tm_mday == j); });
				if (count == 0)
					std::cout << "<td class=\"dt_zero\">" << 0 << "</td>";
				else {
					std::cout << "<td>" << 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> days out of 366 (" << std::setprecision(3) << n / 3.66 << "%).\n";
		std::cout << "</div>\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>Date hidden</th>";
		std::cout << "<th>Date found</th>";
		std::cout << "</tr>\n";

		for (auto i = sorted_caches_by_hidden.begin(); i != 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->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>Date hidden</th>";
		std::cout << "<th>Date found</th>";
		std::cout << "</tr>\n";

		for (auto i = sorted_caches_by_hidden.rbegin(); i != 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->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>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 : 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>";
			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 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 : 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>";
			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 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 : 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";

		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 = caches_by_finds.rbegin(); i != 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";

		short y_min = std::min_element(cc.begin(), 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(cc.begin(), 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 day 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 << "</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(cc.begin(), cc.end(), [i, j](Cache c) { return (c.date_hidden_tm.tm_year == i && c.date_hidden_tm.tm_mon == j - 1); });
				if (count == 0)
					std::cout << "<td class=\"dt_zero\">" << 0 << "</td>";
				else {
					std::cout << "<td>" << count << "</td>";
					n++;
				}
			}
			std::cout << "</tr>\n";
		}
		std::cout << "</table>\n";

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

		std::cout << "<h2>Caches by found day 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 << "</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(cc.begin(), cc.end(), [i, j](Cache c) { return (c.date_tm.tm_year == i && c.date_tm.tm_mon == j - 1); });
				if (count == 0)
					std::cout << "<td class=\"dt_zero\">" << 0 << "</td>";
				else {
					std::cout << "<td>" << count << "</td>";
					n++;
				}
			}
			std::cout << "</tr>\n";
		}
		std::cout << "</table>\n";

	} // end of main if

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

	std::cout << "<div id=\"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 << "</div>\n";

	std::cout << "</body>\n</html>\n";
}
