package javass.jass;

import static javass.Preconditions.checkArgument;
import javass.bits.Bits32;
import javass.bits.Bits64;

/**
 * The representation of a score of the game of Jass in a packed form (as a long).
 * @author Charles BEAUVILLE
 * @author Celia HOUSSIAUX
 *
 */
public final class PackedScore {
    private PackedScore() {}
    
    /**
     * A long representing the initial score.
     */
    public static final long INITIAL = 0b000000; 
    
    /**
     * Packs the six components of a {@link Score} in one long.
     * @param turnTricks1 number of tricks gained by team1 during the current turn.
     * @param turnPoints1 number of points gained by team1 during the current turn.
     * @param gamePoints1 number of points gained by team1 during the previous turns (excluding the current turn).
     * @param turnTricks2 number of tricks gained by team2 during the current turn.   
     * @param turnPoints2 number of points gained by team2 during the current turn.
     * @param gamePoints2 number of points gained by team2 during the previous turns (excluding the current turn).
     * @throws IllegalArgumentException if turnTricks1,turnPoints1,gamePoints1 && turnTricks2,turnPoints2,gamePoints2 are non valid.
     * @return a long the packed version of the scores of both team. 
     */
    public static long pack(int turnTricks1, int turnPoints1, int gamePoints1, int turnTricks2, int turnPoints2, int gamePoints2) {
        checkArgument( areArgumentsValid(turnTricks1,turnPoints1,gamePoints1) && areArgumentsValid(turnTricks2,turnPoints2,gamePoints2));
        
        int t1Score = pack32(turnTricks1,turnPoints1,gamePoints1);
        int t2Score = pack32(turnTricks2, turnPoints2, gamePoints2);
        
        return Bits64.pack(t1Score, Integer.SIZE, t2Score, Integer.SIZE);
    }
    
    private static final int TRICK_COUNT_INDEX = 0;
    private static final int TURN_PTS_INDEX = 4;
    private static final int GAME_PTS_INDEX = 13;
    
    private static final int TRICK_COUNT_SIZE = 4;
    private static final int TURN_PTS_SIZE = 9;
    private static final int GAME_PTS_SIZE = 11;
    private static final int UNUSED_SIZE = 8;
    
    private static final int INITIAL_TRICK_COUNT = 0;
    private static final int INITIAL_TURN_POINTS = 0;
    private static final int MAX_TURN_PTS = 257;
    private static final int MAX_GAME_PTS = 2000;
    
    
    
    /**
     * Check if the value of the packed score is valid.
     * @param pkScore a long the packed version of a score.
     * @return a boolean true if the given packed score is valid.
     */
    public static boolean isValid(long pkScore) {
        long nbTricks1 = Bits64.extract(pkScore, TRICK_COUNT_INDEX, TRICK_COUNT_SIZE);
        long nbTricks2 = Bits64.extract(pkScore, Integer.SIZE + TRICK_COUNT_INDEX, TRICK_COUNT_SIZE);
        
        long turnPts1 = Bits64.extract(pkScore, TURN_PTS_INDEX, TURN_PTS_SIZE);
        long turnPts2 = Bits64.extract(pkScore, Integer.SIZE + TURN_PTS_INDEX, TURN_PTS_SIZE);
        
        long gamePts1 = Bits64.extract(pkScore, GAME_PTS_INDEX, GAME_PTS_SIZE);
        long gamePts2 = Bits64.extract(pkScore, Integer.SIZE + GAME_PTS_INDEX, GAME_PTS_SIZE);
        
        long unusedBits1 = Bits64.extract(pkScore, GAME_PTS_INDEX + GAME_PTS_SIZE, UNUSED_SIZE);
        long unusedBits2 = Bits64.extract(pkScore, Integer.SIZE + GAME_PTS_INDEX + GAME_PTS_SIZE, UNUSED_SIZE);

        return ((nbTricks1 <= Jass.TRICKS_PER_TURN)
                && (turnPts1 <= MAX_TURN_PTS) 
                && (gamePts1 <= MAX_GAME_PTS)
                && (unusedBits1 == 0)
                && (nbTricks2 <= Jass.TRICKS_PER_TURN)
                && (turnPts2 <= MAX_TURN_PTS) 
                && (gamePts2 <= MAX_GAME_PTS)
                && (unusedBits2 == 0));
    }
    
    /**
     * Gives the number of {@link Trick}s made by a given team in the current turn.
     * @param pkScore a long the packed version of a {@link Score}.
     * @param t a {@link TeamId} the team of which we want the number of {@link Trick}s.
     * @return an int the number of {@link Trick}s gained by team t during the current turn.
     */
    public static int turnTricks(long pkScore, TeamId t) {
        assert isValid(pkScore): "Invalid pkScore in turnTricks function of PackedScore";
        
        return t == TeamId.TEAM_1 ?
                (int) Bits64.extract(pkScore, TRICK_COUNT_INDEX, TRICK_COUNT_SIZE) :
                    (int) Bits64.extract(pkScore, Integer.SIZE + TRICK_COUNT_INDEX, TRICK_COUNT_SIZE);
    }
    
    /**
     * Gives the number of points made by a given team in the current turn.
     * @param pkScore a long the packed version of a {@link Score}.
     * @param t a {@link TeamId} the team of which we want the number of points made durring the turn.
     * @return an int the number of points gained by team t during the current turn.
     */
    public static int turnPoints(long pkScore, TeamId t) {
        assert isValid(pkScore): "Invalid pkScore in turnPoints function of PackedScore";
        
        return t == TeamId.TEAM_1 ?
                (int) Bits64.extract(pkScore, TURN_PTS_INDEX, TURN_PTS_SIZE) :
                    (int) Bits64.extract(pkScore, Integer.SIZE + TURN_PTS_INDEX, TURN_PTS_SIZE);
    }
    
