package pathgame.gameplay;

import java.util.Iterator;
import java.util.LinkedList;
import pathgame.tilemap.Tile;
import pathgame.tilemap.TileMap;
import pathgame.tilemap.Tiles;

public class Player
{
    private static final float SPEED = 0.125f;

    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 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;

    public Player()
    {
    }

    public float getLastX()
    {
        return lastX;
    }

    public float getLastY()
    {
        return lastY;
    }

    public float getX()
    {
        return x;
    }

    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();
            }
        }
    }

    public void tick(TileMap map)
    {
        tickSteps();

        int currentTileX = Math.round(x);
        int currentTileY = Math.round(y);
        
        //TODO: Bug beheben: Exception, wenn Spieler sofort an den unteren Levelrand geht (auch am rechten Levelrand)
        //ArrayIndexOutOfBoundsException: Index 10 out of bounds for length 10
        //System.out.println(currentTileY);
        //currentTile = Tiles.GRASS;
        currentTile = map.getTile(currentTileX, currentTileY);
        //System.out.println(currentTile);
        currSpeedSlowdown = currentTile.getEnergyCost(abilities);
        lastX = x;
        lastY = y;
        
        if(isOnTile())
        {
            velX = 0.0f;
            velY = 0.0f;
            if(isMoving)
            {
                currentTile.onEnter(this, map, currentTileX, currentTileY);
                steps.addLast(new MinusStepsValues(currSpeedSlowdown));
                energyUsed += currSpeedSlowdown;
            }
            isMoving = false;
        }

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

    public float getVelX()
    {
        return velX;
    }

    public float getVelY()
    {
        return velY;
    }

    public int getEnergySupply()
    {
        return energySupply;
    }

    public int getEnergyLeft()
    {
        return energySupply - energyUsed;
    }

    public int getEnergyUsed()
    {
        return energyUsed;
    }

    public int getObjectivesAmount()
    {
        return objectivesAmount;
    }
    
    public void setObjectivesAmount(int objectivesAmount)
    {
        this.objectivesAmount = objectivesAmount;
    }

    public int getObjectivesVisited()
    {
        return objectivesVisited;
    }
    
    public void visitTown()
    {
        objectivesVisited++;
    }

    public LinkedList<MinusStepsValues> getLastSteps()
    {
        return steps;
    }

    public void setAbilities(PlayerAbilities playerAbilities)
    {
        abilities = playerAbilities;
    }

    public PlayerAbilities getAbilities()
    {
        return abilities;
    }

    public void setEnergySupply(int energySupply)
    {
        this.energySupply = energySupply;
    }

    public boolean hasWon()
    {
        return objectivesVisited >= objectivesAmount;
    }

    public boolean hasLost()
    {
        return energyUsed >= energySupply;
    }

    public void reset(int energySupply, int objectivesAmount, PlayerAbilities abilities)
    {
        lastX = 0;
        lastY = 0;
        x = 0;
        y = 0;

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

        energyUsed = 0;

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

    public void reset()
    {
        reset(1000, 10, PlayerAbilities.NORMAL);
    }

    public boolean isMoving()
    {
        return isMoving;
    }
    
    public Tile getCurrTile()
    {
        return currentTile;
    }
}
