import edu.neu.ccs.*;
import edu.neu.ccs.gui.*;
import edu.neu.ccs.codec.*;
import edu.neu.ccs.console.*;
import edu.neu.ccs.filter.*;
import edu.neu.ccs.jpf.*;
import edu.neu.ccs.parser.*;
import edu.neu.ccs.pedagogy.*;
import edu.neu.ccs.quick.*;
import edu.neu.ccs.util.*;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.font.*;
import java.awt.image.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
import java.io.*;
import java.util.*;
import java.math.*;
import java.beans.*;
import java.lang.reflect.*;
import java.lang.Object.*;
import java.net.URL;
import java.util.regex.*;
import java.text.ParseException;

public class BlackJackMP extends DisplayPanel implements WindowConstants {

	boolean debug = true;

	//deck
	Card[] cards = getDeck();
	Card[] deck = shuffle();
	int place = 0;
	
	ShapePaintable infoBox;
	public Paintable hintPaintable;
	public TextPaintable win;
	public TextPaintable loose;
	public TextPaintable bust;
	public TextFieldView betAmt = new TextFieldView("15");
	private Paintable chip;

	public String picURL = "http://www.ccs.neu.edu/home/lbryant/BlackJack/";
	public String picList = "imagelist.txt";
	public ImagePaintableLite[] pics = WebImageTools.readImagesAsPaintableLite(
			picURL, picList);

	/** The frame for the animation. */
	private JPTFrame frame = null;
	public PaintableSequence sequence = new PaintableSequence();
	
	/** The main panel. */
	private TablePanel mainPanel = null;

	/** The common panel gap. */
	private int gap = 10;

	private Tile tile = null;

	private volatile boolean running = false;
	private Object animationLock = new Object();

	private Player dealer;
	private ArrayList<Player> players;
	
	int current = 0;

	
	//-----Constructos------
	
	public BlackJackMP(int players) {
		
		this.dealer = new Player(true,0,0);
		
		this.players = new ArrayList<Player>();
		if (players<=0) players=1;
		
		if (players>=1) {
			this.players.add(new Player(false,1,50));
		}
		if (players>=2) {
			this.players.add(new Player(false,2,50));
		}
		if (players>=3) {
			this.players.add(new Player(false,1,50));
		}
		if (players>=4) {
			this.players.add(new Player(false,1,50));
		}
		
		for (int i=1; i<deck.length; i++) {
			System.out.println(deck[i].id);
		}
		
		sequence.addPaintable(this.players.get(current).balanceView);
		this.players.get(current).balanceView.moveCenterTo(400,225);
		
		createMainPanel();
		createTile();
		createChip();
		showBalance(this.players.get(0));
		stopAnimation();

		populateMainPanel(null);
		createFrame();
	}
	
	//-----Actions-----
	
	public SimpleAction bet = new SimpleAction("Bet") {
		public void perform() {
		players.get(current).bet(sequence, betAmt.demandInt());
		players.get(current).balance = players.get(current).balance-
			betAmt.demandInt();
		showBalance(players.get(current));
		}
	};
	
	public SimpleAction doubleAction = new SimpleAction("Double") {
		public void perform() {
		players.get(current).bet(sequence, betAmt.demandInt()*2);
		players.get(current).balance = players.get(current).balance-
			betAmt.demandInt();
		showBalance(players.get(current));
		betAmt.setText(Integer.toString(betAmt.demandInt()*2));
		}
	};
	
	public SimpleAction split = new SimpleAction("Split") {
		public void perform() {
		players.get(current).hand.get(1).getPaint(
				players.get(current).hand.get(1).id).moveCornerTo(
				((int)players.get(current).hand.get(1).
				getPaint(players.get(current).hand.get(1).id).getCorner().x),
				((int)players.get(current).hand.get(1).
				getPaint(players.get(current).hand.get(1).id).getCorner().y-105));
		}
	};

	
	/** The action to run the animation in a separate thread. */
	private ThreadedAction dealAction = new ThreadedAction(new SimpleAction(
			"Deal") {
		public void perform() {

			standAction.setEnabled(true);
			bet.setEnabled(false);
			doubleAction.setEnabled(true);
			
			runAnimation("p",players.get(0));
			place+=1;
			runAnimation("p",players.get(0));
			place+=1;
			runAnimation("d",dealer);
			place+=1;
			
			if (!(players.get(current).hand.get(0) == null) &&
				!(players.get(current).hand.get(1) == null)) {
			if (players.get(current).hand.get(0).value() ==
				players.get(current).hand.get(1).value())
					split.setEnabled(true);
			}
			
			System.out.println(getHint(players.get(current),dealer.hand.get(0)));
			hint();
			
			stopAnimation();
		}
	});
	
