package pathgame.gameplay;

import java.util.Iterator;
import java.util.LinkedList;
import pathgame.logging.Logger;
import pathgame.tilemap.Tile;
import pathgame.tilemap.TileMap;

/**
 * A container for holding everything about the player
 *
 * @author julia
 */
public class Player
{
    private static final float SPEED = 0.25f;

    private PlayerAbilities abilities = PlayerAbilities.NORMAL;

    private float lastX = 0;
    private float lastY = 0;
    private float x = 0;
    private float y = 0;

    private float velX = 0;
    private float velY = 0;
    private boolean isMoving = false;
    private int currSpeedSlowdown = 1;
    private boolean isSailing = false;
    private boolean hasWon = false;

    private int energySupply;
    private int energyUsed = 0;
    private int objectivesAmount;
    private int objectivesVisited = 0;
    private final LinkedList<MinusStepsValues> steps = new LinkedList<>();
    private Tile currentTile;// = Tiles.GRASS;

    /**
     * Constructor of the player
     *
     */
    public Player()
    {
    }

    /**
     * Returns x-position of the player of the last frame
     *
     * @return x-position of the player of the last frame
     */
    public float getLastX()
    {
        return lastX;
    }

    /**
     * Returns y-position of the player of the last frame
     *
     * @return y-position of the player of the last frame
     */
    public float getLastY()
    {
        return lastY;
    }

    /**
     * Returns current x-position of the player
     *
     * @return current x-position of the player
     */
    public float getX()
    {
        return x;
    }

    /**
     * Returns current y-position of the player
     *
     * @return current y-position of the player
     */
    public float getY()
    {
        return y;
    }

    private void tickSteps()
    {
        Iterator<MinusStepsValues> iter = steps.iterator();
        while(iter.hasNext())
        {
            MinusStepsValues next = iter.next();
            if(next.tick())
            {
                iter.remove();
            }
        }
    }

    /**
     * Recalculates the player position every gametick based on user input
     *
     * @param map the current map
     */
    public void tick(TileMap map)
    {
        tickSteps();

        int currentTileX = Math.round(x);
        int currentTileY = Math.round(y);
        currentTile = map.getTile(currentTileX, currentTileY);

        currSpeedSlowdown = currentTile.getEnergyCost(this);
        lastX = x;
        lastY = y;

        if(isOnTile())
        {
            velX = 0.0f;
            velY = 0.0f;
            if(isMoving)
            {
                Logger.onTileEnter(this, map, currentTileX, currentTileY);
                currentTile.onEnter(this, map, currentTileX, currentTileY);
                steps.addLast(new MinusStepsValues(currSpeedSlowdown));
                energyUsed += currSpeedSlowdown;
            }
            isMoving = false;
            currentTile.isStandingOn(this, map, currentTileX, currentTileY);
        }

        if(Keys.LEFT_KEY.isDown() && !isMoving && currentTileX > 0 && !map.getTile(currentTileX - 1, currentTileY).isBlockingMovement(this))
        {
            velX = -SPEED;
            isMoving = true;
            Logger.onTileLeave(this, map, currentTileX, currentTileY);
            currentTile.onLeave(this, map, currentTileX, currentTileY);
        }
        else if(Keys.RIGHT_KEY.isDown() && !isMoving && currentTileX < map.getWidth() - 1 && !map.getTile(currentTileX + 1, currentTileY).isBlockingMovement(this))
        {
            velX = SPEED;
            isMoving = true;
            Logger.onTileLeave(this, map, currentTileX, currentTileY);
            currentTile.onLeave(this, map, currentTileX, currentTileY);
        }
        else if(Keys.UP_KEY.isDown() && !isMoving && currentTileY > 0 && !map.getTile(currentTileX, currentTileY - 1).isBlockingMovement(this))
        {
            velY = -SPEED;
            isMoving = true;
            Logger.onTileLeave(this, map, currentTileX, currentTileY);
            currentTile.onLeave(this, map, currentTileX, currentTileY);
        }
        else if(Keys.DOWN_KEY.isDown() && !isMoving && currentTileY < map.getHeight() - 1 && !map.getTile(currentTileX, currentTileY + 1).isBlockingMovement(this))
        {
            velY = SPEED;
            isMoving = true;
            Logger.onTileLeave(this, map, currentTileX, currentTileY);
            currentTile.onLeave(this, map, currentTileX, currentTileY);
        }

        float moveX = Math.abs(velX / currSpeedSlowdown);
        float moveY = Math.abs(velY / currSpeedSlowdown);
        if(velX < 0.0f)
        {
            float d = x - (float) Math.floor(x);
            if(d < 0.01f)
            {
                d = 1.0f;
            }
            moveX = -Math.min(moveX, d);
        }
        else if(velX > 0.0f)
        {
            float d = (float) Math.ceil(x) - x;
            if(d < 0.01f)
            {
                d = 1.0f;
            }
            moveX = Math.min(moveX, d);
        }
        else if(velY < 0.0f)
        {
            float d = y - (float) Math.floor(y);
            if(d < 0.01f)
            {
                d = 1.0f;
            }
            moveY = -Math.min(moveY, d);
        }
        else if(velY > 0.0f)
        {
            float d = (float) Math.ceil(y) - y;
            if(d < 0.01f)
            {
                d = 1.0f;
            }
            moveY = Math.min(moveY, d);
        }
        x += moveX;
        y += moveY;
    }

