#include <cassert>
#include <scene/main/viewport.h>
#include <scene/3d/camera.h>
#include "spawner.h"
static Spawner *g_spawner_data = NULL;
Spawner * Spawner::get_singleton()
{
	return g_spawner_data;
}
void Spawner::create_singleton()
{
	g_spawner_data = memnew(Spawner);
}
void Spawner::destroy_singleton()
{
	memdelete(g_spawner_data);
}

void Spawner::_bind_methods()
{
	ClassDB::bind_method(D_METHOD("add_scene", "name", "scene"), &Spawner::add_scene);
	ClassDB::bind_method(D_METHOD("remove_scene", "name"), &Spawner::remove_scene);
	ClassDB::bind_method(D_METHOD("place_scene", "name", "place"), &Spawner::place_scene);
	ClassDB::bind_method(D_METHOD("place_scene_relative", "name", "parent", "place"), &Spawner::place_scene_relative);
	ClassDB::bind_method(D_METHOD("update_view", "node", "distance"), &Spawner::update_view);
}
void Spawner::add_scene(const StringName &name, Ref<PackedScene> scene)
{
	lock.lock();
	if (!scenes.has(name)) {
		scenes[name] = scene;
		positions[name] = PoolVector<struct spawn_object>();
	}
	lock.unlock();
}
void Spawner::remove_scene(const StringName &name)
{
	int i;
	lock.lock();
	PoolVector<struct spawn_object> pos = positions[name];
	for (i = 0; i < pos.size(); i++) {
		if (!pos[i].active)
			continue;
		Object *po = ObjectDB::get_instance(pos[i].instance);
		if (po) {
			Node *pn = Object::cast_to<Node>(po);
			if (pn)
				pn->queue_delete();
		}
		pos.write()[i].active = false;
	}
	positions.erase(name);
	scenes.erase(name);
	lock.unlock();
}
void Spawner::place_scene(const StringName &name, const Transform &place)
{
	lock.lock();
	if (scenes.has(name)) {
		struct spawn_object obj;
		obj.xform = place;
		obj.active = false;
		obj.pooled = false;
		obj.instance = -1;
		PoolVector<struct spawn_object> pos = positions[name];
		pos.resize(pos.size() + 1);
		pos.write()[pos.size() - 1] = obj;
		positions[name] = pos;
		force_update = true;
	}
	lock.unlock();
}
void Spawner::place_scene_relative(const StringName &name, Node *parent, const Transform &place)
{
	if (!parent)
		return;
	lock.lock();
	if (scenes.has(name)) {
		Node *n = scenes[name]->instance();
		Spatial *sp = Object::cast_to<Spatial>(n);
		sp->set_transform(place);
		parent->call_deferred("add_child", sp);
	}
	lock.unlock();
}
void Spawner::update_view(Node *node, float distance)
{
	if (!node)
		return;
	Viewport *view = node->get_viewport();
	if (!view)
		return;
	Camera *cam = view->get_camera();
	if (!cam)
		return;
	Transform cam_xform = cam->get_global_transform();
	Vector3 org = cam_xform.origin;
	if (org.distance_squared_to(last_org) < 64.0f && !force_update)
		return;
	last_org = org;
	const String *key;
	lock.lock();
	for (key = positions.next(NULL); key; key = positions.next(key)) {
		int i;
		PoolVector<struct spawn_object> pos = positions[*key];
		for (i = 0; i < pos.size(); i++) {
			if (!pos[i].active && !pos[i].pooled) {
				Transform x = pos[i].xform;
				float d = org.distance_squared_to(x.origin);
				if (d < distance * distance && pos[i].instance < 0) {
					int j;
					Node *pn = NULL;
					bool pooling = false;
					if (pos.size() > 4) {
						for (j = 0; j < pos.size(); j++) {
							if (j == i)
								continue;
							if (!pos[j].active && pos[j].instance >= 0 && pos[j].pooled) {
								pos.write()[i].instance = pos[j].instance;
								pos.write()[j].instance = -1;
								pos.write()[j].pooled = false;
								Object *po = ObjectDB::get_instance(pos[i].instance);
								if (po) {
									pn = Object::cast_to<Node>(po);
									if (pn) {
										pooling = true;
										break;
									}
								}
							}
						}
					}
					if (!pn)
						pn = scenes[*key]->instance();
					assert(pn);
					if (pn) {
						Spatial *sp = Object::cast_to<Spatial>(pn);
						assert(sp);
						if (sp) {
							if (!pooling)
								view->add_child(sp);
							sp->set_global_transform(x);
							pos.write()[i].instance = sp->get_instance_id();
							pos.write()[i].active = true;
							pos.write()[i].pooled = false;
							sp->show();
						} else 
							memfree(pn);
					}
				}
			} else if (pos[i].active) {
				Transform x = pos[i].xform;
				float d = org.distance_squared_to(x.origin);
				if (d > distance * distance + 2500.0f) {
					Object *po = ObjectDB::get_instance(pos[i].instance);
					if (po) {
						Node *pn = Object::cast_to<Node>(po);
						if (pn)
							pn->queue_delete();
					}
					pos.write()[i].active = false;
					pos.write()[i].instance = -1;
					pos.write()[i].pooled = false;
				} else if (d > distance * distance + 600.0f) {
					Object *po = ObjectDB::get_instance(pos[i].instance);
					if (po) {
						Spatial *pn = Object::cast_to<Spatial>(po);
						pn->hide();
					}
					pos.write()[i].active = false;
					pos.write()[i].pooled = true;
				}
			}

		}
		positions[*key] = pos;
	}
	force_update = false;
	lock.unlock();
}


