#include "Grid.h"

Grid::Grid(int gridSize)
{
	assert(gridSize >= 20); //The smallest grid supported is size 20x20.
	this->gridSize = gridSize;

	grid = nullptr;
	counter = nullptr;
	playerPosX = -1;
	playerPosY = -1;
}

Grid::~Grid()
{
	if (counter != nullptr)
	{
		delete counter;
	}

	if (grid != nullptr)
	{
		for (int i = 0; i < gridSize; i++)
		{
			delete grid[i];
		}
		delete grid;
	}
}

void Grid::InititaliseGrid()
{
	//Initialise and set to 0.
	grid = new griditem * [gridSize];
	counter = new GridCounter();
	for (int i = 0; i < gridSize; i++)
	{
		grid[i] = new griditem[gridSize];
		for (int j = 0; j < gridSize; j++)
		{
			grid[i][j] = griditem::empty;
		}
	}

	CreateOuterWalls();
	CreateInnerWalls();
	PlacePlayerAndTarget();
}

void Grid::PlacePlayerAndTarget()
{
	grid[1][gridSize - 2] = griditem::target;
	grid[gridSize - 2][1] = griditem::player;
	playerPosX = 1;
	playerPosY = gridSize - 2;
}

void Grid::PrintGrid()
{
	//calling this function to force update the counters
	auto lGrid = GetGrid();
	for (int i = 0; i < gridSize; i++)
	{
		for (int j = 0; j < gridSize; j++)
		{
			cout << ConvertGridItemToChar(lGrid[i][j]) << " ";
		}
		cout << endl;
	}
}

griditem** Grid::GetGrid()
{
	counter->IncreaseAccessCounter(gridSize * gridSize);
	return grid;
}

griditem Grid::GetGridSquare(int x, int y)
{
	counter->IncreaseAccessCounter();
	return grid[y][x];
}

GridCounter* Grid::GetCounter()
{
	return counter;
}

bool Grid::CheckComplete()
{
	return grid[1][gridSize - 2] == griditem::player;
}

moveresults Grid::MovePlayer(movedirection direction)
{
	int newPosX = playerPosX, newPosY = playerPosY;

	switch (direction) 
	{
	case movedirection::left:
		newPosX --;
		break;
	case movedirection::right:
		newPosX ++;
		break;
	case movedirection::up:
		newPosY--;
		break;
	case movedirection::down:
		newPosY++;
		break;
	}

	switch (grid[newPosY][newPosX])
	{
	case griditem::wall:
		counter->IncreaseWallHitCounter();
		return moveresults::wall;
	case griditem::target:
		counter->IncreaseMoveCounter();
		UpdatePlayerPosition(newPosX, newPosY);
		return moveresults::complete;
	case griditem::empty:
		counter->IncreaseMoveCounter();
		UpdatePlayerPosition(newPosX, newPosY);
		return moveresults::ok;
	default:
		//This definitely shouldn't be possible...
		exit(-1);
	}
}

void Grid::CreateOuterWalls()
{
	for (int i = 0; i < gridSize; i++)
	{
		grid[i][0] = griditem::wall;
		grid[0][i] = griditem::wall;
		grid[i][gridSize - 1] = griditem::wall;
		grid[gridSize - 1][i] = griditem::wall;
	}
}

void Grid::CreateInnerWalls()
{
	random_device dev;
	mt19937 rng(dev());

	CreateFirstWall(rng);
	CreateSecondWall(rng);
	CreateThirdWall(rng);
	CreateRectangles(rng);
	CreateFourthWall(rng);
}

void Grid::CreateFirstWall(mt19937& rng)
{
	uniform_int_distribution<mt19937::result_type> dist(1, gridSize - 2);

	for (int i = 0; i < gridSize; i++)
	{
		grid[2][i] = griditem::wall;
	}

	grid[2][dist(rng)] = griditem::empty;
}

void Grid::CreateSecondWall(mt19937& rng)
{
	uniform_int_distribution<mt19937::result_type> dist(1, gridSize - 2);

	for (int i = 0; i < gridSize; i++)
	{
		grid[4][i] = griditem::wall;
	}

	grid[4][dist(rng)] = griditem::empty;
	grid[4][dist(rng)] = griditem::empty;
}

void Grid::CreateThirdWall(mt19937& rng)
{
	uniform_int_distribution<mt19937::result_type> dist(0, 1);

	for (int i = 0; i < gridSize; i++)
	{
		grid[6][i] = griditem::wall;
	}

	if (dist(rng) == 0)
	{
		grid[6][1] = griditem::empty;
	}
	else
	{
		grid[6][gridSize - 2] = griditem::empty;
	}
}

void Grid::CreateRectangles(mt19937& rng)
{
	uniform_int_distribution<mt19937::result_type> dist(1, gridSize - 6);

	for (int i = 0; i < 3; i++)
	{
		int sp = dist(rng);
		for (int j = sp; j < sp + 5; j++)
		{
			for (int k = 8; k < 13; k++)
			{
				grid[k][j] = griditem::wall;
			}
		}
	}
}

void Grid::CreateFourthWall(mt19937& rng)
{
	uniform_int_distribution<mt19937::result_type> dist(1, gridSize - 7);

	for (int i = 0; i < 2; i++)
	{
		int sp = dist(rng);
		for (int j = sp; j < sp + 6; j++)
		{
			grid[16][j] = griditem::wall;
		}
	}
}

char Grid::ConvertGridItemToChar(griditem it)
{
	switch (it)
	{
	case griditem::empty:
		return ' ';
	case griditem::player:
		return 'P';
	case griditem::target:
		return 'T';
	case griditem::wall:
		return 'W';
	default:
		return '?';
	}
}

void Grid::UpdatePlayerPosition(int x, int y)
{
	//This assumes we already checked that this is a valid move.
	assert(grid[y][x] != griditem::wall);

	grid[playerPosY][playerPosX] = griditem::empty;
	grid[y][x] = griditem::player;

	playerPosX = x;
	playerPosY = y;
}
