import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;

/** A cut-down Klondike game (stock, waste, 7 tableau columns). No JavaFX. */
public class KlondikeGame {
	public static final int COLUMNS = 7;

	private ArrayList<Card> stock_ = new ArrayList<Card>();
	private ArrayList<Card> waste_ = new ArrayList<Card>();
	private ArrayList<ArrayList<Card>> columns_ =
	    new ArrayList<ArrayList<Card>>();
	private String lastError_ = "";
	private int moves_ = 0;
	private ArrayList<GameListener> listeners_ = new ArrayList<GameListener>();

	public KlondikeGame ( long seed ) {
		String[] suits = { "♥", "♦", "♣", "♠" };
		for ( int s = 0 ; s < suits.length ; s++ ) {
			for ( int rank = 1 ; rank <= 13 ; rank++ ) {
				stock_.add(new Card(rank,suits[s]));
			}
		}
		Collections.shuffle(stock_,new Random(seed));
		for ( int c = 0 ; c < COLUMNS ; c++ ) {
			ArrayList<Card> column = new ArrayList<Card>();
			for ( int n = 0 ; n <= c ; n++ ) {
				column.add(stock_.remove(stock_.size() - 1));
			}
			columns_.add(column);
		}
	}

	// ---- Observer: registration and notification ----

	public void addListener ( GameListener listener ) {
		listeners_.add(listener);
	}

	public void removeListener ( GameListener listener ) {
		listeners_.remove(listener);
	}

	private void fireGameChanged () {
		// iterate over a copy, so a listener may remove itself while being notified
		ArrayList<GameListener> copy = new ArrayList<GameListener>(listeners_);
		for ( GameListener listener : copy ) {
			listener.gameChanged(this);
		}
	}

	// ---- operations: each one changes the state, then notifies ----

	/**
	 * Turns over the top stock card, or recycles the waste when the stock is
	 * empty.
	 */
	public void drawFromStock () {
		if ( stock_.isEmpty() ) {
			while ( !waste_.isEmpty() ) {
				stock_.add(waste_.remove(waste_.size() - 1));
			}
		} else {
			waste_.add(stock_.remove(stock_.size() - 1));
		}
		moves_++;
		fireGameChanged();
	}

	/**
	 * Moves the waste card onto a column if the rules allow it; otherwise sets
	 * getLastError().
	 */
	public boolean moveWasteTo ( int column ) {
		Card moving = getWasteTop();
		Card target = getColumnTop(column);
		if ( moving == null ) {
			lastError_ = "There is no card on the waste pile.";
			return false;
		}
		if ( target == null && moving.getRank() != 13 ) {
			lastError_ = "Only a king can go on an empty column.";
			return false;
		}
		if ( target != null ) {
			if ( target.isRed() == moving.isRed() ) {
				lastError_ =
				    "Colors must alternate: " + moving + " can't go on " + target + ".";
				return false;
			}
			if ( moving.getRank() != target.getRank() - 1 ) {
				lastError_ = "The card must be one lower: " + moving + " can't go on "
				    + target + ".";
				return false;
			}
		}
		columns_.get(column).add(waste_.remove(waste_.size() - 1));
		lastError_ = "";
		moves_++;
		fireGameChanged();
		return true;
	}

	// ---- queries: views read the state through these ----

	public Card getWasteTop () {
		if ( waste_.isEmpty() ) {
			return null;
		}
		return waste_.get(waste_.size() - 1);
	}

	public Card getColumnTop ( int column ) {
		ArrayList<Card> cards = columns_.get(column);
		if ( cards.isEmpty() ) {
			return null;
		}
		return cards.get(cards.size() - 1);
	}

	public int getStockSize () {
		return stock_.size();
	}

	public int getMoves () {
		return moves_;
	}

	public String getLastError () {
		return lastError_;
	}
}