	//Show the correct action according to perfect strategy
	public void hint() {
		this.hintPaintable = new TextPaintable(getHint(players.get(current), dealer.hand.get(0)),
				new Font("Arial",3,18),Colors.gold);
		//refreshComponent();
		sequence.addPaintable(this.hintPaintable);
		this.hintPaintable.moveCenterTo(280,225);
	}
	
	//Shows the player's balance
	public void showBalance(Player player) {
		sequence.removePaintable(player.balanceView);
		player.updateBalance(player.balance);		
		sequence.addPaintable(player.balanceView);
		player.balanceView.moveCenterTo(400,225);
	}

	//Hits
	private ThreadedAction hitAction = new ThreadedAction(new SimpleAction(
			"Hit") {
		public void perform() {
			
			dealAction.setEnabled(false);
			hitAction.setEnabled(true);
			doubleAction.setEnabled(false);
			runAnimation("p",players.get(0));
			place+=1;
		}
	});

	//Stands
	private ThreadedAction standAction = new ThreadedAction(new SimpleAction(
			"Stand") {
		public void perform() {
			players.get(0).stand();
			runAnimation("d",dealer);
			decideWinner();
		}
	});

	/** The action to stop the animation. */
	private SimpleAction stopAction = new SimpleAction("Stop Animation") {
		public void perform() {
			stopAnimation();
		}
	};

	//Closes window
	private SimpleAction closingAction = new SimpleAction(
			"Window Closing Action") {
		public void perform() {
			closingAction();
		}
	};
	

	

	//Creates table for main panel
	private void createMainPanel() {
		int rows = 2;
		int cols = 1;

		mainPanel = new TablePanel(rows, cols, gap, gap, CENTER);
	}


	//Creates tile for rendering
	private void createTile() {

		Rectangle2D bounds = new XRect(0, 0, 800, 600);
		sequence.setDefaultBounds2D(bounds);

		sequence.addPaintable(pics[0]);

		tile = new Tile(sequence, Colors.darkgreen);
	}

	//Populates main panel
	private void populateMainPanel(Paintable balance) {
		// populate the main panel
		mainPanel.addObject(tile, 0, 0);
		
		Object[] actions = { dealAction, hitAction, standAction , betAmt, bet, doubleAction, split};
		HTable buttons = new HTable(actions, gap, gap, CENTER);
		mainPanel.addObject(buttons, 1, 0);
		
		hitAction.setEnabled(false);
		standAction.setEnabled(false);
		doubleAction.setEnabled(false);
		//doubleAction.setEnabled(false);

		XRect info = new XRect(120,60);
				
		int thick = 2;
		Color fill = new Color(20,20,20);
        Color draw = Colors.gold;
        BasicStroke stroke = new BasicStroke(thick);
        
        infoBox = new ShapePaintable
            (info, PaintMode.FILL_DRAW, fill, draw, stroke);
		
		// add the main panel to this panel object
		add(mainPanel);
		sequence.addPaintable(infoBox);
		infoBox.moveCenterTo(374,258);
		
		sequence.addPaintable(players.get(current).balanceView);
	}
	
	private void createChip() {
		XCircle circle = new XCircle(20);

	    Color fill = Colors.gold;
	    Color draw = Colors.red;
	    BasicStroke stroke = new BasicStroke(4);
	        
	    chip = new ShapePaintable
	       (circle, PaintMode.FILL_DRAW, fill, draw, stroke);
	}


