#pragma once

namespace WallPlacement
{
	struct FFEPosition
	{
		TilePosition gateway_position = TilePositions::None;
		TilePosition forge_position = TilePositions::None;
		TilePosition pylon_position = TilePositions::None;
		std::vector<TilePosition> cannon_positions;
		std::vector<Gap> gaps;
		size_t gap_unit_count = 0;
		int box_size = 0;
		bool pylon_part_of_wall = false;
		bool multichoke_wall = false;
		bool multichoke_clockwise = false;
	};
	
	class GraphBase
	{
	public:
		GraphBase(const BWEM::Base* base,const std::vector<WalkPosition>* wall1,const std::vector<WalkPosition>* wall2,const std::vector<const InformationUnit*>& neutral_buildings) : base_(base), wall1_(wall1), wall2_(wall2), neutral_buildings_(neutral_buildings) {}
		GraphBase(const BWEM::Base* base,const std::vector<WalkPosition>* wall,const std::vector<const InformationUnit*>& neutral_buildings) : base_(base), wall1_(wall), wall2_(nullptr), neutral_buildings_(neutral_buildings) {}
		
		bool is_single_wall() const { return wall2_ == nullptr; }
		const std::vector<const InformationUnit*>& neutral_buildings() const { return neutral_buildings_; }
		
	private:
		const BWEM::Base* base_;
		const std::vector<WalkPosition>* wall1_;
		const std::vector<WalkPosition>* wall2_;
		const std::vector<const InformationUnit*>& neutral_buildings_;
		
		friend class Graph;
	};
	
	class Graph
	{
	public:
		Graph(const GraphBase& graph_base,UnitType basic_combat_unit_type,UnitType tight_for_unit_type);
		
		bool is_valid() const { return valid_; }
		const std::vector<Gap>& gaps() const { return gaps_; }
		size_t gap_unit_count() const { return gap_unit_count_; }
	protected:
		class Node
		{
		public:
			Node() {}
			Node(const std::vector<WalkPosition>* terrain) : terrain_(terrain) {}
			Node(UnitType unit_type,TilePosition tile_position) : unit_type_(unit_type), tile_position_(tile_position) {}
			
			const std::vector<WalkPosition>* terrain() const { return terrain_; }
			UnitType unit_type() const { return unit_type_; }
			TilePosition tile_position() const { return tile_position_; }
			const std::map<size_t,Gap>& neighbours() const { return neighbours_; }
			void add_neighbour(size_t index,const Gap& gap) { neighbours_[index] = gap; }
			Gap determine_gap(const Node& node) const;
			
		private:
			const std::vector<WalkPosition>* terrain_ = nullptr;
			UnitType unit_type_ = UnitTypes::None;
			TilePosition tile_position_ = TilePositions::None;
			
			std::map<size_t,Gap> neighbours_;
			
			Gap determine_gap(UnitType type,TilePosition position,const std::vector<WalkPosition>* wall_positions) const;
			Gap determine_gap(UnitType type1,TilePosition position1,UnitType type2,TilePosition position2) const;
		};
		
		std::vector<Node> nodes_;
		size_t start_node_;
		size_t end_node_;
		
		std::vector<Gap> gaps_;
		size_t gap_unit_count_;
		bool valid_ = false;
		
		UnitType basic_combat_unit_type_;
		UnitType tight_for_unit_type_;
		
		void add_wall_and_neutrals(const GraphBase& graph_base);
		size_t add_wall_node(const std::vector<WalkPosition>* wall);
		size_t add_base_node(const BWEM::Base* base);
		void add_neutral_building_nodes(const std::vector<const InformationUnit*>& neutral_buildings);
		void add_neighbours();
		bool determine_gaps();
		bool passable();
		bool passable(std::set<size_t>& visited,size_t current_index);
	};
	
	class FFEGraph : public Graph
	{
	public:
		FFEGraph(const GraphBase& graph_base,const FFEPosition& ffe_position);
	private:
		void add_ffe_position(const FFEPosition& ffe_position);
		
	};
	
