package pathgame.tilemap;

import java.util.Random;

public class HighMap
{
    private static final float LOW_FREQUENCY = 8.0f;
    private static final float HIGH_FREQUENCY = 7.0f;
    
    public static HighMap generate(long seed, int width, int height)
    {
        HighMap map = new HighMap(seed, width, height);
        map.zoom();
        return map;
    }
    
    private final int width;
    private final int height;
    private final float data[][];
    private final Random r;
    
    private HighMap(long seed, int width, int height)
    {
        r = new Random(seed);
        this.width = width;
        this.height = height;
        this.data = new float[width][height];
    }
    
    public float get(int x, int y)
    {
        return data[x][y];
    }
    
    private float[][] randomizeBase()
    {
        // +1 for neighbours
        int w = (int) Math.ceil(width / HIGH_FREQUENCY) + 1; 
        int h = (int) Math.ceil(height / HIGH_FREQUENCY) + 1;
        float[][] base = new float[w][h];
        for(int x = 0; x < w; x++)
        {
            for(int y = 0; y < h; y++)
            {
                base[x][y] = r.nextFloat();
            }
        }
        return base;
    }
    
    private float smooth(float[][] base, float x1, float y1)
    {
        // distance to next exact pixel
        float fractX = x1 - (int) x1;
        float fractY = y1 - (int) y1;

        // neighbour pixel coords
        int x2 = ((int) x1 + 1) % width;
        int y2 = ((int) y1 + 1) % height;
        
        // interpolate over all 4 pixels, strength varies with distance
        float value = 0.0f;
        value += (1 - fractX) * (1 - fractY) * base[(int) x1][(int) y1];
        value += (1 - fractX) * fractY * base[(int) x1][(int) y2];
        value += fractX * (1 - fractY) * base[(int) x2][(int) y1];
        value += fractX * fractY * base[(int) x2][(int) y2];
        return value;
    }
    
    private float turbulence(float[][] base, float x1, float y1)
    {
        float sum = 0.0f;
        sum += smooth(base, x1 / LOW_FREQUENCY, y1 / LOW_FREQUENCY) * LOW_FREQUENCY;
        sum += smooth(base, x1 / HIGH_FREQUENCY, y1 / HIGH_FREQUENCY) * HIGH_FREQUENCY;
        return sum;
    }
    
    private void zoom()
    {
        float[][] base = randomizeBase();
        
        float min = Float.MAX_VALUE;
        float max = Float.MIN_VALUE;
        
        for(int x = 0; x < width; x++)
        {
            for(int y = 0; y < height; y++)
            {
                float f = turbulence(base, x, y);
                data[x][y] = f;
                if(f < min)
                {
                    min = f;
                }
                if(f > max)
                {
                    max = f;
                }
            }
        }
        
        float f = 1.0f / (max - min);
        for(int x = 0; x < width; x++)
        {
            for(int y = 0; y < height; y++)
            {
                data[x][y] = (data[x][y] - min) * f; 
            }
        }
    }
}
