#import bevy_sprite::mesh2d_view_bindings globals

struct PlanetMaterial {
    pixels: f32,
    rotation: f32,
    light_origin: vec2<f32>,
    time_speed: f32,
    dither_size: f32,
    light_border_1: f32,
    light_border_2: f32,
    river_cutoff: f32,
    col1: vec4<f32>,
    col2: vec4<f32>,
    col3: vec4<f32>,
    col4: vec4<f32>,
    river_col: vec4<f32>,
    river_col_dark: vec4<f32>,
    size: f32,
    octaves: i32,
	seed: f32,

	cloud_octaves: i32,
	cloud_rotation: f32,
	cloud_curve: f32,
	cloud_cover: f32,
	cloud_light_border_1: f32,
	cloud_light_border_2: f32,
	base_color: vec4<f32>,
	outline_color: vec4<f32>,
	shadow_base_color: vec4<f32>,
	shadow_outline_color: vec4<f32>,
};

const stretch: f32 = 2.0;

@group(1) @binding(0) 
var<uniform> material: PlanetMaterial;

fn modulo2(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> {
    return a - b * floor(a / b);
}

fn rand(coord: vec2<f32>) -> f32 {
    var coord = coord;
	coord = modulo2(coord, vec2(2.0,1.0)*round(material.size));

    return fract(sin(dot(coord.xy, vec2(12.9898, 78.233))) * (15.5453 + material.seed));
}

fn noise(coord: vec2<f32>) -> f32 {
    var i = floor(coord);
    var f = fract(coord);

    var a = rand(i);
    var b = rand(i + vec2(1.0, 0.0));
    var c = rand(i + vec2(0.0, 1.0));
    var d = rand(i + vec2(1.0, 1.0));

    var cubic = f * f * (3.0 - 2.0 * f);

    return mix(a, b, cubic.x) + (c - a) * cubic.y * (1.0 - cubic.x) + (d - b) * cubic.x * cubic.y;
}

fn fbm(coord: vec2<f32>, octaves: i32) -> f32 {
    var coord = coord;
    var value = 0.0;
    var scale = 0.5;

    for (var i = 0; i < octaves ; i++) {
        value += noise(coord) * scale;
        coord *= 2.0;
        scale *= 0.5;
    }
    return value;
}

fn modulo(a: f32, b: f32) -> f32 {
    return a - b * floor(a / b);
}

fn dither(uv1: vec2<f32>, uv2: vec2<f32>) -> bool {
    return modulo(uv1.y + uv2.x, 2.0 / material.pixels) <= 1.0 / material.pixels;
}


fn spherify(uv: vec2<f32>) -> vec2<f32> {
	var centered = uv * 2.0 - vec2(1.0);
	var z = sqrt(1.0 - dot(centered.xy, centered.xy));
	var sphere = centered/(z + 1.0);
	
	return sphere * 0.5+vec2<f32>(0.5);
}

fn rotate(v: vec2<f32>, angle: f32) -> vec2<f32> {
    var v = v;
    v -= vec2<f32>(0.5);
    v *= mat2x2<f32>(vec2<f32>(cos(angle), -sin(angle)), vec2<f32>(sin(angle), cos(angle)));
    v += vec2<f32>(0.5);
    return v;
}

fn circleNoise(uv: vec2<f32>) -> f32 {
	var uv = uv;

	var uv_y = floor(uv.y);
	uv.x += uv_y * .31;
	var f = fract(uv);
	var h = rand(vec2(floor(uv.x), floor(uv_y)));
	var m = (length(f - vec2<f32>(0.25 + (h * 0.5))));
	var r = h * 0.25;
	return smoothstep(0.0, r, m * 0.75);
}

#import bevy_pbr::mesh_vertex_output MeshVertexOutput

fn terrain(
    in: MeshVertexOutput
) -> vec4<f32> {
	// pixelize uv
	var uv:vec2<f32> = floor(in.uv*material.pixels)/material.pixels;
	
	var dith = dither(uv, in.uv);
	// stepping over 0.5 instead of 0.49999 makes some pixels a little buggy
	var a = step(length(uv-vec2(0.5)), 0.49999);
	
	// map to sphere
	uv = spherify(uv);
	var d_light = distance(uv , material.light_origin);
	
	// give planet a tilt
	uv = rotate(uv, material.rotation);
	
	// some scrolling noise for landmasses
	var base_fbm_uv = (uv)*material.size+vec2<f32>(globals.time*material.time_speed,0.0);
	
	// use multiple fbm's at different places so we can determine what color land gets
	var fbm1 = fbm(base_fbm_uv, material.octaves);
	var fbm2 = fbm(base_fbm_uv - material.light_origin*fbm1, material.octaves);
	var fbm3 = fbm(base_fbm_uv - material.light_origin*1.5*fbm1, material.octaves);
	var fbm4 = fbm(base_fbm_uv - material.light_origin*2.0*fbm1, material.octaves);
	
	var river_fbm = fbm(base_fbm_uv+fbm1*6.0, material.octaves);
	river_fbm = step(material.river_cutoff, river_fbm);
	
	// size of edge in which colors should be dithered
	var dither_border = (1.0/material.pixels)*material.dither_size;
	// lots of magic numbers here
	// you can mess with them, it changes the color distribution
	if (d_light < material.light_border_1) {
		fbm4 *= 0.9;
	}
	if (d_light > material.light_border_1) {
		fbm2 *= 1.05;
		fbm3 *= 1.05;
		fbm4 *= 1.05;
	} 
	if (d_light > material.light_border_2) {
		fbm2 *= 1.3;
		fbm3 *= 1.4;
		fbm4 *= 1.8;
		
		if (d_light < material.light_border_2 + dither_border) {
            #ifdef SHOULD_DITHER
            if (dith) {
				fbm4 *= 0.5;
            }
            #else
				fbm4 *= 0.5;
            #endif
		}
		
	} 
	
	// increase contrast on d_light
	d_light = pow(d_light, 2.0)*0.4;
	var col = material.col4;
	if (fbm4 + d_light < fbm1*1.5) {
		col = material.col3;
	}
	if (fbm3 + d_light < fbm1*1.0) {
		col = material.col2;
	}
	if (fbm2 + d_light < fbm1) {
		col = material.col1;
	}
	if (river_fbm < fbm1*0.5) {
		col = material.river_col_dark;
		if (fbm4 + d_light < fbm1*1.5) {
			col = material.river_col;
		}
	}

	return vec4<f32>(col.rgb, a * col.a);
}

fn cloud_alpha(uv: vec2<f32>) -> f32 {
	var c_noise = 0.0;

	// more iterations for more turbulence
	for (var i = 0; i < 9 ; i++) {
		c_noise += circleNoise((uv * material.size * 0.3) + (f32(i+1)+10.) + (vec2(globals.time*material.time_speed, 0.0)));
	}
	var fbm: f32 = fbm(uv*material.size+c_noise + vec2(globals.time*material.time_speed, 0.0), material.cloud_octaves);

	return fbm;
}



fn clouds(in: MeshVertexOutput) -> vec4<f32> {
	// pixelize uv
	var uv: vec2<f32> = floor(in.uv*material.pixels)/material.pixels;

	// distance to light source
	var d_light: f32 = distance(in.uv, material.light_origin);

	// cut out a circle
	var d_circle: f32 = distance(in.uv, vec2(0.5));
	var a: f32 = step(d_circle, 0.5);

	var d_to_center: f32 = distance(in.uv, vec2(0.5));

	uv = rotate(in.uv, material.cloud_rotation);

	// map to sphere
	uv = spherify(uv);
	// slightly make uv go down on the right, and up in the left
	uv.y += smoothstep(0.0, material.cloud_curve, abs((uv.x - 0.4)));


	var c: f32 = cloud_alpha(uv*vec2(1.0, stretch));

	// assign some colors based on cloud depth & distance from light
	var col = material.base_color;
	if (c < material.cloud_cover + 0.03) {
		col = material.outline_color;
	}
	if (d_light + c*0.2 > material.cloud_light_border_1) {
		col = material.shadow_base_color;

	}
	if (d_light + c*0.2 > material.cloud_light_border_2) {
		col = material.shadow_outline_color;
	}

	c *= step(d_to_center, 0.5);
	return vec4<f32>(col.rgb, step(material.cloud_cover, c) * a * col.a);
}

@fragment
fn fragment(
    in: MeshVertexOutput
) -> @location(0) vec4<f32> {

    var col = vec4<f32>(0.0);
    var ter = terrain(in);
    var cld = clouds(in);

    col = mix(col, ter, ter.a);
	col = mix(col, cld, cld.a);

    return col;
}