	struct Chokepoints
	{
		std::vector<const BWEM::ChokePoint*> inside_chokepoints;
		std::vector<const BWEM::ChokePoint*> outside_chokepoints;
	};
	
	class MultipleChokePlacer
	{
	public:
		MultipleChokePlacer(const Chokepoints& chokepoints);
		const std::vector<WalkPosition>& wall_walk_positions() const { return wall_walk_positions_; }
		bool clockwise() const { return clockwise_; }
		
	private:
		enum class NodeType
		{
			Mineral,
			InsideChoke,
			OutsideChoke,
			Wall
		};
		
		struct Node
		{
			NodeType type;
			double angle;
			FastWalkPosition wall_position;
			
			bool operator<(const Node& o) const { return angle < o.angle; }
		};
		
		std::vector<Node> nodes_;
		std::vector<size_t> nonwall_node_indexes_;
		std::vector<std::pair<size_t,size_t>> wall_position_;
		std::vector<WalkPosition> wall_walk_positions_;
		bool clockwise_ = false;
		
		void create_nodes(const Chokepoints& chokepoints);
		void determine_nonwall_node_indexes();
		void determine_wall_position();
		double calculate_angle(const BWEM::Base* base,Position position);
	};
	
	class FFEPlacer
	{
	public:
		bool place_ffe();
	private:
		std::optional<FFEPosition> place_using_graph_base(const TileGrid<bool>& tiles,const GraphBase& graph_base,FastTilePosition top_left,FastTilePosition bottom_right);
		std::vector<FFEPosition> place_gateway_forge(const TileGrid<bool>& in_tiles,TilePosition top_left,TilePosition bottom_right,const GraphBase& graph_base);
		int calculate_box_size(FFEPosition& ffe_position);
		void place_pylon_and_cannons(FFEPosition& ffe_position,const TileGrid<bool>& in_tiles,const GraphBase& graph_base);
		void place_cannons(FFEPosition& ffe_position,TileGrid<bool>& tiles,const GraphBase& graph_base);
		bool check_building_neutral_unit_collision(const GraphBase& graph_base,UnitType unit_type,TilePosition building_tile_position);
		bool check_building_inside(const FFEPosition& ffe_position,UnitType unit_type,TilePosition building_tile_position);
		Chokepoints determine_natural_inside_and_outside_chokepoints();
		TileGrid<bool> determine_tiles_in_range(FastTilePosition top_left,FastTilePosition bottom_right);
		std::vector<TilePosition> determine_forge_positions(const TileGrid<bool>& tiles,TilePosition gateway_position);
		std::vector<TilePosition> determine_forge_positions_near_pylon(const TileGrid<bool>& tiles,TilePosition pylon_position);
		std::vector<TilePosition> determine_gateway_positions_above_forge(const TileGrid<bool>& tiles,TilePosition pylon_position,TilePosition forge_position);
	};
	
	std::vector<const InformationUnit*> determine_neutral_buildings(FastTilePosition top_left,FastTilePosition bottom_right);
	bool can_place_building_at(const TileGrid<bool>& tiles,UnitType unit_type,TilePosition building_tile_position);
	void set_building_at_tile_grid(TileGrid<bool>& tiles,UnitType unit_type,TilePosition building_tile_position,bool value);
	TileGrid<bool> determine_tiles_around_chokepoint(const BWEM::ChokePoint* cp);
	void remove_base_and_neutrals(TileGrid<bool>& tiles);
	void remove_line(TileGrid<bool>& tiles,const BWEM::Base* base,const BWEM::Neutral* neutral);
	bool tile_fully_walkable_in_areas(TilePosition tile_position,const BWEM::Area* area1,const BWEM::Area* area2);
	bool wall_of_areas(FastWalkPosition walk_position,const BWEM::Area* area1,const BWEM::Area* area2);
}
