/** 
 * Author: Keola Silva
 * Date: 11/18/17
 * AU Username: jks0032
 * Filename: jks0032_3.cpp
 */
 
#include <cstdlib>
#include <sys/stat.h>
#include <iostream>
#include <string.h>
#include <sstream>
#include <vector>
#include <fstream>

using namespace std;

/** Node class.
 */
class Node {
private:
	string name;
	Node* attachedNodes[4];
	Node* snakeOrLadderNode;
public:
	/** Default Node constructor */
	Node() {
		name = "";
		attachedNodes[0] = NULL;
		attachedNodes[1] = NULL;
		attachedNodes[2] = NULL;
		attachedNodes[3] = NULL;
	}
	~Node();
	
	/** Node constructor that accepts the node's name. */
	Node(string newName) {
		name = newName;
		attachedNodes[0] = NULL;
		attachedNodes[1] = NULL;
		attachedNodes[2] = NULL;
		attachedNodes[3] = NULL;
	}
	
	/** Sets the node name using the supplied string */
	void setNodeName(string newName) {
		name = newName;
	}
	
	/** Gets the node name g */
	string getNodeName() {
		return name;
	}
	
	/** Attach a new node at the given direction */
	void attachNewNode(Node* node, int direction) {
		if (direction <= 3 && direction >= 0) {
			attachedNodes[direction] = node;
		}
	}
	
	/** Gets an attached node at the given direction */
	Node* getAttachedNode(int direction) {
		if (direction <= 3 && direction >= 0 && attachedNodes[direction] != NULL) {
			return attachedNodes[direction];
		}
		return NULL;
	}
	
	/** Attach a snake/ladder node */
	void attachSnakeOrLadderNode(Node* node) {
		snakeOrLadderNode = node;
	}
	
	/** Return snake/ladder node */
	Node* getSnakeOrLadderNode() {
		return snakeOrLadderNode;
	}
};

/** Board class, represents all nodes.
 */
class Board {
private:
	Node* startNode;
	Node* endNode;
	std::vector<Node*> allNodes;
	int amountOfNodes;
	string fileName;
public:

	/** Default board constructor */
	Board() {
		startNode = NULL; 
		endNode = NULL;
	}
	~Board();
	
	/** Return start node */
	Node* getStartNode() {
		return startNode;
	}
	
	/** Set the start node */
	void setStartNode(Node* newNode) {
		startNode = newNode;
	}
	
	/** Return the end node */
	Node* getEndNode() {
		return endNode;
	}
	
	/** Sets the node name using the supplied string */
	void setEndNode(Node* newNode) {
		endNode = newNode;
	}
	
	/** Return the specified node from the allNodes vector. */
	Node* findNode(string name) {
		for (std::vector<Node*>::iterator it = allNodes.begin(); 
				it != allNodes.end(); it++) {
			if((*it) -> getNodeName() == name) {
				return *it;
			}
		}
		return NULL;
	}
	
	/** Sets the name of the file to read the nodes from. */
	void setFileName(string fileNameIn) {
		fileName = fileNameIn;
	}

