#![cfg(test)]

use crate::{
    graph_material_function,
    material::{common::*, domains::surface::*},
    material_graph,
    math::*,
    mesh::vertex_factory::*,
    render_target::*,
    resources::material_library::*,
};

macro_rules! material_signature {
    (
        mesh( $( $mesh_name:literal ),* )
        render_target( $( $render_target_name:literal ),* )
        $( domain( $domain_name:literal ) )?
        $( middlewares( $( $middleware:literal ),* ) )?
    ) => {
        unsafe {
            #[allow(unused_mut)]
            #[allow(unused_assignments)]
            let mut mesh = vec![];
            $(
                mesh.push(($mesh_name.to_string(), mesh.len()));
            )*
            let render_target = vec![ $( $render_target_name.to_string() ),* ];
            #[allow(unused_mut)]
            #[allow(unused_assignments)]
            let mut domain = None;
            $(
                domain = Some($domain_name.to_string());
            )?
            #[allow(unused_mut)]
            #[allow(unused_assignments)]
            let mut middlewares = core::utils::StringSequence::default();
            $(
                $(
                    middlewares.append($middleware);
                )*
            )?
            MaterialSignature::from_raw(mesh, render_target, domain, middlewares)
        }
    };
}

#[test]
fn test_material_graph() {
    let mut library = MaterialLibrary::default();
    let domain = surface_flat_domain_graph();
    println!("* domain graph: {:#?}", domain);
    library.add_domain("forward".to_owned(), domain.to_owned());
    {
        let graph = default_surface_flat_color_material_graph();
        println!("* material graph color: {:#?}", graph);
        let signature = material_signature! {
            mesh("position", "color")
            render_target("finalColor")
            domain("forward")
        };
        println!("* material graph color signature: {:#?}", signature);
        let baked = graph
            .bake(&signature, Some(&domain), &library, true)
            .unwrap()
            .unwrap();
        println!("* compiled vertex material graph color:\n{}", baked.vertex);
        println!(
            "* compiled fragment material graph color:\n{}",
            baked.fragment
        );
    }
    {
        let graph = default_surface_flat_texture_2d_material_graph();
        println!("* material graph texture: {:#?}", graph);
        let signature = material_signature! {
            mesh("position", "textureCoord", "color")
            render_target("finalColor")
            domain("forward")
        };
        println!("* material graph texture signature: {:#?}", signature);
        let baked = graph
            .bake(&signature, Some(&domain), &library, true)
            .unwrap()
            .unwrap();
        println!(
            "* compiled vertex material graph texture:\n{}",
            baked.vertex
        );
        println!(
            "* compiled fragment material graph texture:\n{}",
            baked.fragment
        );
    }
    {
        let graph = default_surface_flat_text_material_graph();
        println!("* material graph text: {:#?}", graph);
        let signature = material_signature! {
            mesh("position", "textureCoord", "color", "outline", "page", "thickness")
            render_target("finalColor")
            domain("forward")
        };
        println!("* material graph text signature: {:#?}", signature);
        let baked = graph
            .bake(&signature, Some(&domain), &library, true)
            .unwrap()
            .unwrap();
        println!("* compiled vertex material graph text:\n{}", baked.vertex);
        println!(
            "* compiled fragment material graph text:\n{}",
            baked.fragment
        );
    }
    {
        let graph = material_graph! {
            inputs {
                [vertex] inout ScreenPosition: vec3 = {vec3(0.0, 0.0, 0.0)};
            }

            outputs {
                [fragment] inout BaseColor: vec4;
            }

            [(append_vec4, a: [ScreenPosition => vScreenPos], b: {1.0}) -> BaseColor]
        };
        println!("* material graph: {:#?}", graph);
        let signature = material_signature! {
            mesh("position")
            render_target("finalColor")
            domain("forward")
        };
        println!("* material graph signature: {:#?}", signature);
        let baked = graph
            .bake(&signature, Some(&domain), &library, true)
            .unwrap()
            .unwrap();
        println!("* compiled vertex material graph:\n{}", baked.vertex);
        println!("* compiled fragment material graph:\n{}", baked.fragment);
    }
    {
        let graph = material_graph! {
            inputs {
                [fragment] builtin gl_FragCoord: vec4;
                [fragment] uniform ditherImage: sampler2D;
            }

            outputs {
                [fragment] inout BaseColor: vec4;
            }

            [coord = (maskXY_vec4, v: gl_FragCoord)]
            [int_coord = (cast_vec2_ivec2, v: coord)]
            [threshold = (dither_threshold, texture: ditherImage, coord: int_coord)]
            [(fill_vec4, v: threshold) -> BaseColor]
        };
        println!("* material graph: {:#?}", graph);
        let signature = material_signature! {
            mesh("position")
            render_target("finalColor")
            domain("forward")
        };
        println!("* material graph signature: {:#?}", signature);
        let baked = graph
            .bake(&signature, Some(&domain), &library, true)
            .unwrap()
            .unwrap();
        println!("* compiled vertex material graph:\n{}", baked.vertex);
        println!("* compiled fragment material graph:\n{}", baked.fragment);
    }
}

