package connectfour2013;

import processing.core.PApplet;
import processing.core.PFont; // text drawing
import java.util.*; // ArrayList

public class ConnectFour2013 extends PApplet {

	GameBoard theBoard;  // main game board
	GameBoard winBoard;  // to show when a win occurred since theBoard is empty
	GameBoard drawBoard; // either points to theBoard or winBoard
	int spotWidth;  // board drawing info, the width of a "square"
	int spotHeight; // board drawing info, the height of a "square"
	final int EMPTYCOLOR = 191; // what shade of gray to use for empty "square"
	
	Player playerL;    // left-side player
	Player playerR;    // right-side player
	Player currPlayer; // points to the player whose turn it is

	int currRound = 1;
	final int NUM_GAMES = 3; // games per round, should be odd
	boolean byesWaiting = true;
	
	ArrayList<Player> players; // current-round players awaiting games
	ArrayList<Player> winners; // current-round players who have played and won
	
	int delayCount = 0; // for robotic players, delay move time tracker
	int delayX = 0;     // more recent mouse x position to indicate delay amt
	
	int lastX = -1; // most recent *unprocessed* mouse click x-coord
	
	PFont theFont;
	public int fontSize;
	
	String infoMessage = null;
	String attnMessage = null;
	
	boolean paused = true;
	int pauseX;
	int pauseY;

	public void setup() {
		setupGameElements();
		setupDrawingInfo();
		setupTournament();
		setupNextMatch();

		attnMessage = "ROUND 1\n" + players.size() + " players";
	}
	
	public void setupTournament() {
		// This is where you put whatever players you want in 
		// the game.  Add as many as you like!
		
		players.add(new GUIPlayer());
		players.add(new SequentialPlayer());

		// But don't change this part.
		if (isPowerOf2(players.size())) {
			byesWaiting = false;
		}
	}
	
	public void setupGameElements() {
		theBoard = new GameBoard(6, 7);
		winBoard = null;
		drawBoard = theBoard;
		players = new ArrayList<Player>();
		winners = new ArrayList<Player>();
	}
	
	public void setupDrawingInfo() {
		// window size first
		size(603, 603); // best if respectively divisible by numRows+3 and numCols+2
		
		// then board "squares" based on window size
		spotWidth = width / (theBoard.getColAmt() + 2); // leave a border
		spotHeight = height / (theBoard.getRowAmt() + 3); // leave a border + info space

		// then font size based on "square" size
		fontSize = Math.max(spotHeight / 2, 10); // no smaller than 10
		if (fontSize % 2 == 1) fontSize--; // make sure it's even
		theFont = createFont("Arial", fontSize, true);
		textFont(theFont);
		
		// then pause button
		pauseX = width - spotWidth;
		pauseY = height - 3*spotHeight;

		// other drawing stuff that persists in program
		ellipseMode(CORNER);
		smooth();
	}
	
	public boolean isPowerOf2(int n) {
		if (n == 0) return false;
		if (n == 1) return true;
		if (n % 2 == 1) return false;
		return isPowerOf2(n / 2);
	}

	public void setupNextMatch() {
		if (byesWaiting && isPowerOf2(players.size() + winners.size())) {
			handleBye();
		}
		else if (players.size() >= 2) {
			setupTwoPlayers();
		}
		else if (players.size() == 1 && winners.size() == 0) {
			handleTournamentEnd();
		}

		if (players.size() == 0) {
			handleRoundEnd();
		}
	}
	
	public void setupTwoPlayers() {
		Random r = new Random();

		int L = r.nextInt(players.size());
		playerL = players.get(L);
		playerL.resetWins();
		playerL.setColor(GameBoard.BLACK);

		int R = r.nextInt(players.size());
		while (R == L) {
			R = r.nextInt(players.size());
		}
		playerR = players.get(R);
		playerR.resetWins();
		playerR.setColor(GameBoard.RED);

		// debug
		println("ROUND " + currRound + "\t" + playerL.getName() + "\tVS.\t" + playerR.getName() );
		
		setupGame();
	}
	
	public void setupGame() {
		theBoard.reset();

		if (currPlayer == null || currPlayer == playerR) {
			currPlayer = playerL;
		}
		else {
			currPlayer = playerR;
		}
	}
	
	public void handleBye() {		
		byesWaiting = false;
		
		while (players.size() > 0) {
			Player p = players.get(0);
			players.remove(p);
			winners.add(p);
		}
		
		String s = "Remaining players get byes.";
		if (attnMessage == null) {
			attnMessage = s;
		}
		else {
			attnMessage = attnMessage + "\n" + s;
		}

		// debug
		println(attnMessage);
	}
	
	public void handleRoundEnd() {
		String s = "ROUND " + currRound + " is over.";
		
		if (attnMessage == null) {
			attnMessage = s;
		}
		else {
			attnMessage = attnMessage + "\n" + s;
		}
		
		currRound++;

		players = winners;
		winners = new ArrayList<Player>();
		setupNextMatch();
	}
	
