////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////

package pp.monopoly.game.server;

import java.util.List;
import java.util.Random;

import pp.monopoly.message.server.DiceResult;
import pp.monopoly.model.FieldVisitor;
import pp.monopoly.model.Figure;
import pp.monopoly.model.card.DeckHelper;
import pp.monopoly.model.fields.BuildingProperty;
import pp.monopoly.model.fields.EventField;
import pp.monopoly.model.fields.FineField;
import pp.monopoly.model.fields.FoodField;
import pp.monopoly.model.fields.GateField;
import pp.monopoly.model.fields.GoField;
import pp.monopoly.model.fields.GulagField;
import pp.monopoly.model.fields.PropertyField;
import pp.monopoly.model.fields.TestStreckeField;
import pp.monopoly.model.fields.WacheField;

/**
 * Class representing a player
 */
public class Player implements FieldVisitor<Void>{
    private final int id;
    private String name;
    private PlayerColor color;
    private int accountBalance = 0;
    private Figure figure;
    private List<PropertyField> properties;
    private int getOutOfJailCard;
    private int fieldID;
    private DiceResult rollResult;
    private final PlayerHandler handler;
    private PlayerState state = new LobbyState();

    /**
     * Constructs a player with the speciefied params
     * @param id the id of the player
     * @param name the name of the player
     * @param handler the PlayerHandler thispalyer is a part of
     */
    public Player(int id, String name, PlayerHandler handler) {
        this.name = name;
        this.id = id;
        this.handler = handler;
    }

    /**
     * Constructs a player with the specified id
     * @param id the id of the player
     * @param handler the PlayerHandler this player is a part of
     */
    public Player(int id, PlayerHandler handler) {
        this.id = id;
        this.handler = handler;
    }

    /**
     * Set the name of the Player
     * @param name the new name
     */
    void setName(String name) {
        this.name = name;
    }

    /**
     * Set the PlayerColor
     * @param color the color to be set to
     */
    void setColor(PlayerColor color) {
        this.color = color;
    }

    /**
     * Returns this players id
     * @return th eid of this player
     */
    public int getId() {
        return id;
    }

    /**
     * Returns the current position of the player
     * @return the current position of this player
     */
    public int getFieldID() {
        return fieldID;
    }

    /**
     * Moves by the specified amount of steps
     * @param steps the number of steps to move
     * @return the new position
     */
    public int move(int steps){
        return movePos(fieldID+steps);
    }

    /**
     * Moves the player to the specified Position on the board
     * @param position the position to move to
     * @return the new position
     */
    public int movePos(int position){
        fieldID = fieldID+position;
        if(fieldID >= 40) {
            fieldID = fieldID%40;
            earnMoney(2000);
        }
        return fieldID;
    }

    /**
     * Gets all the properties owned by this player
     * @return List of all properties owned by this player
     */
    public List<PropertyField> getProperties() {
        return properties;
    }

    /**
     * Buy the speciefied property.
     * Properties can olny be bought when they are not sold yet and you have enough money left to buy
     * @param property to property to be bought
     */
    public void buyProperty(PropertyField property) {
        if (property.getOwner() == null && accountBalance >= property.getPrice()) {
            properties.add(property);
            pay(property.getPrice());
        }
    }

    /**
     * Sell the property
     * @param property the property to be sold
     */
    public void sellProperty(PropertyField property) {
        if (property.getOwner() == this) {
            properties.remove(property);
            property.setOwner(null);
        }
    }

    /**
     * Gets this players current accountBalanece
     * @return the amount of money currently owned by this player
     */
    public int getAccountBalance() {
        return accountBalance;
    }

    /**
     * Removed the speciefied amount of money to this players accountabalance
     * @param amount the amount to be removed
     */
    public void pay(int amount) {
        accountBalance -= amount;
    }

    /**
     * Add the speciefied amount of money to this players accountabalance
     * @param amount the amount to be added
     */
    public void earnMoney(int amount) {
        accountBalance += amount;
    }

    /**
     * Return the players name
     * @return the name of this player
     */
    public String getName() {
        return name;
    }

    /**
     * Return the number of GEtOutOfJailCards owned by this player
     * @return
     */
    public int getNumJailCard() {
        return getOutOfJailCard;
    }

    /**
     * Adds a GetOutOfJailCard
     */
    public void addJailCard() {
        getOutOfJailCard++;
    }

    /**
     * Removes a GetOutOfJailCard.
     * Removes one single card per call, to a minimum of 0 cards
     */
    public void removeJailCard() {
        if (getOutOfJailCard ==0) {
            throw new IllegalStateException("Has no JailCard to remove");
        }
        getOutOfJailCard--;
    }

    /**
     * Handles the logic of paying the jail bail
     */
    public void payBail() {
        state.payBail();
    }

    /**
     * Handles the logic of using a GetOutOfJailCard
     */
    public void useJailCard() {
        state.useJailCard();
    }

    @Override
    public Void visit(BuildingProperty field) {
        int rent = field.calcRent();

        field.getOwner().earnMoney(rent);
        pay(rent);
        return null;
    }

    @Override
    public Void visit(FoodField field) {
        int factor = 4;
        if (field.getOwner().getNumProp(field) == 2) {
            factor = 10;
        }
        field.getOwner().earnMoney(rollResult.calcTotal()*factor);
        pay(rollResult.calcTotal()*factor);
        return null;
    }

