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_;
    }
}