	/** Initializes the board in its entirety by reading from boardFileName */
	void initialize() {
		
        ifstream fileIn;
        fileIn.open(fileName.c_str());
        
        //note the size of the amount of nodes on the board
        string amountOfNodesIn;
		getline(fileIn, amountOfNodesIn, '\n');
		stringstream ss(amountOfNodesIn);
		ss >> amountOfNodes;
		
		//note the name of the start node
        string startNodeIn;
        getline(fileIn, startNodeIn, '\n');
        startNode = new Node(startNodeIn);
        allNodes.push_back(startNode);
        
        //note the name of the end node
        string endNodeIn;
        getline(fileIn, endNodeIn, '\n');
        endNode = new Node(endNodeIn);
        allNodes.push_back(endNode);
        
        string line = "";
        
        while(!fileIn.eof()) {
            getline(fileIn, line, '\n');
            string pointers[6] = {"", "", "", "", "", ""};
			pointers[0] = line.substr(0, 2);
        
			int index = 1;
        
			for (int k = 1; k < (int)line.length(); k++) {
				
				int pos1 = line.find_first_of(' ', k);
				int pos2 = line.find(' ', pos1 + 1);
				
				if (pos2 == -1) {
					pos2 = line.length();
				}
				
				if (pos2 - pos1 == 2) {
					pointers[index] = "*";
				} else if (pos2 - pos1 > 2) {
					pointers[index] = line.substr(pos1 + 1, 2);
				}
				
				k = pos2 - 1;
				
				if (pos2 == (int)line.length() - 1 || index == 6) {
					break;
				}
				
				index++;
			}
			//cout << "NODES:" << endl;
			//~ for(int i = 0; i < 6; i++) {
				//~ cout << i << ": ";
				//~ cout << pointers[i] << std::endl;
			//~ }
			
			//using the pointer names we just made, make the appropriate connections
			//start with making a new node using index 0 if it hasn't been found
			//in allNodes or isn't the start or end nodes
			
			Node* thisNode;
			bool thisNodeAlreadyExists = false;
			
			if (findNode(pointers[0]) != NULL) {
				thisNodeAlreadyExists = true;
				thisNode = findNode(pointers[0]);
			}
			
			if (pointers[0] == startNode -> getNodeName()) {
				thisNode = findNode(startNode -> getNodeName());
			} else if (pointers[0] == endNode -> getNodeName()) {
				thisNode = findNode(endNode -> getNodeName());
			} else {
				if (thisNodeAlreadyExists) {
					thisNode = findNode(pointers[0]);
				} else {
					Node* node = new Node(pointers[0]);
					thisNode = node;
					allNodes.push_back(node);
				}
			}
			
			//remaining indexes 1-5, 1: N, 2: E, 3: S, W: 4, S/L: 5
			for (int i = 1; i < 6; i++) {
				if (i != 5) {
					if (pointers[i] != "*") {
						if (findNode(pointers[i])) {
							thisNode -> attachNewNode(findNode(pointers[i]), i - 1);
						} else {
							Node* node = new Node(pointers[i]);
							thisNode -> attachNewNode(node, i - 1);
							allNodes.push_back(node);
						}
					} else {
						thisNode -> attachNewNode(NULL, i - 1);
					}
				} else {
					if (pointers[i] != "*") {
						if (findNode(pointers[i])) {
							thisNode -> attachSnakeOrLadderNode(findNode(pointers[i]));
						} else {
							Node* node = new Node(pointers[i]);
							thisNode -> attachSnakeOrLadderNode(new Node(pointers[i]));
							allNodes.push_back(node);
						}
					} else {
						thisNode -> attachSnakeOrLadderNode(NULL);
					}
				}
			}
			
			/////DEBUG
			
			//~ Node* nnode = thisNode -> getAttachedNode(0);
			//~ Node* enode = thisNode -> getAttachedNode(1);
			//~ Node* snode = thisNode -> getAttachedNode(2);
			//~ Node* wnode = thisNode -> getAttachedNode(3);
			//~ Node* slnode = thisNode -> getSnakeOrLadderNode();
			
			//~ cout << thisNode -> getNodeName() << " North Node: ";
			//~ if (nnode != NULL) {
				//~ cout << nnode -> getNodeName();
			//~ } else {
				//~ cout << "NULL";
			//~ }
			//~ cout << endl;
			
			//~ cout << thisNode -> getNodeName() << " East Node: ";
			//~ if (enode != NULL) {
				//~ cout << enode -> getNodeName();
			//~ } else {
				//~ cout << "NULL";
			//~ }
			//~ cout << endl;
			
			//~ cout << thisNode -> getNodeName() << " South Node: ";
			//~ if (snode != NULL) {
				//~ cout << snode -> getNodeName();
			//~ } else {
				//~ cout << "NULL";
			//~ }
			//~ cout << endl;
			
			//~ cout << thisNode -> getNodeName() << " West Node: ";
			//~ if (wnode != NULL) {
				//~ cout << wnode -> getNodeName();
			//~ } else {
				//~ cout << "NULL";
			//~ }
			//~ cout << endl;
			
			//~ cout << thisNode -> getNodeName() << " S/L Node: ";
			//~ if (slnode != NULL) {
				//~ cout << slnode -> getNodeName();
			//~ } else {
				//~ cout << "NULL";
			//~ }
			//~ cout << endl;
			
			/////END DEBUG
		}
	}
};

/** User class
 */
class User {
private:
	string name;
	string pathTaken;
	int stepsTaken;
	int lastDiceRoll;
	Node* currentNode;
public:
	/** Default User constructor*/
	User() {
		name = "";
		pathTaken = "";
	}
	