	//Decides who wins after players and dealer have gone
	private void decideWinner() {
		sequence.removePaintable(players.get(current).balanceView);
		
		for (int i=0; i<players.size(); i++) {
			
			if (! (players.get(i).busted || dealer.busted)) {
			//If won
			if (players.get(i).total >= dealer.total) {
				
				players.get(current).updateBalance(
						players.get(current).balance+(betAmt.demandInt()*2));
				players.get(i).win(sequence);
			}
			
			//If lost
			else {
				players.get(i).loose(sequence,chip);
				}
			}
			else if (players.get(i).busted) players.get(i).loose(sequence,chip);
			else if (dealer.busted) players.get(i).win(sequence);
		}
		
		showBalance(players.get(current));
		
		hitAction.setEnabled(false);
		standAction.setEnabled(false);
		bet.setEnabled(false);
		dealAction.setEnabled(false);
		
		JPTUtilities.pauseThread(1000);
		refreshGame();
	}

	/**
	 * The animation loop method that will
	 * execute in a separate thread.
	 */
	private void runAnimation(String which, Player player) {
		// set state for run
		setRunState();

		// perform animation
		while (true) {
			synchronized (animationLock) {
				if (!running)
					return;
				
				if (which.equals("p")) {
						player.animate(sequence,deck[place]);
						stopAnimation();
						player.hit(deck[place]);
						player.total = BJValue(player);
					
						if (player.total>21) {
							players.get(current).busted = true;
							decideWinner();
					 	}
					}
				
				 if (which.equals("d")) {
					dealer.animate(sequence,deck[place]);
					stopAnimation();
					dealer.hit(deck[place]);
					dealer.total = BJValue(dealer);
					
						if (players.get(current).stood && dealer.total<17) {
							place+=1;
							runAnimation("d",dealer);
						}
					
						if (dealer.total>21) {
							dealer.busted = true;
							decideWinner();
						}
					}
				}
				JPTUtilities.pauseThread(50);
			}
		}

	/**
	 * The method to stop the animation loop.
	 */
	public void stopAnimation() {
		// set state for stop
		setStopState();

		// perform any desired cleanup activities
	}

	/**
	 * <p>Set the enable/disable state of the actions
	 * for the Run state.</p>
	 * 
	 * <p>Set boolean running to true.</p>
	 */
	private void setRunState() {
		synchronized (animationLock) {
			hitAction.setEnabled(false);
			stopAction.setEnabled(true);

			running = true;
		}
	}

	/**
	 * <p>Set boolean running to false.</p>
	 * 
	 * <p>Set the enable/disable state of the actions
	 * for the Stop state.</p>
	 */
	private void setStopState() {
		synchronized (animationLock) {
			running = false;

			stopAction.setEnabled(false);
			hitAction.setEnabled(true);
		}
	}

	/**
	 * <p>The window closing method.</p>
	 *
	 * <p>This method is required to properly terminate
	 * the animation thread if the window is closed
	 * while the animation is running.</p>
	 */
	private void closingAction() {
		synchronized (animationLock) {
			running = false;

			if (frame != null)
				frame.dispose();
		}
	}

	/**
	 * The method to create the application frame and
	 * install the window closing behavior.
	 */
	private void createFrame() {

	}

	/** The method to set the window closing action. */
	private void setWindowClosingAction() {
		// remove default closing operation
		frame.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);

