package pathgame.tilemap;

import java.util.Random;

public class TileMapGenerator
{
    private static long seed = 1;
    
    /** Returns a random generated map
     *
     * @param width the width of the map
     * @param height the height of the map
     * @param towns the amount of towns
     * @return a random generated map
     */
    public static TileMap getMap(int width, int height, int towns)
    {
        return getMap(width, height, seed++, towns);
    }
    
    /** Returns a random generated map
     *
     * @param width the width of the map
     * @param height the height of the map
     * @param seed the seed for the random number generator
     * @param towns the amount of towns
     * @return a random generated map
     */
    public static TileMap getMap(int width, int height, long seed, int towns)
    {
        Random r = new Random(seed);
        
        HighMap highMap = HighMap.generate(seed, width, height);
        
        TileMap map = new TileMap(width, height);
        for(int x = 0; x < width; x++)
        {
            for(int y = 0; y < height; y++)
            {
                if(highMap.get(x, y) < 0.15f)
                {
                    map.setTile(x, y, Tiles.DEEP_WATER);
                }
                else if(highMap.get(x, y) < 0.3f)
                {
                    map.setTile(x, y, Tiles.SHALLOW_WATER);
                }
                else if(highMap.get(x, y) < 0.7f)
                {
                    map.setTile(x, y, randomGrass(r));
                }
                else if(highMap.get(x, y) < 0.85f)
                {
                    map.setTile(x, y, Tiles.HILL);
                }
                else
                {
                    map.setTile(x, y, Tiles.MOUNTAIN);
                }
            }
        }
        
        int forestSize = ((width + height) / 2) / 10;
        forestSize *= forestSize;
        generateForest(map, r, forestSize, 10, 2, Tiles.FOREST);
        generateForest(map, r, forestSize, 5, 2, Tiles.SWAMP, Tiles.SWAMP, Tiles.SWAMP_DECO, Tiles.SWAMP_TREE);
        
        generateTowns(map, r, towns);
        return map;
    }
    
    private static Tile randomGrass(Random r)
    {
        if(r.nextFloat() < 0.3f)
        {
            return randomTile(Tiles.GRASS_VARIANTS, r);
        }
        return Tiles.GRASS;
    }
    
    private static void generateForest(TileMap map, Random r, int depth, int placements, int jumpRadius, Tile... t)
    {
        for(int i = 0; i < placements; i++)
        {
            int x = r.nextInt(map.getWidth());
            int y = r.nextInt(map.getHeight());
            while(map.getTile(x, y).getForestReplaceChance() < 1.0f)
            {
                x = r.nextInt(map.getWidth());
                y = r.nextInt(map.getHeight());
            }
            
            for(int j = 0; j < depth; j++)
            {
                int oldX = x;
                int oldY = y;

                x += r.nextInt(jumpRadius * 2 + 1) - jumpRadius;
                y += r.nextInt(jumpRadius * 2 + 1) - jumpRadius;
                x = Math.min(Math.max(x, 0), map.getWidth() - 1);
                y = Math.min(Math.max(y, 0), map.getHeight() - 1);
                
                if(r.nextFloat() < map.getTile(x, y).getForestReplaceChance())
                {
                    map.setTile(x, y, randomTile(t, r));
                    placeForest(map, r, x - 1, y, t);
                    placeForest(map, r, x + 1, y, t);
                    placeForest(map, r, x, y - 1, t);
                    placeForest(map, r, x, y + 1, t);
                }
                else
                {
                    x = oldX;
                    y = oldY;
                }
            }
        }
    }
    
    private static Tile randomTile(Tile[] tiles, Random r)
    {
        if(tiles.length == 1)
        {
            return tiles[0];
        }
        return tiles[r.nextInt(tiles.length)];
    }
    
    private static void placeForest(TileMap map, Random r, int x, int y, Tile... t)
    {
        if(x >= 0 && x < map.getWidth() && y >= 0 && y < map.getHeight())
        {
            if(r.nextFloat() * 2 < map.getTile(x, y).getForestReplaceChance())
            {
                map.setTile(x, y, randomTile(t, r));
            }
        }
    }
    
    private static void generateTowns(TileMap map, Random r, int towns)
    {
        int failCounter = 0;
        while(towns > 0 && failCounter < 100)
        {
            int x = r.nextInt(map.getWidth());
            int y = r.nextInt(map.getHeight());
            if(map.getTile(x, y).canHostTown() && checkNearbyTowns(map, x, y, 2))
            {
                map.setTile(x, y, Tiles.TOWN);
                towns--;
                failCounter = 0;
            }
            else
            {
                failCounter++;
            }
        }
    }
    
    private static boolean checkNearbyTowns(TileMap map, int x, int y, int radius)
    {
        int startX = Math.max(x - radius, 0);
        int startY = Math.max(y - radius, 0);
        int endX = Math.min(x + radius, map.getWidth() - 1);
        int endY = Math.min(y + radius, map.getHeight() - 1);
        for(int mx = startX; mx <= endX; mx++)
        {
            for(int my = startY; my <= endY; my++)
            {
                if(map.getTile(mx, my) == Tiles.TOWN)
                {
                    return false;
                }
            }
        }
        return true;
    }
}
