package space.animefu.starfallhaven;

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;

/** Platform-neutral, deterministic overview of the region surrounding the haven. */
public final class RegionalMap {
    public static final int DEFAULT_WIDTH=18;
    public static final int DEFAULT_HEIGHT=12;

    public enum Biome { AURORA_WOODS, GLASSLAND, ASH_DUNES, FROST_MARSH, BASALT_RIDGES, SHIMMER_COAST }
    public enum Resource { TIMBER, ORE, FOOD, CRYSTAL, WATER }

    public static final class Cell {
        public final int x,y;
        public final Biome biome;
        public final EnumSet<Resource> resources;
        public final int elevation,moisture,temperature,suitability;

        Cell(int x,int y,Biome biome,EnumSet<Resource> resources,int elevation,int moisture,int temperature,int suitability){
            this.x=x;this.y=y;this.biome=biome;this.resources=resources.clone();this.elevation=elevation;this.moisture=moisture;this.temperature=temperature;this.suitability=suitability;
        }
        public boolean canLand(){return suitability>=35&&biome!=Biome.SHIMMER_COAST;}
        public boolean has(Resource resource){return resources.contains(resource);}
    }

    public final long seed;
    public final int width,height;
    private final Cell[][] cells;

    public RegionalMap(long seed){this(seed,DEFAULT_WIDTH,DEFAULT_HEIGHT);}
    public RegionalMap(long seed,int width,int height){
        if(width<4||height<4)throw new IllegalArgumentException("Regional map is too small");
        this.seed=seed;this.width=width;this.height=height;this.cells=new Cell[width][height];
        generate();
    }

    private void generate(){
        for(int x=0;x<width;x++)for(int y=0;y<height;y++){
            int elevation=field(seed^0x35d2a8c91L,x,y);
            int moisture=field(seed^0x7f4a7c159L,x,y);
            int temperature=field(seed^0x12d6874b9L,x,y);
            Biome biome=biome(elevation,moisture,temperature,x,y);
            EnumSet<Resource> resources=resources(biome,elevation,moisture,x,y);
            int variety=resources.size()*7;
            int slope=Math.abs(elevation-50);
            int climate=Math.abs(temperature-54);
            int suitability=clamp(82-slope-climate/2+variety-(biome==Biome.FROST_MARSH?16:0)-(biome==Biome.SHIMMER_COAST?55:0),0,100);
            cells[x][y]=new Cell(x,y,biome,resources,elevation,moisture,temperature,suitability);
        }
        // Every seed has several valid choices; generation never forces a single landing site.
        int cx=width/2,cy=height/2;
        if(validLandingSites().size()<3)for(int x=cx-1;x<=cx+1;x++)cells[x][cy]=fallback(x,cy);
    }

    private Cell fallback(int x,int y){return new Cell(x,y,Biome.GLASSLAND,EnumSet.of(Resource.FOOD,Resource.ORE),50,50,55,78);}
    private Biome biome(int elevation,int moisture,int temperature,int x,int y){
        boolean coast=(x==0||y==0||x==width-1||y==height-1)&&hash(seed,x,y,31)%100<58;
        if(coast||elevation<18)return Biome.SHIMMER_COAST;
        if(elevation>76)return Biome.BASALT_RIDGES;
        if(temperature<32&&moisture>46)return Biome.FROST_MARSH;
        if(moisture<27&&temperature>57)return Biome.ASH_DUNES;
        if(moisture>58)return Biome.AURORA_WOODS;
        return Biome.GLASSLAND;
    }
    private EnumSet<Resource> resources(Biome biome,int elevation,int moisture,int x,int y){
        EnumSet<Resource> out=EnumSet.noneOf(Resource.class);
        switch(biome){
            case AURORA_WOODS:out.add(Resource.TIMBER);out.add(Resource.FOOD);break;
            case GLASSLAND:out.add(Resource.FOOD);break;
            case ASH_DUNES:out.add(Resource.CRYSTAL);break;
            case FROST_MARSH:out.add(Resource.WATER);out.add(Resource.FOOD);break;
            case BASALT_RIDGES:out.add(Resource.ORE);out.add(Resource.CRYSTAL);break;
            case SHIMMER_COAST:out.add(Resource.WATER);break;
        }
        if(elevation>58&&hash(seed,x,y,73)%100<42)out.add(Resource.ORE);
        if(moisture>45&&hash(seed,x,y,97)%100<38)out.add(Resource.WATER);
        if(hash(seed,x,y,131)%100<16)out.add(Resource.CRYSTAL);
        return out;
    }

    public Cell cell(int x,int y){if(!inside(x,y))throw new IndexOutOfBoundsException(x+","+y);return cells[x][y];}
    public boolean inside(int x,int y){return x>=0&&y>=0&&x<width&&y<height;}
    public List<Cell> validLandingSites(){
        List<Cell> result=new ArrayList<>();for(int y=0;y<height;y++)for(int x=0;x<width;x++)if(cells[x][y]!=null&&cells[x][y].canLand())result.add(cells[x][y]);return Collections.unmodifiableList(result);
    }
    public Cell bestLandingSite(){Cell best=null;for(Cell cell:validLandingSites())if(best==null||cell.suitability>best.suitability)best=cell;return best;}

    private int field(long salt,int x,int y){
        int sum=0,weight=0;for(int dx=-1;dx<=1;dx++)for(int dy=-1;dy<=1;dy++){int w=dx==0&&dy==0?4:(dx==0||dy==0?2:1);sum+=(int)(hash(salt,x+dx,y+dy,17)%101)*w;weight+=w;}return sum/weight;
    }
    static long hash(long seed,int x,int y,int salt){long z=seed+0x9e3779b97f4a7c15L*(x*73856093L+y*19349663L+salt);z=(z^(z>>>30))*0xbf58476d1ce4e5b9L;z=(z^(z>>>27))*0x94d049bb133111ebL;return (z^(z>>>31))&Long.MAX_VALUE;}
    private static int clamp(int value,int low,int high){return Math.max(low,Math.min(high,value));}
}