    /**
     * Gives the number of points made by a given team before the current turn.
     * @param pkScore a long the packed version of a {@link Score} .
     * @param t a {@link TeamId} the team of which we want the number of points.
     * @return an int the number of points gained by team t before the current turn.
     */
    public static int gamePoints(long pkScore, TeamId t) {
        assert isValid(pkScore): "Invalid pkScore in gamePoints function of PackedScore";
        
        return t == TeamId.TEAM_1 ?
                (int) Bits64.extract(pkScore, GAME_PTS_INDEX, GAME_PTS_SIZE) :
                    (int) Bits64.extract(pkScore, Integer.SIZE + GAME_PTS_INDEX, GAME_PTS_SIZE);
    }
    
    /**
     * Gives the number of points made by a given team in the current turn and before.
     * @param pkScore a long the packed version of a {@link Score}.
     * @param t a {@link TeamId} the team of which we want the number of points.
     * @return an int the number of points gained by team t during the current turn and before.
     */
    public static int totalPoints(long pkScore, TeamId t) {
        assert isValid(pkScore): "Invalid pkScore in totalPoints function of PackedScore";
        
        return t == TeamId.TEAM_1 ?
                gamePoints(pkScore,TeamId.TEAM_1) + turnPoints(pkScore,TeamId.TEAM_1) :
                    gamePoints(pkScore,TeamId.TEAM_2) + turnPoints(pkScore,TeamId.TEAM_2);
    }
    
    /**
     * Computes the {@link Score} of the team after a {@link Trick} has been played.
     * @param pkScore the packed version of a {@link Score}.
     * @param winningTeam a {@link TeamId} the team that won the {@link Trick}.
     * @param trickPoints the amount of points the {@link Trick} is worth.
     * @throws IllegalArgumentException if trickPoints is negative.
     * @return a long the updated {@link packedScore}.
     */
    public static long withAdditionalTrick(long pkScore, TeamId winningTeam, int trickPoints) {
        assert isValid(pkScore): "Invalid pkScore in withAdditionalTrick function of PackedScore";
        checkArgument(trickPoints >=0);
        
        int winningTurnTricks = turnTricks(pkScore,winningTeam) + 1;
        int winningTurnPoints = turnPoints(pkScore,winningTeam) + trickPoints; 
        int winningGamePoints = gamePoints(pkScore,winningTeam);
       
        int otherTurnTricks = turnTricks(pkScore,winningTeam.other());
        int otherTurnPoints = turnPoints(pkScore,winningTeam.other());
        int otherGamePoints = gamePoints(pkScore,winningTeam.other());
        
        if(winningTurnTricks == Jass.TRICKS_PER_TURN) 
            winningTurnPoints += Jass.MATCH_ADDITIONAL_POINTS;
        
        return winningTeam == TeamId.TEAM_1 ? 
                pack(winningTurnTricks, winningTurnPoints, winningGamePoints, otherTurnTricks, otherTurnPoints, otherGamePoints) :
                    pack(otherTurnTricks, otherTurnPoints, otherGamePoints, winningTurnTricks, winningTurnPoints, winningGamePoints);
    }
    
    /**
     * Gives the packed version of the {@link Score} at the beginning of the next turn.
     * @param pkScore the packed version of the {@link Score}.
     * @return a long the updated {@link packedScore} for the next turn.
     */
    public static long nextTurn(long pkScore) {
        assert isValid(pkScore): "Invalid pkScore in nextTurn function of PackedScore";
        
        return pack(INITIAL_TRICK_COUNT, INITIAL_TURN_POINTS, totalPoints(pkScore,TeamId.TEAM_1),
                INITIAL_TRICK_COUNT, INITIAL_TURN_POINTS, totalPoints(pkScore,TeamId.TEAM_2));
    }
    
    /**
     * Gives a textual representation of the {@link Score}.
     * @param pkScore a long the packed version of the {@link Score}.
     * @return a string the representation of a {@link Score}.
     */
    public static String toString(long pkScore) {
        assert isValid(pkScore): "Invalid pkScore in toString function of PackedScore";
        
        return ("(" + Integer.toUnsignedString(turnTricks(pkScore,TeamId.TEAM_1)) + 
                "," + Integer.toUnsignedString(turnPoints(pkScore,TeamId.TEAM_1)) + 
                "," + Integer.toUnsignedString(gamePoints(pkScore,TeamId.TEAM_1)) + 
                ")/(" + Integer.toUnsignedString(turnTricks(pkScore,TeamId.TEAM_2)) + 
                "," + Integer.toUnsignedString(turnPoints(pkScore,TeamId.TEAM_2)) +
                "," + Integer.toUnsignedString(gamePoints(pkScore,TeamId.TEAM_2)) + ")"); 
    }
    
    private static int pack32(int turnTricks, int turnPoints, int gamePoints) {
        return Bits32.pack(turnTricks, TRICK_COUNT_SIZE, turnPoints, TURN_PTS_SIZE, gamePoints, GAME_PTS_SIZE);
    }
    
    private static boolean areArgumentsValid(int turnTricks, int turnPoints, int gamePoints) {
        return ((turnTricks >= 0) && (turnTricks <= Jass.TRICKS_PER_TURN) &&
                (turnPoints >= 0) && (turnPoints <= MAX_TURN_PTS) &&
                (gamePoints >= 0) && (gamePoints <= MAX_GAME_PTS));
    }
}
