#include <cmath>
#include <core/image.h>
#include <core/math/random_number_generator.h>
#include "world_map_data.h"

static WorldMapData *g_world_map_data = NULL;
WorldMapData *WorldMapData::get_singleton()
{
	return g_world_map_data;
}
void WorldMapData::create_singleton()
{
	g_world_map_data = memnew(WorldMapData);
}
void WorldMapData::destroy_singleton()
{
	memdelete(g_world_map_data);
	g_world_map_data = NULL;
}
void WorldMapData::_bind_methods()
{
}
void WorldMapData::add_circle(int x, int y, float radius)
{
	struct area a;
	int grid_x_min, grid_x_max, grid_x, grid_y_min, grid_y_max, grid_y;
	int i;
	Vector2i grid_pos;
	a.x = x;
	a.y = y;
	a.radius = radius;
	if (last_area >= areas.size())
		areas.resize(areas.size() + alloc_count);
	areas.write[last_area] = a;
	grid_x_min = floorf(((float)x - radius) / (float)grid_size);
	grid_x_max = ceilf(((float)x + radius) / (float)grid_size);
	grid_y_min = floorf(((float)y - radius) / (float)grid_size);
	grid_y_max = ceilf(((float)y + radius) / (float)grid_size);
	for (grid_y = grid_y_min; grid_y < grid_y_max + 1; grid_y++)
		for (grid_x = grid_x_min; grid_x < grid_x_max + 1; grid_x++) {
			grid_pos.x = grid_x;
			grid_pos.y = grid_y;
			if (!grid.has(grid_pos))
				grid[grid_pos] = List<int>();
			if (grid[grid_pos].find(last_area) == NULL) {
				int oid = get_sorted_index(grid[grid_pos], radius);
				int xsize = grid[grid_pos].size();
				if (oid == 0)
					grid[grid_pos].push_front(last_area);
				else if (oid >= xsize) 
					grid[grid_pos].push_back(last_area);
				else {
					i = 0;
					List<int>::Element *e = grid[grid_pos].front();
					while (i != oid && e) {
						e = e->next();
						i++;
					}
					if (e)
						grid[grid_pos].insert_before(e, last_area);
				}
			}
		}
	last_area++;
}

List<int> WorldMapData::get_circles_for_pos(int x, int y)
{
	Vector2i grid_pos;
	grid_pos.x = x / grid_size;
	grid_pos.y = y / grid_size;
	if (grid.has(grid_pos))
		return grid[grid_pos];
	else
		return List<int>();
}

bool WorldMapData::in_circle(int id, int x, int y)
{
	if (id >= areas.size())
		return false;
	struct area a = areas[id];
	int xx = x - a.x;
	int yy = y - a.y;
	int rsq = xx * xx + yy * yy;
	if ((float)rsq < a.radius * a.radius)
		return true;
	return false;
}

int WorldMapData::get_sorted_index(const List<int> &items, float radius) const
{
	int low = 0;
	int high = items.size();
	while (low < high) {
		int mid = (low + high) >> 1;
		if (areas[items[mid]].radius < radius)
			low = mid + 1;
		else
			high = mid;
	}
	return low;
}

void WorldMapData::clear()
{
	last_area = 0;
	alloc_count = 1000;
	areas.resize(alloc_count);
	grid.clear();
}

WorldMapData::WorldMapData()
{
	grid_size = 100;
	last_area = 0;
	alloc_count = 1000;
	areas.resize(alloc_count);
#ifdef WORLD_MAP_TESTS
	tests_run = false;
#endif
	world_x = 2048;
	world_y = 2048;
	seed = 1038;
}
WorldMapData::~WorldMapData()
{
}
void WorldMapData::build_point_clusters_iteration(Vector2i seed_point, int bound, int count)
{
	Ref<RandomNumberGenerator> rnd;
	rnd.instance();
	rnd->set_seed(seed);
	int mind = MIN(bound / count / 4, 1);
	int rmax = bound / mind;
	if (rmax == 0)
		return;
	float radius = (float)bound / (float)count;
	float radiussq = radius * radius;
	List<Vector2i> points;
	int n = 0;
	int max_steps = 10000;
	while (n < count && max_steps-- > 0) {
		int x = seed_point.x + rnd->randi() % rmax;
		int y = seed_point.y + rnd->randi() % rmax;
		if (x >= world_x || y >= world_y)
			continue;
		bool good = true;
		List<Vector2i>::Element *e = points.front();
		while (e) {
			Vector2i p = e->get();
			int dx = x - p.x;
			int dy = y - p.y;
			if (dx * dx + dy * dy < (int)radiussq) {
				good = false;
				break;
			}
			e = e->next();
		}
		if (good) {
			add_circle(x, y, radius);
			points.push_back(Vector2i(x, y));
			n++;
		}
	}
}
void WorldMapData::save_debug_image()
{
	int i, j;
	Ref<Image> image;
	HashMap<int, Color> colors;
	image.instance();
	image->create(2048, 2048, false, Image::FORMAT_RGB8);
	image->lock();
	Color newcolor(0.1f, 0.2f, 1.0f);
	printf("world %d %d\n", world_x, world_y);
	for (i = 0; i < image->get_width(); i++)
		for (j = 0; j < image->get_height(); j++) {
			List<int> data = get_circles_for_pos(i * world_x / 2048, j * world_y / 2048);
			while (data.size() > 0) {
				int id = data.front()->get();
				if (in_circle(id, i * world_x / 2048, j * world_y / 2048)) {
					if (!colors.has(id)) {
						colors[id] = newcolor;
						newcolor.r = (newcolor.r + fmodf(1321.27f * (float)i, 13.0f) / 13.0f) / 1.9f + 0.1f;
						newcolor.g = (newcolor.r + newcolor.g + fmodf(5321.23f * (float)i, 10.0f) / 10.0f) / 2.9f + 0.1f;
						newcolor.b = (newcolor.r + newcolor.g + newcolor.b + fmodf(1121.13f * (float)i, 11.0f) / 11.0f) / 3.9f + 0.1f;
					}
					image->set_pixel(i, j, colors[id]);
					break;
				}
				data.pop_front();
			}
		}
	image->unlock();
	image->save_png("world_map_test.png");
}
#ifdef WORLD_MAP_TESTS
void WorldMapData::unit_test()
{
	int i;
	add_circle(100, 100, 50.0f);
	add_circle(200, 200, 100.0f);
	add_circle(100, 300, 30.0f);
	add_circle(300, 100, 30.0f);
	for (i = 0; i < 1000; i++) {
		int x = (i * 100) % 1000;
		int y = (i * 100) / 1000;
		add_circle(x, y, 90.0f);
	}
	save_debug_image();
}
#endif
