#include <core/hash_map.h>
#include <core/math/vector3.h>
#include <scene/resources/packed_scene.h>
#include <scene/resources/primitive_meshes.h>
#include <scene/3d/mesh_instance.h>
#include "direct_lod_multimesh.h"
#include "world_chunk.h"
#include "terrain_object.h"
#include "world_terrain_lod.h"

void WorldTerrainLod::init_lod(int mind, int maxd,
		float density_range)
{
	int i;

	min_dist = mind;
	max_dist = maxd;
	this->density_range = density_range;
}
void WorldTerrainLod::update()
{
}

void WorldTerrainLod::create_chunk(const WorldChunk *chunk, const List<TerrainObject> &obj_list)
{
	int i, j, id;
	if (mesh_h.has(chunk->get_loc()))
		return;
	int lod_offset = 1 << chunk->get_lod();
	int obj_count = obj_list.size();
	printf("create_chunk started %d\n", obj_count);

	LodMultiMesh3D *mi = memnew(LodMultiMesh3D);
	id = 0;
	int rcount = chunk->get_transforms_count() / lod_offset;
	const Transform *xfptr = chunk->get_transforms();
	const float *variances = chunk->get_variance();
	Vector<int> ids;
	ids.resize(rcount);
	printf("ids size: %d rcount: %d\n", ids.size(), rcount);
	HashMap<int, List<Transform> > instances;
	for (j = 0; j < rcount; j++) {
		float variance = variances[j * lod_offset];
		int obj_id = (int)(variance * obj_count);
		printf("3 %d %d", j, rcount);
		ids.write[j] = obj_id;
		if (!instances.has(obj_id))
			instances[obj_id] = List<Transform>();
		instances[obj_id].push_back(xfptr[j * lod_offset]);
		printf("6");
	}
	const List<TerrainObject>::Element *e = obj_list.front();
	int count = 0;
	while (e) {
		printf("obj: %d\n", count);
		const TerrainObject *obj = &e->get();
		if (!instances.has(count))
			instances[count] = List<Transform>();
		Ref<Mesh> mesh = obj->get_mesh(chunk->get_lod());
		printf("lod: %d mesh: %p, instance count: %d\n", chunk->get_lod(), mesh.ptr(), instances[count].size());
		mi->add_mesh(obj->get_mesh(chunk->get_lod()));
		mi->set_instance_count(count, instances[count].size());
		const List<Transform>::Element *ep = instances[count].front();
		int pcount = 0;
		while (ep) {
			const Transform &pxform = ep->get();
			printf("ep %d %d %ls\n", count, pcount, String(pxform.origin).c_str());
			mi->set_instance_transform(count, pcount++, pxform);
			ep = ep->next();
		}
		e = e->next();
		count++;
	}
#if 0
	for (j = 0; j < mesh_grass.size(); j++) {
		if (!new_points[*org].has(j))
			continue;
		const List<struct new_point> &nps = new_points[*org][j];
		mi->add_mesh(mesh_grass[j]);
		mi->set_instance_count(id, nps.size());
		const List<struct new_point>::Element *e = nps.front();
		int icount = 0;
		while(e) {
			mi->set_instance_transform(id, icount++, e->get().xform);
			e = e->next();
		}
		id++;
	}
#endif
	mi->hide();
	mi->set_world(NULL);
	mi->set_global_transform(Transform(Basis(), chunk->get_loc().to_vec3()));
	meshes.push_back(mi);
	mesh_h[chunk->get_loc()] = mi;
	mi->set_world(gworld.ptr());
	mi->show();
}
void WorldTerrainLod::cleanup(const Transform &vxform)
{
	if (meshes.size() > 0)
		printf("meshes.size() %p %d\n", this, meshes.size());
	List<LodMultiMesh3D *>::Element *e = meshes.front();
	while(e) {
		LodMultiMesh3D *ve = e->get();
		List<LodMultiMesh3D *>::Element *ne = e->next();
		Vector3 rpos = ve->get_center();
		Vector3 vpos = vxform.origin;
		Vector3i loc(ve->get_global_transform().origin);
		if (!valid_org(vxform, ve->get_global_transform().origin, 0.5f)) {
			memfree(ve);
			meshes.erase(e);
			mesh_h.erase(loc);
		}
		e = ne;
	}
}
static inline bool too_close(const Transform &vxform, const Vector3 &org, float zmin)
{
	Vector3 lpoint = vxform.xform_inv(org);
	if (-lpoint.z < zmin)
		return true;
	return false;
}
bool WorldTerrainLod::valid_org(const Transform &vxform, const Vector3 &org, float margin) const
{
	bool ret = true;
	float view_angle = M_PI / 3.0f;
	float view_s = Math::sin(view_angle);
	if (too_close(vxform, org, min_dist))
		return false;
	// if (vxform.origin.distance_squared_to(org) > max_dist * max_dist)
	//	return false;
	//if (min_dist > 0 && vxform.origin.distance_squared_to(org) < min_dist * min_dist)
	//	return false;
	Vector3 lpoint = vxform.xform_inv(org);
	/* View is in -Z direction */
	if (lpoint.z > -min_dist + margin || lpoint.z < -max_dist - margin)
		ret = false;
	if (lpoint.x < -view_s * max_dist -8.0f - margin|| lpoint.x > view_s * max_dist + 8.0f + margin)
		ret = false;
	if (lpoint.y < -36.0f || lpoint.y > 36.0f)
		ret = false;
	return ret;
}

void WorldTerrainLod::update_visibility(const Transform &vxform)
{
	List<LodMultiMesh3D *>::Element *e = meshes.front();

	while(e) {
		LodMultiMesh3D *mi = e->get();
		Transform xform = mi->get_global_transform();
		// Vector3 lpoint = vxform.xform_inv(xform.origin);
		if (mi->is_visible() && !valid_org(vxform, xform.origin, 1.0f)) {
			mi->hide();
			mi->set_world(NULL);
		} else if (!mi->is_visible() && valid_org(vxform, xform.origin, 0.0f)) {
			mi->show();
			mi->set_world(gworld.ptr());
		}
		e = e->next();
	}
}
void WorldTerrainLod::set_world(Ref<World> world)
{
	if (!world.ptr())
		printf("exited world %p\n", gworld.ptr());
	gworld = world;
	List<LodMultiMesh3D *>::Element *e = meshes.front();
	while(e) {
		LodMultiMesh3D *mi = e->get();
		mi->set_world(gworld.ptr());
		e = e->next();
	}
	if (gworld.ptr())
		printf("entered world %p\n", gworld.ptr());
}

