use blinds::*;
use golem::{
    Attribute, AttributeType, ColorFormat, Context,
    Dimension::{D2, D4},
    ElementBuffer, GeometryMode, GolemError, NumberType, ShaderDescription, ShaderProgram, Surface,
    Texture, Uniform, UniformType, UniformValue, VertexBuffer,
};

async fn app(
    window: Window,
    ctx: golem::glow::Context,
    mut events: EventStream,
) -> Result<(), GolemError> {
    let ctx = &Context::from_glow(ctx)?;

    // Step 1: Draw a triangle to the surface
    #[rustfmt::skip]
    let vertices = [
        // Position         Color
        -0.5, -0.5,         1.0, 0.0, 0.0, 1.0,
        0.5, -0.5,          0.0, 1.0, 0.0, 1.0,
        0.0, 0.5,           0.0, 0.0, 1.0, 1.0
    ];
    let indices = [0, 1, 2];

    let mut shader = ShaderProgram::new(
        ctx,
        ShaderDescription {
            vertex_input: &[
                Attribute::new("vert_position", AttributeType::Vector(D2)),
                Attribute::new("vert_color", AttributeType::Vector(D4)),
            ],
            fragment_input: &[Attribute::new("frag_color", AttributeType::Vector(D4))],
            uniforms: &[],
            vertex_shader: r#" void main() {
            gl_Position = vec4(vert_position, 0, 1);
            frag_color = vert_color;
        }"#,
            fragment_shader: r#" void main() {
            gl_FragColor = frag_color;
        }"#,
        },
    )?;

    let mut vb = VertexBuffer::new(ctx)?;
    let mut eb = ElementBuffer::new(ctx)?;
    vb.set_data(&vertices);
    eb.set_data(&indices);
    shader.bind();
    let mut backing_texture = Texture::new(ctx)?;
    backing_texture.set_image(None, 100, 100, ColorFormat::RGBA);
    ctx.set_viewport(0, 0, backing_texture.width(), backing_texture.height());
    let mut surface = Surface::new(ctx, backing_texture)?;

    surface.bind();
    ctx.clear();
    unsafe {
        shader.draw(&vb, &eb, 0..indices.len(), GeometryMode::Triangles)?;
    }
    Surface::unbind(ctx);

    let size = window.size();
    let scale = window.scale_factor();
    ctx.set_viewport(0, 0, (size.x * scale) as u32, (size.y * scale) as u32);

    // Step 2: Draw a few copies of this triangle to the screen
    // Also, for fun, let's rotate them dynamically
    #[rustfmt::skip]
    let vertices = [
        // Position         UV
        -0.2, -0.2,         0.0, 0.0,
        0.2, -0.2,          1.0, 0.0,
        0.2, 0.2,           1.0, 1.0,
        -0.2, 0.2,          0.0, 1.0,
    ];
    let indices = [0, 1, 2, 2, 3, 0];
    let mut shader = ShaderProgram::new(
        ctx,
        ShaderDescription {
            vertex_input: &[
                Attribute::new("vert_position", AttributeType::Vector(D2)),
                Attribute::new("vert_uv", AttributeType::Vector(D2)),
            ],
            fragment_input: &[Attribute::new("frag_uv", AttributeType::Vector(D2))],
            uniforms: &[
                Uniform::new("image", UniformType::Sampler2D),
                Uniform::new("rotate", UniformType::Matrix(D2)),
                Uniform::new("translate", UniformType::Vector(NumberType::Float, D2)),
            ],
            vertex_shader: r#" void main() {
            gl_Position = vec4(translate + (rotate * vert_position), 0, 1);
            frag_uv = vert_uv;
        }"#,
            fragment_shader: r#" void main() {
            gl_FragColor = texture(image, frag_uv);
        }"#,
        },
    )?;
    vb.set_data(&vertices);
    eb.set_data(&indices);
    shader.bind();
    shader.prepare_draw(&vb, &eb)?;
    shader.set_uniform("image", UniformValue::Int(1))?;

    let bind_point = std::num::NonZeroU32::new(1).unwrap();
    let texture = surface.take_texture().unwrap();
    texture.set_active(bind_point);

    let mut angle = 0f32;
    loop {
        while let Some(_) = events.next_event().await {}
        ctx.clear();
        let draw = |angle: f32, translate| -> Result<(), GolemError> {
            let c = angle.cos();
            let s = angle.sin();
            let rotate = [c, -s, s, c];
            shader.set_uniform("rotate", UniformValue::Matrix2(rotate))?;
            shader.set_uniform("translate", UniformValue::Vector2(translate))?;
            unsafe {
                shader.draw_prepared(0..indices.len(), GeometryMode::Triangles);
            }

            Ok(())
        };
        draw(0.0, [0.0, 0.0])?;
        draw(angle, [-0.5, -0.5])?;
        draw(angle / 2.0, [-0.5, 0.5])?;
        draw(angle / 4.0, [0.5, 0.5])?;
        draw(angle / 8.0, [0.5, -0.5])?;

        angle += 0.005;

        window.present();
    }
}

fn main() {
    run_gl(Settings::default(), |window, gfx, events| async move {
        app(window, gfx, events).await.unwrap()
    });
}