    private boolean isOnTile()
    {
        return Math.abs(x - Math.round(x)) < 0.01f && Math.abs(y - Math.round(y)) < 0.01f;
    }

    /**
     * Returns the current x-velocity of the player
     *
     * @return the current x-velocity of the player
     */
    public float getVelX()
    {
        return velX;
    }

    /**
     * Returns the current y-velocity of the player
     *
     * @return the current y-velocity of the player
     */
    public float getVelY()
    {
        return velY;
    }

    /**
     * Returns the overall energy supply of the player for the current map
     *
     * @return the overall energy supply of the player for the current map
     */
    public int getEnergySupply()
    {
        return energySupply;
    }

    /**
     * Returns the current energy of the player that is left for the current map
     *
     * @return the current energy of the player that is left for the current map
     */
    public int getEnergyLeft()
    {
        return energySupply - energyUsed;
    }

    /**
     * Returns the energy of the player that is already used for the current map
     *
     * @return the energy of the player that is already used for the current map
     */
    public int getEnergyUsed()
    {
        return energyUsed;
    }

    /**
     * Returns the overall amount of objectives that the player has to visit
     *
     * @return the overall amount of objectives that the player has to visit
     */
    public int getObjectivesAmount()
    {
        return objectivesAmount;
    }

    /**
     * Sets the overall amount of objectives that the player has to visit
     *
     * @param objectivesAmount sets the overall amount of objectives that the
     * player has to visit
     */
    public void setObjectivesAmount(int objectivesAmount)
    {
        this.objectivesAmount = objectivesAmount;
    }

    /**
     * Returns the amount of objectives that the player has already to visited
     *
     * @return the amount of objectives that the player has already to visited
     */
    public int getObjectivesVisited()
    {
        return objectivesVisited;
    }

    /**
     * Player visits a town and updates the player statistics
     */
    public void visitTown()
    {
        objectivesVisited++;
    }

    /**
     * Returns a list of the last energy costs of the last steps of the player
     *
     * @return a list of the last energy costs of the last steps of the player
     */
    public LinkedList<MinusStepsValues> getLastSteps()
    {
        return steps;
    }

    /**
     * Sets the abilities of the player
     *
     * @param playerAbilities sets the abilities of the player
     */
    public void setAbilities(PlayerAbilities playerAbilities)
    {
        abilities = playerAbilities;
    }

    /**
     * Returns the abilities of the player
     *
     * @return the abilities of the player
     */
    public PlayerAbilities getAbilities()
    {
        return abilities;
    }

    /**
     * Sets the overall energy supply for the player for the current map
     *
     * @param energySupply sets the overall energy supply for the player for the
     * current map
     */
    public void setEnergySupply(int energySupply)
    {
        this.energySupply = energySupply;
    }

    /**
     * Returns if player has already visited all towns and is allowed win when
     * going to the start field
     *
     * @return if player has already visited all towns and is allowed win when
     * going to the start field
     */
    public boolean canWin()
    {
        return objectivesVisited >= objectivesAmount;
    }

    /**
     * Returns if player has already won the current map
     *
     * @return if player has already won the current map
     */
    public boolean hasWon()
    {
        return hasWon;
    }

    /**
     * Player wins and updates the player statistics
     */
    public void win(TileMap map)
    {
        this.hasWon = true;
        Logger.onWin(this, map);
    }

    /**
     * Returns if player has lost the current map
     *
     * @return if player has lost the current map
     */
    public boolean hasLost()
    {
        return energyUsed >= energySupply;
    }

    /**
     * Player is resetted
     *
     * @param sx start x-position
     * @param sy start y-position
     * @param energySupply overall energy supply for current map
     * @param objectivesAmount objectives amount on current map
     */
    public void reset(int sx, int sy, int energySupply, int objectivesAmount)
    {
        lastX = sx;
        lastY = sy;
        x = sx;
        y = sy;

        velX = 0;
        velY = 0;
        isMoving = false;
        currSpeedSlowdown = 1;
        hasWon = false;

        energyUsed = 0;

        isSailing = false;
        objectivesVisited = 0;
        steps.clear();
        this.energySupply = energySupply;
        this.objectivesAmount = objectivesAmount;
    }

    /**
     * Player is resetted, energy supply is set to standard of 1000, objectives
     * amount is set to standard of 10
     *
     * @param sx start x-position
     * @param sy start y-position
     */
    public void reset(int sx, int sy)
    {
        reset(sx, sy, 1000, 10);
    }

    /**
     * Returns if player is currently moving
     *
     * @return if player is currently moving
     */
    public boolean isMoving()
    {
        return isMoving;
    }

    /**
     * Changes Player state to sailing
     */
    public void switchSailing()
    {
        isSailing = !isSailing;
    }

    /**
     * Returns if player is currently sailing
     *
     * @return if player is currently sailing
     */
    public boolean isSailing()
    {
        return isSailing;
    }

    /**
     * Returns the tile the player is currently standing on
     *
     * @return the tile the player is currently standing on
     */
    public Tile getCurrTile()
    {
        return currentTile;
    }
}