		// set special closing operation
		WindowActionAdapter adapter = new WindowActionAdapter(frame);
		adapter.addWindowClosingAction(closingAction);
	}

	/**
	 * The main program to launch the animation.
	 * @param args ignored
	 */

	public static void main(String[] args) {
		new BlackJackMP(1).frame("BlackJack");
	}

	//Shuffles a deck and returns it as an arra of cards.
	public Card[] shuffle() {

		Card[] shuffledDeck = new Card[52];
		int[] deck;

		deck = ProbStatTools.randomPermutation(52);

		for (int i = 0; i < 52; i++) {
			shuffledDeck[i] = cards[deck[i]];
		}

		return shuffledDeck;
	}
	
	public Card[] getDeck() {
		Card[] deck = new Card[52];
		
		for (int i=0; i<52; i++) {
			deck[i] = new Card(i, true);
		}
		
		return deck;
	}

	//Returns the value of a card taking into acount Blackjack rules
	public int BJValue(Player p) {
		int acc = 0;
		int hardAces = 0;

		for (int i=0; i<p.hand.size(); i++) {
			if (p.hand.get(i).value()==1) {
				if (acc+11<=21) {
					acc+=11;
					hardAces+=1;
				}
				else acc+=1;
			}
			else acc+=p.hand.get(i).value();
		}
		
		if (acc>21) {
			
			while (acc>21 && hardAces>0) {
				acc-=10;
				hardAces--;
			}
		}
		
		if (acc==21 && p.hand.size()==2) {
			System.out.println("BlackJack!");
			players.get(current).win(sequence);
			return 21;
		}
		
		return acc;
	}
	
	public String getHint(Player player, Card dealer) {
		
		String[][] hints = {
				{"h","h","h","h","h","h","h","h","h","h"},
				{"h","h","d","d","d","d","h","h","h","h"},
				{"h","d","d","d","d","d","d","d","d","h"},
				{"h","d","d","d","d","d","d","d","d","d"},
				{"h","h","h","s","s","s","h","h","h","h"},
				{"h","s","s","s","s","s","h","h","h","h"},
				{"h","s","s","s","s","s","h","h","h","h"},
				{"h","s","s","s","s","s","h","h","h","h"},
				{"h","s","s","s","s","s","h","h","h","h"},
				{"s","s","s","s","s","s","s","s","s","s"},
				
				{"h","h","h","h","d","d","h","h","h","h"},
				{"h","h","h","h","d","d","h","h","h","h"},
				{"h","h","h","d","d","d","h","h","h","h"},
				{"h","h","h","d","d","d","h","h","h","h"},
				{"h","h","d","d","d","d","h","h","h","h"},
				{"h","h","d","d","d","d","h","h","h","h"},
				{"s","s","s","s","s","s","s","s","s","s"},
				{"s","s","s","s","s","s","s","s","s","s"},
				
				{"p","p","p","p","p","p","p","p","p","p"},
				{"h","h","h","p","p","p","p","h","h","h"},
				{"h","h","h","p","p","p","p","h","h","h"},
				{"h","h","h","h","h","h","h","h","h","h"},
				{"h","h","p","p","p","p","h","h","h","h"},
				{"h","p","p","p","p","p","p","h","h","h"},
				{"p","p","p","p","p","p","p","p","p","p"},
				{"s","s","s","s","s","s","s","p","p","s"},
				{"s","s","s","s","s","s","s","s","s","s"}};
		
		
		if (player.hand.get(0).value() == 1) {
			Card temp = player.hand.get(0);
			player.hand.set(0, player.hand.get(1));
			player.hand.set(1, temp);
		}
		
		if (isAce(player.hand.get(0))) {
			return print(hints[BJValue(player)-2][dealer.value()-1]);
		}
		
		if (BJValue(player)<8) return "Hit";
		
		else if (BJValue(player) >=8 || BJValue(player)<=17) 
			return print(hints[BJValue(player)-8][dealer.value()-1]);
		
		else if (player.hand.get(0).value() == 10 && player.hand.get(1).value() == 10)
			return "Stand";
		
		else return print(hints[BJValue(player)][dealer.value()-1]);
		
	}	
	
	private boolean isAce(Card card) {
		return (card.value() == 1);
	}
		
	public String print(String which) {
		if (which.equals("h")) return "Hit";
		if (which.equals("d")) return "Double";
		if (which.equals("s")) return "Stand";
		if (which.equals("p")) return "Split";
		else return "";
	}
	
	private void refreshGame() {
		stopAnimation();
		deck=shuffle();
		place=0;
		dealAction.setEnabled(true);
		bet.setEnabled(true);
		doubleAction.setEnabled(false);

		sequence.clear();
		sequence.addPaintable(pics[0]);
		sequence.addPaintable(infoBox);
		
		//System.out.println(BJValue(players.get(current).hand));
		//System.out.println(BJValue(dealer.hand));
		
		bet.setEnabled(true);
		dealer = new Player(true,0,0);
		
		for (int i=0; i<players.size(); i++) {
			players.get(i).hand = new ArrayList<Card>();
			players.get(i).total = 0;
			players.get(i).stood = false;
			players.get(i).busted = false;
		}
		showBalance(players.get(current));
		
	}

}

