//
// Cribbage: Card and Board Game
// Nathan Haug
// CS260 Dr. Erhart
//

import java.util.Random;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Cribbage {
	private int numPlayers;
	private int playerTurn;
	private Deck theDeck;
	private Hand theCrib;
	private Player[] players;
	private CribbagePanel gameWindow;
	private boolean humanMode;
	
	// These two variables are passed back and forth between waitForSelection() and finishedSelection()
	private GUIWaiter servant;
	private int userSelection;
	
	// Constructor
	public Cribbage(int numOfPlayers) {
		numPlayers = numOfPlayers;
		theDeck = new Deck();
		players = new Player[numPlayers];
		theCrib = new Hand();
		humanMode = false;
		
		// Initialize the players
		for (int i = 0; i<numPlayers; i++) {
			players[i] = new AIPlayer("Player "+i,i);
		}
		
		// Setup our GUI
		gameWindow = new CribbagePanel(this,false); // gameManager, isHumanMode
	}
	
	public Cribbage(int numOfPlayers, String humanName) {
		numPlayers = numOfPlayers;
		theDeck = new Deck();
		players = new Player[numPlayers];
		theCrib = new Hand();
		humanMode = true;
		
		// Initialize the players
		players[0] = new HumanPlayer(humanName,0);
		for (int i = 1; i<numPlayers; i++) {
			players[i] = new AIPlayer("Player "+i,i);
		}
		
		// Setup our GUI
		gameWindow = new CribbagePanel(this,true); // gameManager, isHumanMode
	}
	
	// Accessors
	public Player[] getPlayers() {
		return players;
	}
	
	public Hand getCrib() {
		return theCrib;
	}
	
	public int getNumPlayers() {
		return numPlayers;
	}
	
	public boolean isHumanMode() {
		return humanMode;
	}
	
	public CribbagePanel getGameWindow() {
		return gameWindow;
	}
	
	// Mutators
	
	public void deal() {
		// Pre:  aDeck is created, thePlayers are created, theCrib is created
		// Post: aDeck has been distributed between thePlayers, one card goes to the Crib if a 3-player game
		
		// Note that the player in position 0 is always the dealer, this rotates at the end of rounds
		// TUI
		System.out.println(players[0].getName()+" shuffles and deals the deck.");
		// GUI
		gameWindow.setPlayText(players[0].getName()+" shuffles and deals the deck.");
		gameWindow.validate();
		gameWindow.repaint();

		
		theDeck.shuffle();
		
		int roundsToDeal;
		if (numPlayers == 2)
			roundsToDeal = 6;
		else // (numPlayers == 3)
			roundsToDeal = 5;
		
		Card currentCard;
		for (int round=0; round<roundsToDeal; round++) {
			for (int plr=0; plr<numPlayers; plr++) {
				currentCard = theDeck.getCard();
				players[plr].putCard(currentCard);
				// Add to out GUI
				gameWindow.getHand(players[plr].getPlayerID()).putCard(currentCard);
				gameWindow.validate();
				gameWindow.repaint();
				
			}
		}
		
		// Deal a card to theCrib, if necessary
		if (numPlayers == 3) {
			currentCard = theDeck.getCard();
			theCrib.putCard(currentCard);
		}
	}
	
	public void playersDiscard() {
		// Pre:  the players are created and have cards
		// Post: each player chooses two discards (if 2-player) or just one (if 3-player)
		int  discardNum;
		Card discardCard;
		if (numPlayers == 2)
			for (int i = 0; i<numPlayers; i++) {
				gameWindow.notify(players[i].getPlayerID(),"Please choose two discards");
				gameWindow.validate();
				gameWindow.repaint();
				discardNum = players[i].getDiscardCard(this);
				discardCard = players[i].getHand().getCard(discardNum);
				
				players[i].getHand().deleteCard(discardNum);
				gameWindow.deleteCard(players[i].getPlayerID(),discardNum);
				
				theCrib.putCard(discardCard);
				
				discardNum = players[i].getDiscardCard(this);
				discardCard = players[i].getHand().getCard(discardNum);
				
				players[i].getHand().deleteCard(discardNum);
				gameWindow.deleteCard(players[i].getPlayerID(),discardNum);
				
				theCrib.putCard(discardCard);
				
				String message = players[i].getName() + " discards two cards";
				gameWindow.notify(players[i].getPlayerID(),message);
				
				gameWindow.validate();
				gameWindow.repaint();
			}
		else //(numPlayers == 3)
			for (int i = 0; i<numPlayers; i++) {
				gameWindow.notify(players[i].getPlayerID(),"Please choose a discard");
				gameWindow.validate();
				gameWindow.repaint();
				discardNum = players[i].getDiscardCard(this);
				discardCard = players[i].getHand().getCard(discardNum);
				
				players[i].getHand().deleteCard(discardNum);
				gameWindow.deleteCard(players[i].getPlayerID(),discardNum);
				
				theCrib.putCard(discardCard);
				
				String message = players[i].getName() + " discards";
				gameWindow.notify(players[i].getPlayerID(),message);	
							
				gameWindow.validate();
				gameWindow.repaint();
			}
	}
	
	public void playersPeg() throws GameOverException{
		// Pre:  players[] are created, it is thier turn to peg.
		// Post: Each player has pegged until they have expended all their cards in thier peggingHand

		CurrentPlay cardsInPlay;
		int playersWithCards = numPlayers;
		int numPlayableHands;
		boolean playableHands[] = new boolean[numPlayers];
		boolean successfulPlay;
		int currentPlayer = 1; // Player 1 in the array is the person to the 'left' of the dealer (player 0)
		int lastPlayer = 1;
		
		Card pegCard;
		int pegCardPos;
		int pointsScored;
		
		
		System.out.println("Begin pegging:");
		
		while (playersWithCards > 0) {
			// Make a new playing field
			cardsInPlay = new CurrentPlay();
			// The number of playable hands is equal to the number of players
			numPlayableHands = numPlayers;
			
			// Everyone should start with a playable hand
			for (int i = 0; i<numPlayers; i++) 
				playableHands[i] = true;
			
			while (numPlayableHands != 0) { // Have the players play to 31 (or as close as possible)

				if ( playableHands[currentPlayer] ) {
					gameWindow.notify(players[currentPlayer].getPlayerID(),"Please choose a card to play");
					gameWindow.validate();
					gameWindow.repaint();
					pegCardPos = players[currentPlayer].getPeggingCard(cardsInPlay,this);
					if (pegCardPos != -1) {
						// If the player chose a card to play, go ahead and play it
						pegCard = players[currentPlayer].getPeggingHand().getCard(pegCardPos);
						players[currentPlayer].getPeggingHand().deleteCard(pegCardPos);
						pointsScored = cardsInPlay.playCard(pegCard);
						
						players[currentPlayer].addScore(pointsScored);
						
						// Prepare our output
						String outputMessage = players[currentPlayer].getName()+" played the "+pegCard.display();
						if (pointsScored > 0)
							outputMessage += " and pegged "+pointsScored+ " points";
						
						// Update the GUI
						gameWindow.deleteCard(players[currentPlayer].getPlayerID(),pegCardPos);
						gameWindow.notify(players[currentPlayer].getPlayerID(),outputMessage);
						gameWindow.addScore(players[currentPlayer].getPlayerID(),pointsScored);
						gameWindow.playCard(pegCard);
						gameWindow.validate();
						gameWindow.repaint();
						
						// Update the TUI
						System.out.println(outputMessage+" (Total: "+cardsInPlay.getTotal()+")");
						
						// Check for win
						checkForWin();
						
						successfulPlay = true;
						}
					else {
						String outputMessage = players[currentPlayer].getName()+" cannot play";
						
						gameWindow.notify(players[currentPlayer].getPlayerID(),outputMessage);
						gameWindow.validate();
						gameWindow.repaint();
						successfulPlay = false;
					}
					playableHands[currentPlayer] = successfulPlay;
					if (successfulPlay)
						lastPlayer = currentPlayer;
				}
				
				// Increment currentPlayer
				currentPlayer++;
				if (currentPlayer == numPlayers)
					currentPlayer = 0;
					
				// Check if we hit 31
				if (cardsInPlay.getTotal() == 31)
					break;
				
				// Check to see if anyone has a playable card
				numPlayableHands = numPlayers;
				for (int i = 0; i < numPlayers; i++) {
					if (!playableHands[i])
						numPlayableHands--;
				}
			}
			
			// If we're not at exactly 31, award the last player 1 point
			if (cardsInPlay.getTotal() != 31) {
				players[lastPlayer].addScore(1);
				String outputMessage = players[lastPlayer].getName()+" pegs 1 for last.";
				System.out.println(outputMessage);
				gameWindow.notify(players[lastPlayer].getPlayerID(),outputMessage);
				gameWindow.addScore(players[lastPlayer].getPlayerID(),1);
				gameWindow.validate();
				gameWindow.repaint();
				// Check for Win
				checkForWin();
			}
			
			// TUI Display
			System.out.println("No one can play, the table is cleared\n");
			
			// GUI Display
			gameWindow.setPlayText("No one can play, click to continue.");
			gameWindow.validate();
			gameWindow.repaint();
			if (humanMode)
				gameWindow.clickWait();
			gameWindow.clearPlayArea();
			gameWindow.validate();
			gameWindow.repaint();
			
			// Check to see if anyone has cards left
			playersWithCards = numPlayers;
			for (int i = 0; i<numPlayers; i++) {
				if (players[i].getPeggingHand().numCards() == 0)
					playersWithCards--;
			}
		}
	}

	public void theShow(Card theCutCard)  throws GameOverException {
		// Pre: All players are created and have hands ready (with the cut card)
		// Post: Each player has scored thier points
		int previousScore;
		int currentScore;
		String message;
		
		for (int i = 1; i<=numPlayers; i++) {
			if (i == numPlayers)
				i = 0;
			previousScore = players[i].getScore();
			players[i].getHand().putCard(theCutCard);
			players[i].tallyHand();
			currentScore = players[i].getScore();
			message = players[i].getName()+" pegs "+(currentScore-previousScore)+" points for the show";
			
			// Update GUI
			gameWindow.notify(players[i].getPlayerID(),message);
			gameWindow.addScore(players[i].getPlayerID(),(currentScore-previousScore));
			gameWindow.show(players[i].getPlayerID(),players[i].getHand());
			gameWindow.validate();
			gameWindow.repaint();
			
			// Update TUI
			message +=  ". Totalling "+currentScore+" points.";
			System.out.println(message);
			
			// Check for win
			checkForWin();
			
			if (i == 0)
				i = numPlayers;
		}
		// Wait for the user to continue
		gameWindow.setPlayText("The Show. Click to continue.");
		if (humanMode)
			gameWindow.clickWait();
	}
	
	public int tallyCrib (Card theCutCard) {
		// Pre:  theCrib has five cards
		// Post: call tally(), because the crib is just a show for theCrib
		theCrib.putCard(theCutCard);
		return theCrib.tally();
	}
	
	public void playOneHand () throws GameOverException{
		Card cutCard;
		int dealerPoints;
		
		// Deal the deck
		deal();
		// All players choose their discards
		playersDiscard();
		// Get the cut card
		cutCard = theDeck.cut();
		gameWindow.setCutCard(cutCard);
		
		// Check for nubs
		if (cutCard.getSuit() == Card.JACK){
			players[0].addScore(1);
			gameWindow.notify(players[0].getPlayerID(), players[0].getName() + "pegs one for nubs");
			gameWindow.addScore(players[0].getPlayerID(),1);
			gameWindow.validate();
			gameWindow.repaint();
		}
		
		// All players enter pegging play
		playersPeg();
		
		// Players all tally their hands, plus the cut card
		theShow(cutCard);
		
		// The dealer tallies the crib to his score		
		dealerPoints = tallyCrib(cutCard);
		players[0].addScore(dealerPoints);
		System.out.println("The crib contains:");
		theCrib.display();
		String message = players[0].getName()+" pegs "+dealerPoints+" points from the crib";
		
		// Display the crib show
		gameWindow.cleanUp();
		gameWindow.setPlayText("The round is over, click to continue.");
		gameWindow.show(players[0].getPlayerID(),theCrib);
		gameWindow.notify(players[0].getPlayerID(),message);
		gameWindow.addScore(players[0].getPlayerID(),dealerPoints);
		gameWindow.validate();
		gameWindow.repaint();
		
		System.out.println(message+"Now totaling "+players[0].getScore()+".");
		
		// Check for win
		checkForWin();
		
		if (humanMode)
			gameWindow.clickWait();
		
		// Rotate the dealer position
		Player tempPlayer = players[0];
		for (int i = 0; i < numPlayers-1; i++) {
			players[i] = players[i+1];
		}
		players[numPlayers-1] = tempPlayer;
		
		// Clean up hands, return everything to the deck
		for (int i = 0; i < numPlayers; i++) {
			Hand currentHand = players[i].getHand();
			int numInHand = currentHand.numCards();
			for (int j = 0; j < numInHand; j++)
				currentHand.deleteCard(0);
		}
		// Clean up the crib
		int numInCrib = theCrib.numCards();
		for (int i = 0; i < numInCrib; i++)
			theCrib.deleteCard(0);
			
		// Clean up our GUI
		gameWindow.cleanUp();
		gameWindow.validate();
		gameWindow.repaint();
	}
	
	public void playGame () throws GameOverException {
		// Returns if the player wants to play again
		// Play the Game
		for (int i = 0; i < 30; i++) {
			playOneHand();
			System.out.println("Hand "+(i+1)+" Finished\n");
		}
	}
	
	public void checkForWin () throws GameOverException {
		// Pre: The players all exsist
		// Post: If a player exceeds 120, GameOverException is thrown
		
		int winnerPos = -1;
		for (int i = 0; i < numPlayers; i++) {
			if (players[i].getScore() > 120)
				throw new GameOverException(i);
		}
	}
	
	public int waitForSelection() {
		// This function halts the thread, waiting for input
		// Returns the integer position of the card in the hand
		servant = new GUIWaiter();
		servant.start();
		try {servant.join();}
		catch (InterruptedException e) {}
		
		return userSelection;
	}
	
	public void finishedSelection(int selectionNum) {
		userSelection = selectionNum;
		servant.setDone();
	}
	
}
