#ifndef TILING_WAVE_FORM_COLLAPSE_H
#define TILING_WAVE_FORM_COLLAPSE_H

#include "array_2d.h"
#include "core/vector.h"

#include "wave_form_collapse.h"

struct Tile {
	struct ActionMap {
		Vector<int> map[8];

		void set_size(int size) {
			for (int i = 0; i < 8; ++i) {
				map[i].resize(size);
			}
		}

		void zero() {
			for (int i = 0; i < 8; ++i) {
				map[i].fill(0);
			}
		}
	};

	static const uint8_t ROTATION_MAP[6][9];
	static const uint8_t REFLECTION_MAP[6][9];

	String name;
	Vector<Array2D<int>> data;
	WaveFormCollapse::Symmetry symmetry;
	double weight;

	static ActionMap generate_action_map(const WaveFormCollapse::Symmetry symmetry);
	static Vector<Array2D<int>> generate_oriented(Array2D<int> data, WaveFormCollapse::Symmetry symmetry);

	void set_generate_data(const PoolIntArray &p_data, const int width, const int height);

	PoolIntArray data_get(const int index);
	void data_set(const int index, const PoolIntArray &p_data, const int width, const int height);
	void data_remove(const int index);

	Tile();
	Tile(WaveFormCollapse::Symmetry p_symmetry, double p_weight);
	Tile(const Vector<Array2D<int>> &p_data, WaveFormCollapse::Symmetry p_symmetry, double p_weight);
	Tile(const Array2D<int> &p_data, WaveFormCollapse::Symmetry p_symmetry, double p_weight);
	Tile(const PoolIntArray &p_data, const int width, const int height, WaveFormCollapse::Symmetry p_symmetry, double p_weight);
};

class TilingWaveFormCollapse : public WaveFormCollapse {
	GDCLASS(TilingWaveFormCollapse, WaveFormCollapse);

public:
	struct NeighbourData {
		int data[4];

		NeighbourData() {
			for (int i = 0; i < 4; ++i) {
				data[i] = 0;
			}
		}

		NeighbourData(const int n1, const int n2, const int n3, const int n4) {
			data[0] = n1;
			data[1] = n2;
			data[2] = n3;
			data[3] = n4;
		}
	};

	struct DensePropagatorHelper {
		Vector<bool> directions[4];

		void resize(const int size) {
			for (int i = 0; i < 4; ++i) {
				directions[i].resize(size);
				directions[i].fill(false);
			}
		}
	};

	struct IdToTilePair {
		int id;
		int oriented_tile;

		IdToTilePair() {
			id = 0;
			oriented_tile = 0;
		}

		IdToTilePair(int p_id, int p_oriented_tile) {
			id = p_id;
			oriented_tile = p_oriented_tile;
		}
	};

public:
	int tile_add_generated(const PoolIntArray &data, const int width, const int height, const WaveFormCollapse::Symmetry symmetry, const float weight);
	int tile_add(const WaveFormCollapse::Symmetry symmetry, const float weight);
	int tile_create();
	void tile_remove(const int tile_index);
	int tile_count_get();

	void tile_data_add(const int tile_index, const PoolIntArray &data, const int width, const int height);
	void tile_data_generated_add(const int tile_index, const PoolIntArray &data, const int width, const int height);
	PoolIntArray tile_data_get(const int tile_index, const int data_index);
	void tile_data_set(const int tile_index, const int data_index, const PoolIntArray &data, const int width, const int height);
	void tile_data_remove(const int tile_index, const int data_index);
	void tile_data_clear(const int tile_index);
	int tile_data_count_get(const int tile_index);
	int tile_data_required_count_get(const int tile_index);

	int tile_width_get(const int tile_index, const int data_index);
	int tile_height_get(const int tile_index, const int data_index);

	WaveFormCollapse::Symmetry tile_symmetry_get(const int tile_index);
	void tile_symmetry_set(const int tile_index, const WaveFormCollapse::Symmetry val);

	float tile_weight_get(const int tile_index);
	void tile_weight_set(const int tile_index, const float val);

	String tile_name_get(const int tile_index);
	void tile_name_set(const int tile_index, const String &val);
	int tile_index_get(const String &tile_name);

	int neighbour_data_add(const int left, const int left_orientation, const int right, const int right_orientation);
	int neighbour_data_add_str(const String &left, const int left_orientation, const String &right, const int right_orientation);
	PoolIntArray neighbour_data_get(const int index);
	void neighbour_data_remove(const int index);
	void neighbour_data_set(const int index, const int left, const int left_orientation, const int right, const int right_orientation);
	void neighbour_data_set_str(const int index, const String &left, const int left_orientation, const String &right, const int right_orientation);
	bool neighbour_data_validate(const int tile_index, const int orientation);
	bool neighbour_data_validate_str(const String &tile_name, const int orientation);

	void set_tiles(const Vector<Tile> &p_tiles);
	void set_neighbours(const Vector<NeighbourData> &p_neighbors);

	void generate_oriented_tile_ids();
	void generate_propagator();

	static Vector<double> get_tiles_weights(const Vector<Tile> &tiles);

	void set_tile(int tile_id, int i, int j);
	bool set_tile(int tile_id, int orientation, int i, int j);

	Array2D<int> run();

	Array2D<int> id_to_tiling(Array2D<int> ids);

	bool validate();
	void initialize();

	TilingWaveFormCollapse();
	~TilingWaveFormCollapse();

protected:
	static void _bind_methods();

private:
	void generate_propagator_add_helper(const Tile::ActionMap &action_map1, const Tile::ActionMap &action_map2,
			Vector<DensePropagatorHelper> *dense_propagator,
			const NeighbourData &neighbour,
			int action, int direction);

	Vector<Tile> _tiles;
	Vector<NeighbourData> _neighbors;

	Vector<IdToTilePair> _id_to_oriented_tile;
	Vector<Vector<int>> _oriented_tile_ids;
};

#endif