#![allow(dead_code)]
use crate::GL::{gl, mesh::Screen, *};
use crate::{glsl::*, lib::*};

pub struct SdfGenerator {
	dst_t: Shader,
	dt_h: Shader,
	render: Shader,
	sampl: Sampler,
}
impl SdfGenerator {
	pub fn new(sampl: &[(GLenum, GLenum)]) -> Self {
		let dst_t = Shader::pure([vs_mesh__2d_screen, ps_sdf__distance_transform_v]);
		let dt_h = Shader::pure([vs_mesh__2d_screen, ps_sdf__distance_transform]);
		let render = Shader::pure([vs_mesh__2d_screen, ps_mesh__2d_screen]);
		let sampl = sampl.iter().chain(&[(gl::TEXTURE_MIN_FILTER, gl::NEAREST)]).copied().collect_vec().into();
		Self { dst_t, dt_h, render, sampl }
	}
	pub fn generate<S: TexSize, F: TexFmt, FROM: TexSize>(&mut self, tex: &Tex2d<S, F>, scale: i32, thickness: i32) -> Tex2d<RED, f16> {
		ASSERT!(FROM::SIZE <= S::SIZE, "Wrong sdf source channel");
		let thickness = thickness * scale;
		let TexParam { w, h, .. } = tex.param;
		GLSave!(BLEND, DEPTH_TEST);
		GLDisable!(BLEND, DEPTH_TEST);
		let Self { dst_t, dt_h, ref sampl, .. } = self;
		let mut surf_out = Fbo::<RED, f16>::new((w, h));
		let mut surf_in = Fbo::<RED, f16>::new((w, h));
		{
			let t = tex.Bind(sampl);
			let s = Uniforms!(dst_t, ("tex", t), ("iChannel", FROM::SIZE), ("iThickness", thickness));

			let s = Uniform!(s, ("iSide", 1.));
			surf_out.bind();
			Screen::Draw();

			let _ = Uniform!(s, ("iSide", -1.));
			surf_in.bind();
			Screen::Draw();
		}
		let mut out = Fbo::<RED, f16>::new((w, h));
		{
			let to = surf_out.tex.Bind(sampl);
			let ti = surf_in.tex.Bind(sampl);
			let _ = Uniforms!(dt_h, ("tex_o", to), ("tex_i", ti), ("iThickness", thickness));
			out.bind();
			Screen::Draw();
		}
		GLRestore!(BLEND, DEPTH_TEST);

		out.tex
	}
}
impl Default for SdfGenerator {
	fn default() -> Self {
		Self::new(&[(gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE), (gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE)])
	}
}

SHADER!(
	ps_sdf__distance_transform_v,
	r"in vec2 glTexUV;
	layout(location = 0) out float glFragColor;
	uniform sampler2D tex;
	uniform int iChannel;
	uniform int iThickness;
	uniform float iSide;

	vec2 iStep = vec2(0, 1) / textureSize(tex, 0);

	float get_tex(in vec2 p) {
		if (1 == iChannel) return texture(tex, glTexUV + p).r;
		if (2 == iChannel) return texture(tex, glTexUV + p).g;
		if (3 == iChannel) return texture(tex, glTexUV + p).b;
		return texture(tex, glTexUV + p).a;
	}

	void main() {
		const float t = iSide * .5;
		for (int _i = 0; _i < iThickness; ++_i) {
			float i = float(_i);
			vec2 o = iStep * i;
			if (iSide * get_tex(o) > t || iSide * get_tex(-o) > t) {
				glFragColor = i / iThickness;
				return;
			}
		}

		glFragColor = 1;
	}"
);

SHADER!(
	ps_sdf__distance_transform,
	r"in vec2 glTexUV;
	layout(location = 0) out float glFragColor;
	uniform sampler2D tex_i, tex_o;
	uniform int iThickness;

	vec2 iStep = vec2(1, 0) / textureSize(tex_i, 0);

	void main() {
		float d_i = texture(tex_i, glTexUV).r;
		float d_o = texture(tex_o, glTexUV).r;

		for (int _i = 1; _i < iThickness; ++_i) {
			float i = float(_i);
			vec2 o = i * iStep;
			vec2 o_p = glTexUV + o;
			vec2 o_m = glTexUV - o;
			i /= iThickness;
			d_o = min(d_o, min(length(vec2(i, texture(tex_o, o_p).r)), length(vec2(i, texture(tex_o, o_m).r))));
			d_i = min(d_i, min(length(vec2(i, texture(tex_i, o_p).r)), length(vec2(i, texture(tex_i, o_m).r))));
		}

		d_o = .5 - d_o * .5;
		d_i = .5 + d_i * .5;

		glFragColor = mix(d_o, d_i, float(d_i > .5));
	}"
);