	~User();
	
	/** Constructor that accepts the user's name */
	User(string newName) {
		name = newName;
	}
	
	/** Returns the name of this user */
	string getName() {
		return name;
	}
	
	/** Following methods sets the current node to their respective directions */
	void moveNorth() {
		pathTaken += currentNode -> getNodeName() + " ";
		setCurrentNode(currentNode -> getAttachedNode(0));
	}
	void moveEast() {
		pathTaken += currentNode -> getNodeName() + " ";
		setCurrentNode(currentNode -> getAttachedNode(1));
	}
	void moveSouth() {
		pathTaken += currentNode -> getNodeName()  + " ";
		setCurrentNode(currentNode -> getAttachedNode(2));
	}
	void moveWest() {
		pathTaken += currentNode -> getNodeName()  + " ";
		setCurrentNode(currentNode -> getAttachedNode(3));
	}
	
	/** accepts a string to know what direction the player should move in */
	void moveDirectionByString(string direction) {
		if (direction[0] == 'N') 
			moveNorth ();
		if (direction[0] == 'E') 
			moveEast();
		if (direction[0] == 'S') 
			moveSouth();
		if (direction[0] == 'W') 
			moveWest();
	}
	
	/** Checks the validity of the direction (ensures a node exists there) */
	bool isDirectionValid(int direction) {
		if (currentNode -> getAttachedNode(direction) != NULL) {
			return true;
		}
		return false;
	}
	
	/** Returns a string of user's currentNode's attached directions*/
	string getStringOfValidDirections() {
		string out = "";
		if (currentNode -> getAttachedNode(0) != NULL) 
			out += "N ";
		if (currentNode -> getAttachedNode(1) != NULL) 
			out += "E ";
		if (currentNode -> getAttachedNode(2) != NULL)
			out += "S ";
		if (currentNode -> getAttachedNode(3) != NULL)
			out += "W ";
		return out;
	}
	
	/** Roll a six-sided die and return its value */
	int diceRoll() {
		lastDiceRoll = rand() % 7;
		return lastDiceRoll;
	}
	
	/** Returns the value of the last die roll */
	int getLastDiceRoll() {
		return lastDiceRoll;
	}
	
	/** Returns the Node this player is on. */
	Node* getCurrentNode() {
		return currentNode;
	}

	/** Sets this player's current node */
	void setCurrentNode(Node* node) {
		currentNode = node;
	}
	
	/** Returns the path that this player has taken */
	string getPathTaken() {
		return pathTaken;
	}
};

/** System class
 */
class System {
private:
	int currentUserIndex;
	string boardFileName;
	std::ifstream boardFile;
	Board* board;
	User* currentUser;
	vector<User*> users;
public:
	System() {};
	~System() {};
	
	/** Initializes the system's values */
	bool initialize () {
		if (doesBoardFileExist(boardFileName)) {
			board = new Board();
			board -> setFileName(boardFileName);
			board -> initialize();
			currentUserIndex = 0;
			return true;
		}
		return false;
	}
	
	/** Checks to ensure that the file specified exists */
	bool doesBoardFileExist(string filename) {
		ifstream inStream(filename.c_str());
		return inStream;
	}
	
	/** Set the filename */
	void setBoardFileName(string boardFileNameIn) {
		boardFileName = boardFileNameIn;
	}
	
	/** Returns the system's active user*/
	User* getCurrentUser() {
		return currentUser;
	}
	
	/** Sets the system's active user*/
	void setCurrentUser(User* newCurrentUser) {
		currentUser = newCurrentUser;
	}
	void setNextUserAsCurrent() {
		if ((int)users.size() - 1 == currentUserIndex) {
			currentUserIndex = 0;
		} else {
			currentUserIndex += 1;
		}
		currentUser = users.at(currentUserIndex);
	}
	
	/** Sets the current user to the first user created */
	void initializeCurrentUser() {
		currentUser = users.at(0);
		currentUserIndex = 0;
	}
	
	/** Returns the board object */
	Board* getBoard() {
		return board;
	}
	
	/** Creates a new user and adds it to the list. */
	void createUser(string usernameIn) {
		User* user = new User(usernameIn);
		user -> setCurrentNode(board -> getStartNode());
		users.push_back(user);
	}
	
