package bingo.game;

import java.util.Stack;
import bingo.shared.*;

class RingMaster implements States {

    private SocketGate socketGate;
    private GameParameters gameParameters;
    private NotaryPublic notary;
    private Roster roster;
    private Stack announcedBalls;

    private int gameNumber = 0;
    private int state = BEFOREINITIALIZATION;

    RingMaster() throws java.io.IOException {
	socketGate = new SocketGate();
	gameParameters = new GameParameters();
	notary = new NotaryPublic();
	roster = new Roster();
	announcedBalls = new Stack();
	announcedBalls.push(new BingoBall(BingoBall.FREE_SPACE));
    }

    GameParameters getGameParameters() {
	return gameParameters;
    }

    Roster getRoster() {
	return roster;
    }

    int getGameNumber() {
	return gameNumber;
    }

    synchronized void waitForFirstPlayer() {
	gameNumber++;
	state = WAITING;
	socketGate.sendGameStatusMessage(statusString());

        while (state == WAITING) {
            try {
               wait();
            } catch (InterruptedException e) {
            }
        }

        socketGate.sendGameStatusMessage("Beginning count down ... ");
    }

    synchronized void waitForGameToEnd() {
        while (gameInProgress()) {
            try {
               wait();
            } catch (InterruptedException e) {
            }
        }
    }

    boolean ready() {
	return (state == BEFOREINITIALIZATION) ? false : true;
    }

    boolean isCountingDown() {
	return (state == COUNTINGDOWN) ? true : false;
    }

    synchronized boolean isRegistering() {
	return (state == WAITING || state == COUNTINGDOWN) ? true : false;
    }

    boolean isCheckingForWinner() {
	return (state == CHECKINGFORWINNER) ? true : false;
    }

    synchronized boolean gameInProgress() {
	return (state == PLAYING || state == CHECKINGFORWINNER) ? true : false;
    }

    synchronized void startCountDown() {
	state = COUNTINGDOWN;
	notifyAll();
    }

    void startGame() {
	state = PLAYING;
	socketGate.sendGameStatusMessage("Game Starting...");
    }

    void setCheckingForWinner() {
	state = CHECKINGFORWINNER;
    }

    synchronized void setGameResumed() {
	if (state == CHECKINGFORWINNER)
	    state = PLAYING;
    }

    synchronized void setGameOver() {
	state = GAMEOVER;
        announceBall(new BingoBall(BingoBall.GAME_OVER));
	announcedBalls.removeAllElements();
	announcedBalls.push(new BingoBall(BingoBall.FREE_SPACE));
	roster.removeAllElements();
	notifyAll();
    }

    void signTheCards(Card[] cards) {
	try {
	    for (int i = 0; i < cards.length; i++)
	        notary.signTheCard(cards[i], gameNumber);
	} catch (Exception e) {
	    // PENDING: can't sign the cards, what to do?
	}
    }

    boolean verify(Card c) {

	int colMatches = 0;
	int rowMatches = 0;
	int diagMatches = 0;
	int otherDiagMatches = 0;

	if (!(notary.verifyTheSignature(c, gameNumber)))
	    return false;

	for (int i = 0; i < Card.SIZE; i ++) {
	    for (int j = 0; j < Card.SIZE; j ++) {
		if (announcedBalls.contains(c.boardValues[i][j]))
		    rowMatches ++;
		if (announcedBalls.contains(c.boardValues[j][i]))
		    colMatches ++;
		if ((i == j) && announcedBalls.contains(c.boardValues[i][j]))
		    diagMatches ++;
		if (((i + j) == (Card.SIZE-1)) && announcedBalls.contains(c.boardValues[j][i]))
		    otherDiagMatches ++;
	    }
	    if (colMatches == Card.SIZE) {
		return true;
	    } else if (rowMatches == Card.SIZE) {
		return true;
	    } else {
		rowMatches = 0;
		colMatches = 0;
	    }
	}
	if (diagMatches == Card.SIZE) {
	    return true;
	} else if (otherDiagMatches == Card.SIZE) {
	    return true;
	}

	return false;
    }

    void announceBall(BingoBall b) {
	socketGate.sendBall(b);
	socketGate.sendGameStatusMessage(statusString());
	announcedBalls.push(b);
    }

    void sendTimeRemainingMessage(int timeRemaining) {
	socketGate.sendGameStatusMessage(statusString(timeRemaining));
    }

    void sendPlayerStatusMessage(PlayerRecord p) {
	socketGate.sendPlayerStatusMessage(p);
    }

    String statusString() {
	return statusString(-1);
    }

    private String statusString(int anArgument) {
	switch (state) {
	case BEFOREINITIALIZATION:
	    return "The BINGO server isn't ready yet.";
	case WAITING:
	    return "Waiting for first player to register for game # " + gameNumber + ".";
	case COUNTINGDOWN:
	    return "Game # " + gameNumber + " starts in "
		   + anArgument + " seconds.";
	case PLAYING:
	    return "Game #" + gameNumber + " in progress. "
		   + announcedBalls.size() + " balls announced.";
	case CHECKINGFORWINNER:
	    return "Game paused while checking for winner."
		   + announcedBalls.size() + " balls announced.";
	case GAMEOVER:
	    return "Game over.";
	default:
	    return "Nuttin'.";
	}
    }
}
