package pathgame.tilemap;

import java.util.function.Function;
import pathgame.gameplay.Player;

/** Base class for tiles. Tiles are registered on construction.
 *
 * @author kajetan
 */
public class Tile
{
    public static class TileBuilder
    {
        private int energyCost = 2;
        private float forestReplaceChance = 1.0f;
        private Function<Player, Integer> speedUp = (p) -> 0;
        private boolean canHostTown = true;
        private boolean blocksMovement = false;
        private TileType type = TileType.LAND;
        private boolean canHostPath = false;
        private boolean path = false;
        private TileRenderType renderType = TileRenderType.NORMAL;
        
        private TileBuilder()
        {
        }
        
        public static TileBuilder create()
        {
            return new TileBuilder();
        }

        public TileBuilder setEnergyCost(int energyCost)
        {
            this.energyCost = energyCost;
            return this;
        }

        public TileBuilder setForestReplaceChance(float forestReplaceChance)
        {
            this.forestReplaceChance = forestReplaceChance;
            return this;
        }

        public TileBuilder setSpeedUp(Function<Player, Integer> speedUp)
        {
            this.speedUp = speedUp;
            return this;
        }

        public TileBuilder noTown()
        {
            this.canHostTown = false;
            return this;
        }
        
        public TileBuilder noMovement()
        {
            this.blocksMovement = true;
            return this;
        }
        
        public TileBuilder setType(TileType type)
        {
            this.type = type;
            return this;
        }
        
        public TileBuilder pathHost()
        {
            this.canHostPath = true;
            return this;
        }
        
        public TileBuilder path()
        {
            this.path = true;
            return this;
        }
        
        public TileBuilder setRenderType(TileRenderType type)
        {
            this.renderType = type;
            return this;
        }
        
        public Tile build()
        {
            return new Tile(energyCost, forestReplaceChance, speedUp, canHostTown, blocksMovement, type, canHostPath, path, renderType);
        }
    }
    
    private static int idCounter = 0;
    private static Tile[] tiles = new Tile[8];
    
    private static int addTile(Tile t)
    {
        if(idCounter >= tiles.length)
        {
            Tile[] newTiles = new Tile[tiles.length * 2];
            System.arraycopy(tiles, 0, newTiles, 0, tiles.length);
            tiles = newTiles;
        }
        tiles[idCounter] = t;
        return idCounter++;
    }
    
    /** Returns a tile from the given id or null if the id is invalid.
     *
     * @param id the id of the tile
     * @return a tile from the given id or null if the id is invalid
     */
    public static Tile fromId(int id)
    {
        if(id < 0 || id >= idCounter)
        {
            return null;
        }
        return tiles[id];
    }
    
    private final int id;
    private final int energyCost;
    private final float forestReplaceChance;
    private final Function<Player, Integer> speedUp;
    private final boolean canHostTown;
    private final boolean blocksMovement;
    private final TileType type;
    private final boolean canHostPath;
    private final boolean path;
    private final TileRenderType renderType;
    
    protected Tile(int energyCost, float forestReplaceChance, Function<Player, Integer> speedUp, 
            boolean canHostTown, boolean blocksMovement, TileType type, boolean canHostPath, boolean path,
            TileRenderType renderType)
    {
        id = addTile(this);
        this.energyCost = energyCost;
        this.forestReplaceChance = forestReplaceChance;
        this.speedUp = speedUp;
        this.canHostTown = canHostTown;
        this.blocksMovement = blocksMovement;
        this.type = type;
        this.canHostPath = canHostPath;
        this.path = path;
        this.renderType = renderType;
    }

    /** Returns the id of the tile.
     *
     * @return the id of the tile
     */
    public final int getId()
    {
        return id;
    }
    
    public int getEnergyCost(Player p)
    {
        return energyCost - speedUp.apply(p);
    }

    /** Returns the chance that this tile is replaced by forest.
     *
     * @return the chance that this tile is replaced by forest
     */
    public float getForestReplaceChance()
    {
        return forestReplaceChance;
    } 
    
    /** Returns true if this tile can be replaced by a town.
     *
     * @return true if this tile can be replaced by a town
     */
    public boolean canHostTown()
    {
        return canHostTown;
    } 
    
    /** Called when the player fully enters a tile.
     *
     * @param p the player
     * @param map the current tilemap
     * @param x the x coordinate of the tile
     * @param y the y coordinate of the tile
     */
    public void onEnter(Player p, TileMap map, int x, int y)
    {
    }
    
    /** Called when the player leaves a tile.
     *
     * @param p the player
     * @param map the current tilemap
     * @param x the x coordinate of the tile
     * @param y the y coordinate of the tile
     */
    public void onLeave(Player p, TileMap map, int x, int y)
    {
    }

    /** Returns true if this tile blocks movement.
     *
     * @param p the player
     * @return true if this tile blocks movement
     */
    public boolean isBlockingMovement(Player p)
    {
        return blocksMovement || (p.isSailing() && type == TileType.LAND) || 
                (!p.isSailing() && type == TileType.DEEP_WATER);
    }
    
    /** Returns the type of the tile.
     *
     * @return the type of the tile
     */
    public TileType getType()
    {
        return type;
    }

    /** Returns true if this tile can be replaced by a path.
     *
     * @return true if this tile can be replaced by a path
     */
    public boolean canHostPath()
    {
        return canHostPath;
    }
    
    /** Returns true if this tile is a path.
     *
     * @return true if this tile is a path
     */
    public boolean isPath()
    {
        return path;
    }

    /** Returns the rendering type for overlays.
     *
     * @return the rendering type for overlays
     */
    public TileRenderType getRenderType()
    {
        return renderType;
    }
    
    /** Called when the player stands on that tile.
     *
     * @param p the player
     * @param map the current tilemap
     * @param x the x coordinate of the tile
     * @param y the y coordinate of the tile
     */
    public void isStandingOn(Player p, TileMap map, int x, int y)
    {
    }
}