    @Override
    public Void visit(GateField field) {
        int rent = field.calcRent() * field.getOwner().getNumProp(field);

        field.getOwner().earnMoney(rent);
        pay(rent);
        return null;
    }

    @Override
    public Void visit(GulagField field) {
        state = new JailState();
        return null;
    }

    @Override
    public Void visit(TestStreckeField field) {
        earnMoney(field.collectMoney());
        return null;
    }

    @Override
    public Void visit(EventField field) {
        DeckHelper.drawCard();
        return null;
    }

    @Override
    public Void visit(WacheField field) {
        movePos(10);
        return null;
    }

    @Override
    public Void visit(GoField field) {
        earnMoney(2000);
        GulagField res = (GulagField) handler.getLogic().getBoardManager().getFieldAtIndex(10);
        res.accept(this);
        return null;
    }

    @Override
    public Void visit(FineField field) {
        int amount = field.getFine();
        pay(amount);
        TestStreckeField res =(TestStreckeField) handler.getLogic().getBoardManager().getFieldAtIndex(20);
        res.addMoney(amount);
        return null;
    }

    /**
     * Return the number of Properties of the speciefied fild type
     * @param field the type of field to search for
     * @return the number of the fields owned with the specified type
     */
    public int getNumProp(PropertyField field) {
        int count = 0;
        for (PropertyField propertyField : properties) {
            if (propertyField.getClass() == field.getClass()) {
                count++;
            }
        }

        return count;
    }

        /**
     * Inner class for dice functionality in the game.
     * Rolls random dice values.
     */
    private class Dice {
        private static Random random = new Random();

        /**
         * Rolls a single die and returns a random value from 1 to 6.
         *
         * @return the result of a dice roll (1 to 6)
         */
        private static int rollDice() {
            return random.nextInt(6) + 1;
        }
    }

    /**
     * Rolls two dice and returns a list with the results.
     *
     * @return a List of two integers representing the dice roll results
     */
    DiceResult rollDice() {
        return state.rollDice();
    }

    /**
     * A interface representing the PlayerStates
     */
    private interface PlayerState {
        /**
         * Handles the logic for rolling Dice
         * @return the {@link DiceResult} of this the DiceRoll
         */
        DiceResult rollDice();

        /**
         * Handles the logic for paying the Jail Bail
         */
        void payBail();

        /**
         * Handles the action of using a GetOutOfJail Card
         */
        void useJailCard();
    }


    /**
     * Class to represent the Active PlayerState
     * This class is set when it is the Players turn to do actions
     */
    private class ActiveState implements PlayerState {

        @Override
        public DiceResult rollDice() {
            List<Integer> roll = List.of(Dice.rollDice(), Dice.rollDice());
            rollResult = new DiceResult(roll);
            return rollResult;
        }

        @Override
        public void payBail() {
            // do nothing
        }

        @Override
        public void useJailCard() {
            // do nothings
        }
    }


    /**
     * A class to represent the Lobby PlayerState
     * Set when in Lobby
     */
    private class LobbyState implements PlayerState{

        @Override
        public DiceResult rollDice() {
            //do nothing
            return null;
        }

        @Override
        public void payBail() {
            //do nothing
        }

        @Override
        public void useJailCard() {
            // do nothing
        }

    }


    /**
     * A class to represent the Jailed PlayerState
     * Set when in Gulag
     */
    private class JailState implements PlayerState {

        private int DoubletsCounter = 3;

        @Override
        public DiceResult rollDice() {
            List<Integer> roll = List.of(Dice.rollDice(), Dice.rollDice());
            rollResult = new DiceResult(roll);
            if (rollResult.isDoublets()) {
                state = new ActiveState();
            } else if (DoubletsCounter == 0) {

            } else {
                DoubletsCounter--;
            }
            return rollResult;
        }

        @Override
        public void payBail() {
            pay(500);
            state = new ActiveState();
        }

        @Override
        public void useJailCard() {
            getOutOfJailCard--;
            state = new ActiveState();
        }
        
    }

    private class BankruptState implements PlayerState {

        @Override
        public DiceResult rollDice() {
            // TODO Auto-generated method stub
            throw new UnsupportedOperationException("Unimplemented method 'rollDice'");
        }

        @Override
        public void payBail() {
            // TODO Auto-generated method stub
            throw new UnsupportedOperationException("Unimplemented method 'payBail'");
        }

        @Override
        public void useJailCard() {
            // TODO Auto-generated method stub
            throw new UnsupportedOperationException("Unimplemented method 'useJailCard'");
        }
        
    }

    private class WaitForTurnState implements PlayerState {

        @Override
        public DiceResult rollDice() {
            // TODO Auto-generated method stub
            throw new UnsupportedOperationException("Unimplemented method 'rollDice'");
        }

        @Override
        public void payBail() {
            // TODO Auto-generated method stub
            throw new UnsupportedOperationException("Unimplemented method 'payBail'");
        }

        @Override
        public void useJailCard() {
            // TODO Auto-generated method stub
            throw new UnsupportedOperationException("Unimplemented method 'useJailCard'");
        }
        
    }
}
