#include <modules/world/road_grid.h>

#include <scene/main/node.h>
#include <scene/3d/mesh_instance.h>
#include <scene/3d/immediate_geometry.h>
#include <scene/resources/concave_polygon_shape.h>

class StaticBody;
struct vshape;

struct RoadMeshData {
	PoolVector<Vector3> points;
	float width1;
	float width2;
	int flags;
	float sidewalk_width;
	struct vshape *vshape;
};

class Roads: public MeshInstance {
	GDCLASS(Roads, MeshInstance);
protected:
	Mutex mutex;
	Ref<Curve> curve;
	Ref<FastNoiseLite> noise;
	Ref<Material> mat;
	Ref<PackedScene> road_data;
	HashMap<String, Array> mesh_data;
	HashMap<String, Vector3> offsets;
	bool _set(const StringName &p_name, const Variant &p_value);
	bool _get(const StringName &p_name, Variant &r_ret) const;
	void _get_property_list(List<PropertyInfo> *p_list) const;
	static void _bind_methods();
	void update_all();
	float sidewalk_width;
	struct thread_data {
		Thread thread;
		Ref<Material> mat;
		Node *root;
		Ref<ArrayMesh> mesh;
		MeshInstance *xmi;
		struct RoadMeshData data;
	};
	struct thread_data thread;
	static void generate_threaded(void *p_userdata);
	StaticBody *body;
	Transform cam_xform;
#if 0
	struct edge_data {
		int a;
		int b;
		float width;
		bool sidewalk;
		float sidewalk_width;
		edge_data(): a(-1), b(-1), width(1.0f), sidewalk(false), sidewalk_width(1.0f)
		{}
	};
#endif
#if 0
	PoolVector<struct edge_data> edges;
#endif
#if 0
	void extrude_direct(Array &out, const Array &arrays, const struct edge_data *data) const;
	void extrude_vshape(Array &out, const Array &arrays, const struct vshape *data) const;
#endif
	void _notification(int p_what);
	int counter;
	friend class RoadsData;
public:
#if 0
	void update(Ref<RoadGrid> roads, Vector3 where, float radius);
#endif
	Roads();
	~Roads();
	Vector3 calculate_offsets(const Array &data) const;
	Vector3 quadratic_bezier(const Vector3 &p1, const Vector3 &p2,
			const Vector3 &p3, float t) const;
	void all_offsets();
	PoolVector<String> build_item_list(float width, int flags, float sidewalk_width) const;
	Ref<Curve3D> build_curve(Vector3 p1, Vector3 p2, Vector3 p3, float total_width) const;
	// Array curve_mesh(PoolVector<Vector3> points, float width1, float width2, int flags, float sidewalk_width);
	Array curve_mesh(const PoolVector<Vector3> &points, Ref<Curve3D> curve3, float total_width1, float total_width2,
			const PoolVector<String> &parts_list1,
			const PoolVector<String> &parts_list2,
			int flags, float sidewalk_width);
	int make_vmesh(Node *root, Ref<Material> mat, Ref<ArrayMesh> mesh, MeshInstance *xmi, RoadMeshData *data);
	void process_vshapes();
	void create_vshape_data(struct vshape *v, int flags, float sidewalk_width);
	void create_data(struct vshape *v,
			struct RoadMeshData *data,
			int flags, float sidewalk_width);
	void add_scene_element(Node *root, Node *xnode, const Vector3 &p2, Ref<ConcavePolygonShape> shape);
	inline float get_total_width(float width, int flags, float sidewalk_width);
	inline Ref<Curve3D> get_curve(const PoolVector<Vector3> &points, float width, int flags, float sidewalk_width);
	void build_vshape_data();
};

class RoadsData: public Object {
	GDCLASS(RoadsData, Object);
protected:
	RoadGrid *rg;
	static void _bind_methods();
	Ref<Curve> curve;
	Ref<FastNoiseLite> noise;
	Mutex sdf_mutex;
	HashMap<int, float> sdf_data;
public:
	RoadsData();
	~RoadsData();
	static RoadsData *get_singleton();
	static void create_singleton();
	static void destroy_singleton();
	RoadGrid *get_road_grid() const;
	void set_noise(Ref<FastNoiseLite> noise);
	void set_curve(Ref<Curve> curve);
	float get_sdf(int x, int y, int z);
	Vector2 get_site_pos(int site);
	PoolVector<Vector2> get_site_polygon_2d(int site);
	PoolVector<Vector3> get_site_polygon_3d(int site);
	PoolVector<int> get_here_sites(const Vector3 &pos);
	bool site_is_town(int site) const;
	bool site_is_farm(int site) const;
	inline float get_site_avg_height(int site)
	{
		return rg->get_site_avg_height(site);
	}
	inline void save_json(const String &path)
	{
		rg->save_json(path);
	}
	inline PoolVector<Vector3> get_site_border(int site)
	{
		return rg->get_site_border(site);
	}
	inline int get_site_count() const
	{
		return rg->get_site_count();
	}
	inline int get_site_type(int site) const
	{
		return rg->get_site_type(site);
	}
};

