package com.codingame.game;

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

import com.codingame.gameengine.core.AbstractPlayer.TimeoutException;
import com.codingame.gameengine.core.AbstractReferee;
import com.codingame.game.GodModeManager;
import com.google.inject.Inject;
import com.google.inject.Provider;

public class Referee extends AbstractReferee {
    @Inject private GameManager gameManager;
    @Inject private GodModeManager gm;
    @Inject private LeagueManager league;

    @Inject private View view;
    @Inject private Model model;

    boolean disqual = false;

    @Override
    public void init() {
        gm.init();
        model.init();
        gameManager.getPlayer(0).model = model.p0;
        gameManager.getPlayer(1).model = model.p1;

        for (Player p: gameManager.getPlayers()) {
            p.gameInit(model.roadLength, model.initialStones,
                       model.seed, gm.getSalt());
        }

        view.init(model);
        gameManager.getPlayer(0).view = view.p0;
        gameManager.getPlayer(1).view = view.p1;
    }

    private void disqualify(Player player, String popup, String message) {
        player.deactivate(player.getNicknameToken() + " " + popup);
        player.view.disqualify(message);
        player.setScore(-1);
    }

    @Override
    public void gameTurn(int turn) {
        // System.err.println("Starting turn " + turn);

        view.startTurn();

        // Did I mention I hate Java? It didn't *have* to be this ugly!
        if (disqual) { endGame(); return; }
        if (model.exhausted()) { finishStones(); return ;}
        if (model.haveWinner()) { endGame(); return; }

        for (Player player : gameManager.getActivePlayers()) {
            player.sendGameTurn();
        }
        // SDK @#%^&! arbitrary sequence point: last input < first output

        /* Parse player actions and decide basic disqualifications.
         * Display their optional message right now: if their action
         * is ill-formed it could help them debug.  Or shame them, at
         * least.
         */
        for (Player player : gameManager.getActivePlayers()) {
            player.receiveGameTurn(); gm.transcend(player);
            switch (player.type) {
            case Timeout:
                disqualify(player, "T/O", "timed out!");
                player.view.markTimeout();
                disqual = true;
                break;
            case Invalid:
                disqualify(player, "INVALID", "provided an ill-formed action");
                player.view.markIllegal();
                disqual = true;
                break;
            case Throw:
                try { player.model.consumeStones(player.stoneThrow); }
                catch (Model.Player.ThrewMoreStonesThanHad e) {
                    if (model.FIX_IT()) {
                        player.view.threwMoreStonesThanHad();
                        player.stoneThrow = player.model.consumeMaxStones();
                    }
                    else {
                        disqualify(player, "ILLEGAL", "tried to throw more stones than they had.  They went into debt trying to provide.  The economy tanked, recession and famine ensued; even the troll wouldn't have wanted to bash them anymore.  But that's no victory.");
                        player.view.markIllegal();
                        disqual = true;
                    }
                }
                catch (Model.Player.FailedToThrowStonesAndShouldHave e) {
                    if (model.FIX_IT()) {
                        player.view.failedToThrowStonesAndShouldHave();
                        player.stoneThrow = player.model.consumeMinStones();
                    }
                    else {
                        disqualify(player, "ILLEGAL", "tried not throwing any stones.  They were then eaten by a grue.");
                        disqual = true;
                    }
                }
                break;
            }
            player.view.displayMessage(player.messageString);
        }
        if (disqual) return;

        /* Update game model and view, stones' part.
         *
         * As a special case, the "cheater" (sending out negative
         * stones) handling is deferred here because we need to update
         * its view for it to be funny.  In the end, they're still
         * disqualified.
         *
         * Gather other game end scenarios (actual victory or stone
         * exhaustion).
         */
        int delta = 0;
        gm.update(gameManager.getPlayers());
        for (Player player : gameManager.getActivePlayers()) {
            player.view.throwStones(player.stoneThrow);
            delta += player.model.getMultiplier() * player.stoneThrow;

            if (player.stoneThrow < 0) {
                switch(league.cheatLevel) {
                case ALLOWED:
                    break;
                case TOLERATED:
                    player.view.markCheat();
                    if (model.random.nextInt(2) == 0) player.model.loseRound();
                    break;
                case FORBIDDEN:
                    disqualify(player, "CHEAT", "cheated.  Banning account.");
                    player.view.markCheat();
                    disqual = true;
                    break;
                }
            }
            if (player.stoneThrow != 0) {
                player.view.animateStones(player.stoneThrow);
                player.view.updateStoneCounter();
            }
        }

        /* Update game model and view, troll part.
         *
         * If a player cheated, delta is unusable as is.
         * (Consider the case the player on the right sent
         * INT_MIN.  INT_MIN * (-1) = INT_MIN, so that player
         * would both glean the stones *and* push the troll away.
         * It would be unfair to have a cheating player "win"
         * (earn the opponent castle destruction animation) this
         * way.
         */
        boolean cheat0 = gameManager.getPlayer(0).isActive()
            && gameManager.getPlayer(0).stoneThrow < 0;
        boolean cheat1 = gameManager.getPlayer(1).isActive()
            && gameManager.getPlayer(1).stoneThrow < 0;
        if (cheat0 && cheat1); // here we can actually keep delta's value
        else if (cheat0) delta = -1;
        else if (cheat1) delta =  1;

        if (delta > 0) {
            model.moveTroll(+1);
            view.moveTroll(View.Dir.RIGHT);
        }
        else if (delta < 0) {
            model.moveTroll(-1);
            view.moveTroll(View.Dir.LEFT);
        }
        else {
            view.moveTroll(View.Dir.STILL);
            // XXX animate
        }
    }

    // XXX very similar to main turn pendant
    private void finishStones() {
        boolean noStones = true;
        int delta = 0;
        for (Player player : gameManager.getActivePlayers()) {
            if (model.haveWinner() && player.getIndex() == model.getLoser())
                continue;
            player.stoneThrow = player.model.getStones();
            player.model.setStones(0);
            delta += player.stoneThrow * player.model.getMultiplier();
            player.view.throwStones(player.stoneThrow);
            if (player.stoneThrow != 0) {
                noStones = false;
                player.view.animateStones(player.stoneThrow);
                player.view.updateStoneCounter();
            }
        }
        if (noStones) { endGame(); return; }
        model.moveTroll(delta);
        view.moveTroll();
    }

    private void endGame() {
        gameManager.endGame();

        if (model.haveWinner()) {
            int loser = model.getLoser();
            gameManager.getPlayer(loser).view.defeat();
        }

        Player p0 = gameManager.getPlayer(0);
        Player p1 = gameManager.getPlayer(1);

        int s0 = p0.getScore();
        int s1 = p1.getScore();

        if (s0 > s1) {
            p0.view.victory();
            p1.view.markLoser();
        }
        else if (s0 < s1) {
            p1.view.victory();
            p0.view.markLoser();
        }
        else if (s0 < 0) {
            view.doubleDefeat();
            p0.view.markLoser();
            p1.view.markLoser();
        }
        else {
            view.draw();
        }
    }
}
