use tracing::{debug, warn};
use crate::map::Tileset;

pub struct MapRender {
    program: glow::Program,
    vertex_buffer: glow::Buffer,
}

#[allow(unsafe_code)]
impl MapRender {
    pub fn new(gl: &glow::Context) -> Option<Self> {
        use glow::HasContext as _;

        let shader_version = egui_glow::ShaderVersion::get(gl);

        unsafe {
            let program = gl.create_program().expect("Cannot create program");

            if !shader_version.is_new_shader_interface() {
                warn!(
                    "Custom 3D painting hasn't been ported to {:?}",
                    shader_version
                );
                return None;
            }

            let (vertex_shader_source, fragment_shader_source) = (
                r#"
                    layout(location = 0) in vec2 pos;
                    const vec4 colors[6] = vec4[6](
                        vec4(1.0, 0.0, 0.0, 1.0),
                        vec4(0.0, 1.0, 0.0, 1.0),
                        vec4(0.0, 0.0, 1.0, 1.0),
                        vec4(1.0, 0.0, 0.0, 1.0),
                        vec4(0.0, 1.0, 0.0, 1.0),
                        vec4(0.0, 0.0, 1.0, 1.0)
                    );
                    out vec4 v_color;
                    void main() {
                        v_color = colors[gl_VertexID];
                        gl_Position = vec4(pos, 0.0, 1.0);
                    }
                "#,
                r#"
                    precision mediump float;
                    in vec4 v_color;
                    out vec4 out_color;
                    void main() {
                        out_color = v_color;
                    }
                "#,
            );

            let shader_sources = [
                (glow::VERTEX_SHADER, vertex_shader_source),
                (glow::FRAGMENT_SHADER, fragment_shader_source),
            ];

            let shaders: Vec<_> = shader_sources
                .iter()
                .map(|(shader_type, shader_source)| {
                    let shader = gl
                        .create_shader(*shader_type)
                        .expect("Cannot create shader");
                    gl.shader_source(
                        shader,
                        &format!(
                            "{}\n{}",
                            shader_version.version_declaration(),
                            shader_source
                        ),
                    );
                    gl.compile_shader(shader);
                    assert!(
                        gl.get_shader_compile_status(shader),
                        "Failed to compile custom_3d_glow {shader_type} ({} {}): {}",
                        glow::VERTEX_SHADER,
                        glow::FRAGMENT_SHADER,
                        gl.get_shader_info_log(shader)
                    );

                    gl.attach_shader(program, shader);
                    shader
                })
                .collect();

            gl.link_program(program);
            assert!(
                gl.get_program_link_status(program),
                "{}",
                gl.get_program_info_log(program)
            );

            for shader in shaders {
                gl.detach_shader(program, shader);
                gl.delete_shader(shader);
            }

            let vertex_buffer = gl.create_buffer().expect("Cannot create vertex buffer");
            gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer));
            gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, (2 * size_of::<f32>()) as i32, 0);
            gl.enable_vertex_attrib_array(0);

            Some(Self {
                program,
                vertex_buffer
            })
        }
    }

    pub fn destroy(&self, gl: &glow::Context) {
        use glow::HasContext as _;
        unsafe {
            gl.delete_program(self.program);
        }
    }

    pub fn paint(&self, gl: &glow::Context, tileset: Tileset) {
        use glow::HasContext as _;
        unsafe {
            gl.use_program(Some(self.program));



            let tiles = tileset.tiles.lock().unwrap();
            for tile in tiles.iter() {
                let mut data = vec![];
                // A-B
                // | |
                // C-D
                // ABC
                // BDC
                // a: (sp_xclip, sp_yclip)
                // b: (ep_xclip, sp_yclip)
                // c: (sp_xclip, ep_yclip)
                // d: (ep_xclip, ep_yclip)
                // sp_xclip sp_yclip ep_xclip sp_yclip sp_xclip ep_yclip // ABC
                // ep_xclip sp_yclip ep_xclip ep_yclip sp_xclip ep_yclip // BDC
                debug!("A {:?} - B {:?}", (tile.sp_xclip, tile.sp_yclip), (tile.ep_xclip, tile.sp_yclip));
                debug!("C {:?} - D {:?}", (tile.sp_xclip, tile.ep_yclip), (tile.ep_xclip, tile.ep_yclip));
                //let raw_data = [tile.sp_xclip, tile.sp_yclip, tile.ep_xclip, tile.sp_yclip, tile.sp_xclip, tile.ep_yclip, tile.ep_xclip, tile.sp_yclip, tile.ep_xclip, tile.ep_yclip, tile.sp_xclip, tile.ep_yclip];

                let raw_data = [1.0f32, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0];

                for v in &raw_data {
                    data.extend(v.to_be_bytes());
                }

                gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, &data, glow::DYNAMIC_DRAW);
                gl.draw_arrays(glow::TRIANGLES, 0, 3);
            }


        }
    }
}