	public void handleTournamentEnd() {
		Player p = players.get(0);
		currPlayer = null;
		
		infoMessage = p.getName() + " IS THE CHAMP!";
		
		// debug
		println("ROUND " + currRound + "\t" + p.getName() + "\tWINS THE TOURNAMENT");
	}
	
	public void draw() {
		background(EMPTYCOLOR);

		checkForMove();
		drawBoard();
		drawGameInfo();
		drawPauseButton();
		drawInfoMessage();
		drawAttnMessage();
	}
	
	public void checkForMove() {
		if (currPlayer == null || drawBoard == winBoard || paused) return;

		if (currPlayer.toString().contains("GUIPlayer")) {
			delayCount = 0;
		}
		else {
			double pct = (double)delayX / width;
			int delayAmt = (int)(pct * 300) + 15;
			delayCount = (delayCount + 1) % (delayAmt);
		}
		
		if (delayCount == 0) {
			infoMessage = "ROUND " + currRound;
			makeMove();
		}
	}
	
	public void makeMove() {
		if (currPlayer.toString().contains("GUIPlayer")) {
			boolean b = convertCoords();
			if (!b) return;
		}

		try {
			int dropCol = currPlayer.ai(theBoard.getCopy());

			if (theBoard.play(dropCol, currPlayer.getColor())) {
				if (theBoard.hasWinner()) {
					handleWin();
				}
				// TODO: analyze board for a tie? what if infinitely ties?
				else {
					switchPlayers();
				}
			}
			else {
				handleError(dropCol);
			}
		}
		catch (Exception e) {
			handleError(-1);
		}
	}
	
	public void handleError(int i) {
		String err = "\n(ai threw exception)\n";
		if (i > -1) {
			err = "\n(dropped in col " + i + ")\n";
		}
		
		attnMessage = currPlayer.getName() + " made an error" + err + "and loses the game.";
		switchPlayers();
		handleWin();
	}
	
	public void switchPlayers() {
		if (currPlayer == playerL) {
			currPlayer = playerR;
		}
		else if (currPlayer == playerR) {
			currPlayer = playerL;
		}
	}
	
	public void handleWin() {
		currPlayer.recordWin();
		infoMessage = currPlayer.getName() + " WINS A GAME!";
		winBoard = theBoard.getCopy();
		drawBoard = winBoard;
		paused = true;

		// debug
		println("  " + currPlayer.getName() + " wins a game and has " + currPlayer.getWins() + " wins.");

		if (currPlayer.getWins() == (NUM_GAMES / 2) + 1) {
			
			String s = currPlayer.getName() + " wins the match.";
			if (attnMessage == null) {
				attnMessage = s;
			}
			else {
				attnMessage = attnMessage + "\n" + s;
			}
			
			winners.add(currPlayer);
			players.remove(playerL);
			players.remove(playerR);
			setupNextMatch();
		}
		else {
			setupGame();
		}
	}
	
	public boolean convertCoords() {
		if (lastX == -1) return false;
		
		if (lastX < spotWidth || lastX > width - spotWidth) return false;
		
		GUIPlayer p = (GUIPlayer)currPlayer;
		p.setClickedCol((lastX/spotWidth)-1);

		lastX = -1;
		return true;
	}
	
	public void drawBoard() {
		int rows = drawBoard.getRowAmt();
		int cols = drawBoard.getColAmt();
		int[][] board = drawBoard.getBoard2D();
		int x = 0;
		int y = 0;
		for (int r = 0; r < rows; r++) {
			x = 0;
			y = y + spotHeight;
			for (int c = 0; c < cols; c++) {
				x = x + spotWidth;
				drawBoardSpot(x, y);
				drawChecker(x, y, board[r][c]);
			}
		}
	}
	
	public void drawBoardSpot(int x, int y) {
		strokeWeight(1);
		stroke(0);
		fill(255,255,0);
		rect(x, y, spotWidth, spotHeight);
	}
	
	public void drawChecker(int x, int y, int color) {
		noStroke();
		if (color == GameBoard.RED) {
			fill(255, 0, 0);
		}
		else if (color == GameBoard.BLACK) {
			fill(0);
		}
		else {
			fill(EMPTYCOLOR);
		}
		x = x + (spotWidth / 10);
		y = y + (spotHeight / 10);
		ellipse(x, y, 8 * spotWidth / 10, 8 * spotHeight / 10);
	}


	public void drawPauseButton() {
		int x = pauseX + spotWidth/5;
		int y = pauseY + spotHeight/5;
		int w = 3*spotWidth/5;
		int h = 3*spotHeight/5;
		noStroke();
		if (paused) { // draw play shape
			fill(0, 255, 0);
			triangle(x, y, x, y + h, x + w, y + h/2);
		}
		else { // draw pause shape
			w = w / 3;
			fill(0);
			rect(x, y, w, h);
			rect(x + 2*w, y, w, h);
		}
	}
	