#[test]
fn test_graph_variants() {
    let library = MaterialLibrary::default();
    let graph = material_graph! {
        inputs {}

        outputs {
            [vertex] builtin gl_Position: vec4;
            [fragment] out outputA: vec4;
            [fragment] out outputB: vec4;
        }

        [{vec4(1.0, 0.0, 0.0, 1.0)} -> gl_Position]
        [{vec4(0.0, 1.0, 0.0, 1.0)} -> outputA]
        [{vec4(0.0, 0.0, 1.0, 1.0)} -> outputB]
    };
    println!("* material graph: {:#?}", graph);
    {
        let signature = material_signature! {
            mesh()
            render_target("outputA")
        };
        let baked = graph
            .bake(&signature, None, &library, true)
            .unwrap()
            .unwrap();
        println!("* VS variant A:\n{}", baked.vertex);
        println!("* FS variant A:\n{}", baked.fragment);
    }
    {
        let signature = material_signature! {
            mesh()
            render_target("outputB")
        };
        let baked = graph
            .bake(&signature, None, &library, true)
            .unwrap()
            .unwrap();
        println!("* VS variant B:\n{}", baked.vertex);
        println!("* FS variant B:\n{}", baked.fragment);
    }
}

#[test]
fn test_graph_function() {
    let library = MaterialLibrary::default();
    let add = graph_material_function! {
        fn add(a: vec3, b: vec3) -> vec3 {
            [return (add_vec3, a: a, b: b)]
        }
    };
    add.validate(&library).unwrap();
    println!("* `add` material function: {:#?}", add);
    let times_two = graph_material_function! {
        fn times_two(a: vec3) -> vec3 {
            [return (mul_vec3, a: a, b: {vec3(2.0, 2.0, 2.0)})]
        }
    };
    times_two.validate(&library).unwrap();
    println!("* `times_two` material function: {:#?}", times_two);
}

#[test]
fn test_material_middlewares() {
    let mut library = MaterialLibrary::default();

    let middleware_a = material_graph! {
        inputs {
            [vertex] in position as in_position: vec4 = {vec4(0.0, 0.0, 0.0, 0.0)};
        }

        outputs {
            [vertex] out position as out_position: vec4;
        }

        [(add_vec4, a: in_position, b: {vec4(1.0, 1.0, 1.0, 0.0)}) -> out_position]
    };
    println!("* middleware A graph: {:#?}", middleware_a);
    middleware_a.validate(&library).unwrap();
    library.add_middleware("a".to_owned(), middleware_a);

    let middleware_b = material_graph! {
        inputs {
            [vertex] in position as in_position: vec4 = {vec4(0.0, 0.0, 0.0, 0.0)};
        }

        outputs {
            [vertex] out position as out_position: vec4;
        }

        [(mul_vec4, a: in_position, b: {vec4(2.0, 2.0, 2.0, 0.0)}) -> out_position]
    };
    println!("* middleware B graph: {:#?}", middleware_b);
    middleware_b.validate(&library).unwrap();
    library.add_middleware("b".to_owned(), middleware_b);

    let domain = material_graph! {
        inputs {
            [vertex] in position: vec4 = {vec4(0.0, 0.0, 0.0, 0.0)};
            [vertex] inout Color: vec4 = {vec4(1.0, 1.0, 1.0, 1.0)};
        }

        outputs {
            [vertex] builtin gl_Position: vec4;
            [fragment] out output: vec4;
        }

        [position -> gl_Position]
        [[Color => vColor] -> output]
    };
    println!("* domain graph: {:#?}", domain);

    let graph = material_graph! {
        inputs {
            [vertex] in color: vec4 = {vec4(4.0, 3.0, 2.0, 1.0)};
        }

        outputs {
            [vertex] inout Color: vec4;
        }

        [color -> Color]
    };
    println!("* material graph: {:#?}", graph);

    let signature = material_signature! {
        mesh("position", "color")
        render_target("output")
        middlewares("a", "b")
    };
    let baked = graph
        .bake(&signature, Some(&domain), &library, true)
        .unwrap()
        .unwrap();
    println!("* VS variant A:\n{}", baked.vertex);
    println!("* FS variant A:\n{}", baked.fragment);

    MaterialLibrary::assert_material_compilation(
        &SurfaceVertexAP::vertex_layout().unwrap(),
        RenderTargetDescriptor::Main,
        &surface_flat_domain_graph(),
        &default_surface_flat_color_material_graph(),
    );

    MaterialLibrary::assert_material_compilation(
        &SurfaceVertexSP::vertex_layout().unwrap(),
        RenderTargetDescriptor::Main,
        &surface_flat_domain_graph(),
        &default_surface_flat_color_material_graph(),
    );

    MaterialLibrary::assert_material_compilation(
        &SurfaceVertexDP::vertex_layout().unwrap(),
        RenderTargetDescriptor::Main,
        &surface_flat_domain_graph(),
        &default_surface_flat_color_material_graph(),
    );
}

#[test]
fn test_compound_vertex_type() {
    println!(
        "* SurfaceVertexPT vertex layout: {:#?}",
        SurfaceVertexPT::vertex_layout()
    );
    println!(
        "* SurfaceVertexAPT vertex layout: {:#?}",
        SurfaceVertexAPT::vertex_layout()
    );
    println!(
        "* SurfaceVertexSPT vertex layout: {:#?}",
        SurfaceVertexSPT::vertex_layout()
    );
    println!(
        "* SurfaceVertexASPT vertex layout: {:#?}",
        SurfaceVertexASPT::vertex_layout()
    );
}
