package com.codingame.game;

import java.util.Random;
import java.util.Properties;

import com.codingame.gameengine.core.GameManager;
import com.codingame.gameengine.core.MultiplayerGameManager;
import com.google.inject.Inject;

class Model {
    @Inject private MultiplayerGameManager<com.codingame.game.Player> gameManager;
    @Inject private LeagueManager league;
    long seed;
    Random random;
    int roadLength;
    int initialStones;
    int trollPosition;
    Player p0, p1;

    class Player {
        com.codingame.game.Player gp;
        int index;
        boolean hit;

        Player(int i) {
            index = i;
            hit = false;
        }

        void loseRound() {
            hit = true;
            winner = 1 - index;
        }

        private int castlePosition;
        public int getCastlePosition() { return castlePosition; }
        public void setCastlePosition(int pos) { castlePosition = pos; }

        private int multiplier;
        public int getMultiplier() { return multiplier; }
        public void setMultiplier(int m) { multiplier = m; }

        class FailedToThrowStonesAndShouldHave extends Exception {}
        class ThrewMoreStonesThanHad extends Exception {}

        private int stones;
        public int getStones() { return stones; }
        public void consumeStones(int n)
            throws ThrewMoreStonesThanHad,
                   FailedToThrowStonesAndShouldHave
        {
            if (n > stones) {
                throw new ThrewMoreStonesThanHad();
            }
            if (n == 0 && stones > 0) {
                throw new FailedToThrowStonesAndShouldHave();
            }
            setStones(stones - n);
        }
        public int consumeMaxStones() {
            int r = stones;
            stones = 0;
            return r;
        }
        public int consumeMinStones() {
            if (stones < 1) {
                throw new Error("Internal error: tried to consume min stones on an empty heap.");
            }
            stones--;
            return 1;
        }
        public void setStones(int n) {
            stones = n;
        }
        public int getOppStones() {
            return p0.stones + p1.stones - stones;
        }

        public void adjustScore(int trollPosition) {
            if (gp.isActive()) {
                gp.setScore(Math.abs(castlePosition - trollPosition));
            }
        }

        public int getTrollDistance() {
            return Math.abs(castlePosition - trollPosition);
        }
    }

    void init() {
        seed = gameManager.getSeed();
        random = new Random(seed);

        switch(league.mapLevel) {
        case SINGLE:
            roadLength = 6;
            initialStones = 15;
            break;
        case DISCRETE:
            int i = random.nextInt(4);
            switch (i) {
            case 0:
                roadLength = 6;
                initialStones = 15;
                break;
            case 1:
                roadLength = 6;
                initialStones = 30;
                break;
            case 2:
                roadLength = 14;
                initialStones = 30;
                break;
            case 3:
                roadLength = 14;
                initialStones = 50;
                break;
            }
            break;
        case CONTINUOUS:
            roadLength = 2 * (3 + random.nextInt(7-3+1));
            initialStones = 15 + random.nextInt(50-15+1);
            break;
        }

        Properties ps = gameManager.getGameParameters();
        String buf = ps.getProperty("roadLength");
        if (buf != null) {
            try {
                int i = Integer.parseInt(buf);
                if (i < 0 || i > 20 || (i & 1) != 0) {
                    gameManager.addToGameSummary(GameManager.formatErrorMessage("Ignoring invalid road length: " + buf));
                }
                else {
                    roadLength = i;
                    gameManager.addToGameSummary("Road length overridden to " + i);
                }
            }
            catch(NumberFormatException e) {
                gameManager.addToGameSummary(GameManager.formatErrorMessage("Ill-formed road length: " + buf));
            }
        }
        ps.setProperty("roadLength", new Integer(roadLength).toString());

        buf = ps.getProperty("initialStones");
        if (buf != null) {
            try {
                int i = Integer.parseInt(buf);
                if (i > 50) {
                    gameManager.addToGameSummary(GameManager.formatErrorMessage("Ignoring invalid initial stone count: " + buf));
                }
                else {
                    initialStones = i;
                    gameManager.addToGameSummary("Initial stone count overridden to " + buf);
                }
            }
            catch (NumberFormatException e) {
                gameManager.addToGameSummary(GameManager.formatErrorMessage("Ill-formed initial stone count: " + buf));
            }
        }
        ps.setProperty("initialStones", new Integer(initialStones).toString());

        trollPosition = roadLength / 2;

        p0 = new Player(0);
        p0.gp = gameManager.getPlayer(0);
        p0.setCastlePosition(0);
        p0.setMultiplier(1);
        p0.adjustScore(trollPosition);
        p0.setStones(initialStones);

        p1 = new Player(1);
        p1.gp = gameManager.getPlayer(1);
        p1.setCastlePosition(roadLength);
        p1.setMultiplier(-1);
        p1.adjustScore(trollPosition);
        p1.setStones(initialStones);
    }

    void moveTroll(int delta) {
        trollPosition += delta;
        if (trollPosition <= 0) {
            trollPosition = 0;
            winner = 1;
        }
        if (trollPosition >= roadLength) {
            trollPosition = roadLength;
            winner = 0;
        }

        p0.adjustScore(trollPosition);
        p1.adjustScore(trollPosition);
    }

    boolean FIX_IT() {
        switch (league.fixLevel) {
	    case SOMETIMES: return random.nextInt(10) > 0;
	    case NEVER: return false;
	    default: throw new JavaLimitationError();
        }
    }

    private Integer winner;
    boolean haveWinner() {
        return winner != null;
    }

    int getWinner() { return winner; }
    int getLoser() { return 1 - winner; }

    boolean exhausted() {
        return p0.getStones() <= 0 || p1.getStones() <= 0;
    }
}