	/** Checks to see if the direction specified is valid. */
	bool isStringDirectionValid(string direction) {
		string dir = direction;
		int dirIndex = -1;
				
		dir[0] = toupper(dir[0]);
		
		if (dir[0] == 'N')
			dirIndex = 0;
		if (dir[0] == 'E')
			dirIndex = 1;
		if (dir[0] == 'S')
			dirIndex = 2;
		if (dir[0] == 'W')
			dirIndex = 3;
			
		if (currentUser -> isDirectionValid(dirIndex)) {
			return true;
		}
		return false;
	}
};

/** Menu class (all menu methods, part user sees)
 */
class Menu {
private:
	System sys;
public:
	Menu() {}
	~Menu() {}
	void welcomeBanner() {
		cout << "=====================================================" << endl;
		cout << "|   Welcome to the Snakes and Ladders Maze Game     |" << endl;
		cout << "=====================================================" << endl;
		cout << endl;
		askForSetupInformation();
	}
	void askForSetupInformation() {
		int numberOfPlayers;
		
		cout << "Name of the file you want to build the maze from (filename cannot have spaces): ";
		string boardFileIn;
		cin >> boardFileIn;
		sys.setBoardFileName(boardFileIn);
		
		cout << "Number of players: ";
		cin >> numberOfPlayers;
			
		if (sys.initialize()) {
			for (int i = 1; i <= numberOfPlayers; i++) {
				string thisPlayersName;
				cout << "Name of Player #" << i << ": ";
				cin >> thisPlayersName;
				sys.createUser(thisPlayersName);
				if (i == 1) {
					sys.initializeCurrentUser();
				}
			}
		} else {
			cout << "Sorry, that file doesn't exist. Please try again." << endl;
			askForSetupInformation();
		}
		cout << endl;
		
		showCurrentUserInformation();
	}
	void showCurrentUserInformation() {
		string thisPlayersName = sys.getCurrentUser() -> getName();
		
		cout << "===============================" << endl;
		cout << "|        " << thisPlayersName << "'s turn!         |" << endl;
		cout << "===============================" << endl;
		cout << endl;
		
		cout << thisPlayersName << "'s turn to roll the dice!" << endl;
		cout << thisPlayersName << " rolled a: " << sys.getCurrentUser() -> diceRoll() << endl;
		cout << thisPlayersName << " can make that many moves.";
		cout << endl;
		
		showInputMenu();
	}
	void showInputMenu() {
		for (int i = 0; i < sys.getCurrentUser() -> getLastDiceRoll(); i++) {
			sys.getCurrentUser() -> getName();
			string thisPlayersName = sys.getCurrentUser() -> getName();
			string choice = "";
			if (sys.getCurrentUser() -> getCurrentNode() -> getSnakeOrLadderNode() != NULL) {
				sys.getCurrentUser() -> setCurrentNode(sys.getCurrentUser() -> getCurrentNode() -> getSnakeOrLadderNode());
				cout << "Oh noes! That node had a snake/ladder node." << endl;
			}
			cout << thisPlayersName << " is currently at Node ";
			cout << sys.getCurrentUser() -> getCurrentNode() -> getNodeName() << ". ";
			cout << thisPlayersName << " can move in the following directions: ";
			cout << sys.getCurrentUser() -> getStringOfValidDirections() << endl;
			cout << "What is your choice?" << endl;
			promptForInput();
		}
		sys.setNextUserAsCurrent();
		showCurrentUserInformation();
	}
	void promptForInput() {
		string choice;
		cin >> choice;
		if (sys.isStringDirectionValid(choice)) {
			sys.getCurrentUser() -> moveDirectionByString(choice);
			if (sys.getCurrentUser() -> getCurrentNode() -> getNodeName() == sys.getBoard() -> getEndNode() -> getNodeName()) {
				showWinScreen();
			}
		} else {
			cout << "Sorry, you cannot go that way." << endl;
			promptForInput();
		}
	}
	void showWinScreen() {
		cout << "===============================" << endl;
		cout << "|    " << sys.getCurrentUser() -> getName() << " wins!    |" << endl;
		cout << "===============================" << endl;
		cout << "Path this player took: " << sys.getCurrentUser() -> getPathTaken() << endl;
		cout << endl;
		quit();
	}
	
	void quit() {
		exit(0);
	}
};

/** main method, sets program in motion
 */
int main() {
	System sys;
	Menu menu;
	menu.welcomeBanner();
	return 0;
}
