#include "region.h"
#include "debug.h"

#include <fstream>
#include <stdexcept>
#include <algorithm>

#include <nlohmann/json.hpp>

using json = nlohmann::json;
typedef bg::model::point<double, 2, bg::cs::cartesian> point_t;

BoundingBox::BoundingBox(Position p1, Position p2) {
	a.lat = std::min(p1.lat, p2.lat);
	b.lat = std::max(p1.lat, p2.lat);
	a.lon = std::min(p1.lon, p2.lon);
	b.lon = std::max(p1.lon, p2.lon);
}

bool BoundingBox::within(Position p) {
	return (p.lat >= a.lat && p.lat <= b.lat && p.lon >= a.lon && p.lon <= b.lon);
}

bool ContourData::within(Position p) {
	return bbox.within(p) && bg::within(point_t{p.lon, p.lat}, contour);
}

ContourData::ContourData(BoundingBox bbox) : bbox(bbox) {}

void ContourData::add_point(Position p) {
	bg::append(contour, point_t{p.lon, p.lat});
}

// TODO: Parse inner ring and multi-poligon 
std::map<std::string, ContourData*> Region::parse_geojson(const std::string& json_file) {
	if (!std::filesystem::exists(json_file)) {
		throw std::runtime_error("File " + json_file + " does not exist.");
	}
	std::ifstream file(json_file);
	json j;
	file >> j;
	std::string name;

	std::map<std::string, ContourData*> rr;

	for (auto& el : j["features"].items()) {
		if (el.value()["properties"]["alltags"].count("short_name") > 0)
			name = el.value()["properties"]["alltags"]["short_name"];
		else if (el.value()["properties"].count("localname") > 0)
			name = el.value()["properties"]["localname"];
		else if (el.value()["properties"].count("name") > 0)
			name = el.value()["properties"]["name"];
		else {
			Debug(1) << "Unknown name for an object, skipping";
			continue;
		}
		ContourData* cont = new ContourData(BoundingBox(Position(el.value()["bbox"][1], el.value()["bbox"][0]), Position(el.value()["bbox"][3], el.value()["bbox"][2])));
		for (auto& el2 : el.value()["geometry"]["coordinates"][0][0].items()) {
			cont->add_point(Position(el2.value()[1],el2.value()[0]));
		}
		Debug(3) << "Parsed region " << name;
		rr[name] = cont;
	}
	return rr;
}

Region::Region(const std::filesystem::path& geojson, const std::filesystem::path& geojson_sub) {
	auto reg = *parse_geojson(geojson).begin();
	name = reg.first;
	contour = reg.second;

	for (auto& el : parse_geojson(geojson_sub))
		subregions.emplace_back(new Region(el.second, el.first));
}

Region::~Region() {
	subregions.clear();
	delete contour;
}

std::string Region::find_subregion(Position p) {
	if (!within(p)) throw std::runtime_error("Point not in this region.");

	auto res = std::find_if(subregions.begin(), subregions.end(), [&](auto a) { return a->within(p); });
	if (res != subregions.end())
			return (*res)->get_name();
	return "";
// 	else
// 			throw std::runtime_error("Point " + std::to_string(p.lat) + " " + std::to_string(p.lon) + " not in any of the subregions.");
}

Country::Country(const std::vector<std::filesystem::path>& jsons) {
	for (auto el : jsons) {
		std::filesystem::path p1 = "geojson" / el;
		std::filesystem::path p2 = p1; 
		p1 += ".geojson";
		p2 += "_sub.geojson";
		regions.emplace_back(new Region(p1, p2));
	}
}

void Country::locate(Cache& c) {
	Position p = c.pos;
	for (auto& el : regions) {
		if (el->within(p)) {
			c.region = el->get_name();
			c.subregion = el->find_subregion(p);
			Debug(3) << "Found subregion " << c.subregion;
			return;
		}
	}
}
