// Example of world building, display, and successor computation for the artificial 
// intelligence path-finding lab
//
// Author: Didier LIME
// Adapted by : Jovian HERSEMEULE
// Date: 2018-10-03

#include <iostream>
#include <list>
#include <cstdlib>
#include <ctime>
#include <stack>

using namespace std;

// Tile codes
#define FREE 0
#define WALL 1
#define ORIGIN 2
#define TARGET 3
#define DISCOVERED 4
#define TRACE 5

unsigned int identifyTile(unsigned int y, unsigned int x, unsigned int l) {
	return y * l + x;
}

class World
{
	private:
		// Number of columns
		unsigned int l;

		// Number of lines
		unsigned int h;

		// Size of the array
		const unsigned int size;

		// Unidimensional array for tiles
		int* board;

	public:
		// Constructor
		World(unsigned int l_, unsigned int h_, double p)
			:l(l_), h(h_), size(l_ * h_)
		{
			board = new int[size]();

			// Add walls to the first and last columns
			for (unsigned int i = 0; i < h; i++)
			{
				board[i * l] = WALL;
				board[i * l + l - 1] = WALL;
			}

			// Add walls to the first and last lines
			for (unsigned int j = 0; j < l; j++)
			{
				board[j] = WALL;
				board[(h - 1) * l + j] = WALL;
			}

			for (unsigned int i = 0; i < h; i++)
			{
				for (unsigned int j = 0; j < l; j++)
				{
					// add a wall in this tile with probability p and provided that it is neither
					// the starting tile nor the goal tile 
					if ((double) rand() / RAND_MAX < p && !(i == 1 && j == 1) && !(i == h - 2 && j == l - 2))
					{
						board[i * l + j] = WALL;
					}
				}
			}
		}

		// Display the world
		void display()
		{
			for (unsigned int i = 0; i < h; i++)
			{
				for (unsigned int j = 0; j < l; j++)
				{
					switch (board[identifyTile(i, j, l)])
					{
						case FREE:
							cout << " ";
							break;
						case WALL:
							cout << "#";
							break;
						case ORIGIN:
							cout << "o";
							break;
						case TARGET:
							cout << "T";
							break;
						case DISCOVERED:
							cout << "+";
							break;
						case TRACE:
							cout << "*";
							break;
					}
				}
				cout << endl;
			}
		}

		// compute the successors of tile number i in world w
		// we return the number n of valid successors
		// the actual list is in array r where only the first n
		// elements are significant
		unsigned int successors(unsigned int i, unsigned int r[4])
		{
			unsigned int n = 0;

			if (i >= 0 && i < size && board[i] != WALL)
			{
				// if i is a correct tile number (inside the array and not on a wall)
				// look in the four adjacent tiles and keep only those with no wall
				const unsigned int moves[] = { i - 1, i + 1, i - l, i + l};

				for (unsigned int k = 0; k < 4; k++)
				{
					if (board[moves[k]] != WALL)
					{
						r[n] = moves[k];
						n++;
					}
				}
			}

			return n;
		}

		// Mark a list of points in the world
		void markAll(const list<unsigned int>& path, int value = 2) {
			for (auto tile : path) {
				markOne(tile, value);
			}
		}

		// Mark a point in the world
		void markOne(unsigned int tile, int value = 3) {
			board[tile] = value;
		}



		// Depth-first search
		// starting from tile number s0, find a path to tile number t
		// return true if such a path exists, false otherwise
		// if it exists the path is given in variable path (hence the reference &)
		bool dfs(unsigned int s0, unsigned int t, list<unsigned int>& path)
		{
			bool r = false;

			bool explored[size];

			stack<unsigned int> open;
			open.push(s0);

			for (unsigned int k(0); k < size; k ++)
				explored[k] = false;

			explored[s0] = true;

			int current;
			int neighbour;
			unsigned int succs[4];
			unsigned int nbSuccs;

			do {
				current = open.top();
				open.pop();

				nbSuccs = successors(current, succs);

				for (unsigned int i(0); i < nbSuccs; i++) {
					neighbour = succs[i];

					if (!explored[neighbour])
						open.push(neighbour);
				}

				// Build path
				path.push_back(current);


			} while (!r && !open.empty());

			return r;
		} 
};

int main()
{
	// Initialise the random number generator
	srand(time(0));

	// Create a world
	const unsigned int l(20), h(10);
	const double wallProbability(0.2);

	World w(l, h, wallProbability);

	unsigned int start(identifyTile(1, 1, l));
	unsigned int end(identifyTile(h - 2, l - 2, l));

	// Display it
	cout << endl << "Generated world" << endl;
	w.markOne(start, ORIGIN);
	w.markOne(end, TARGET);
	w.display();

	// Find a path with Depth-First Search
	list<unsigned int> dfsPath;
	bool exitFound = w.dfs(start, end, dfsPath);

	// Display DFS
	cout << endl << "Depth-First Search" << endl;

	w.markAll(dfsPath);
	w.display();

	return 0;
}