 	public void drawGameInfo() {
		drawNames();
		drawLastChecker();
	}
	
	public void drawNames() {
		int c = EMPTYCOLOR + (255 - EMPTYCOLOR)/2;
		fill(c);
		stroke(c);
		strokeWeight(1);
		int infoWidth = theBoard.getColAmt() * spotWidth;
		int infoHeight = spotHeight-1;
		rect(spotWidth, 0, infoWidth, infoHeight);
		
		int playerHeight = 2*infoHeight/3;
		drawPlayerInfo(playerL, spotWidth+2, playerHeight, LEFT);
		drawPlayerInfo(playerR, spotWidth + infoWidth - 2, playerHeight, RIGHT);
	}
	
	public void drawPlayerInfo(Player p, int x, int y, int align) {
		if (p != null) {
			drawName(p, x, y, align);
			drawWins(p, align);
		}
	}
	
	public void drawName(Player p, int x, int y, int align) {
		if (p != null) {
			if (p.getColor() == GameBoard.BLACK) {
				fill(0);
			}
			else if (p.getColor() == GameBoard.RED) {
				fill(255, 0, 0);
			}
			else { // weird, no color
				fill(0, 255, 0);
			}
			
			String name = p.getName();
			if (p == currPlayer && drawBoard == theBoard) {
				if (align == LEFT) {
					name = name + " <";
				}
				else {
					name = "> " + name;
				}
			}
	
			textAlign(align);
			text(name, x, y);
		}
	}

	
	public void drawWins(Player p, int align) {
		int w = spotWidth/3;
		int h = spotWidth/3;
		int x = spotWidth/2;
		int y = spotHeight/2;
		if (align == RIGHT) {
			x = width - x;
		}
		
		stroke(0);
		strokeWeight(1);
		fill(127, 255, 127);
		ellipseMode(CENTER);
		for (int i = 0; i < p.getWins(); i++) {
			ellipse(x, y, w, h);
			y = y + h + 2;
		}
		ellipseMode(CORNER);
	}
	
	public void drawLastChecker() {
		// last checker
		int c = theBoard.prevColumn();
		if (c == -1) return;
		
		int halfSpot = spotWidth / 2;
		int x1 = (c+1) * spotWidth + halfSpot;
		int y1 = height - 2*spotHeight + 4;
		int x2 = x1 - halfSpot / 2;
		int y2 = height - spotHeight - halfSpot;
		int x3 = x1 + halfSpot / 2;
		int y3 = y2;
		if (theBoard.prevColor() == GameBoard.BLACK) {
			fill(0);
			stroke(255, 0, 0);
		}
		else {
			fill(255, 0, 0);
			stroke(0);
		}
		strokeWeight(2);
		triangle(x1, y1, x2, y2, x3, y3);
	}
	
	public void drawInfoMessage() {
		fill(EMPTYCOLOR + (255-EMPTYCOLOR)/2);
		stroke(EMPTYCOLOR + (255-EMPTYCOLOR)/2);
		strokeWeight(1);
		int y = height - spotHeight;
		rect(0, y, width-1, spotHeight-1);

		// game
		textAlign(CENTER);
		text("CONNECT 4 TOURNAMENT", width/2, y);

		// speed indicator
		fill(0,255,0);
		noStroke();
		ellipse(delayX, y, 5, 5);
		int pct = (int)(100 * (1 - (double)delayX / width));
		if (pct < 50) {
			textAlign(RIGHT);
		}
		else {
			textAlign(LEFT);
		}
		String msg = pct + "% speed";
		text(msg, delayX, y + spotHeight);

				
		if (infoMessage == null) return;

		y = y + 2*spotHeight/3;
		fill(0);
		textAlign(CENTER);
		text(infoMessage, width/2, y);
	}
	
	public void drawAttnMessage() {
		if (attnMessage == null) return;
		
		fill(0,0,128,192);
		stroke(0,255,0,192);
		strokeWeight(3);
		int x = spotWidth;
		int y = spotHeight;
		int w = width - 2*spotWidth;
		int h = height - 3*spotHeight;
		rect(x, y, w, h);
		
		x = width / 2;
		y = y + spotHeight;
		fill(0,255,0,192);
		textAlign(CENTER);
		text(attnMessage, x, y);
	}


	public void mouseClicked() {
		if (mouseX >= pauseX && mouseX <= pauseX + spotWidth) {
			if (mouseY >= pauseY && mouseY <= pauseY + spotHeight) {
				paused = !paused;
			}
		}
	    if (attnMessage != null) {
	    	attnMessage = null;
	    }
		if (winBoard != null) {
			winBoard = null;
			drawBoard = theBoard;
		}
		else {
			lastX = mouseX;
		}
	}

	
	public void mouseMoved() {
		if (mouseY > height - spotHeight) {
			delayX = mouseX;
		}
	}
}
