use cfg_if::cfg_if;
cfg_if! {
    if #[cfg(any(feature = "visualization", feature = "visualization_wasm"))] {
        use std::f32::consts::PI;
        use std::fmt::Display;
        use std::hash::Hash;

        use crate::bevy::prelude::{Res, Vec2};

        use crate::visualization::wrappers::ActiveState;

        use crate::engine::{
            fields::network::{Edge, Network},
            location::Real2D,
            state::State,
        };

        pub use bevy::prelude::Color;
        use bevy::prelude::{Commands, Component, Query, Transform};
        use bevy_prototype_lyon::draw::{DrawMode, FillMode, StrokeMode};
        use bevy_prototype_lyon::path::ShapePath;
        use bevy_prototype_lyon::prelude::{GeometryBuilder, Path};
        use bevy_prototype_lyon::shapes::Line;

        pub extern crate bevy_prototype_lyon;

        // Allows customization of the arrow geometry used to render edges.
        pub struct ArrowOptions {
            pub length: f32,
            pub angle: f32,
        }

        impl Default for ArrowOptions {
            fn default() -> Self {
                ArrowOptions {
                    length: 10.,
                    angle: PI / 12.,
                }
            }
        }

        // Specifies the type of geometry to use to represent the edge, along with drawing directives if needed.
        pub enum LineType {
            Line,
            // An arrow will be drawn with the arrow head placed on the second point. You can specify the length
            // of the vectors representing the arrow head segments, and the angle in radians.
            Arrow(ArrowOptions),
        }

        // All the data we need to properly visualize an edge.
        #[derive(Component)]
        pub struct EdgeRenderInfo {
            pub line_color: Color,
            pub line_width: f32,
            pub source_loc: Real2D,
            pub target_loc: Real2D,
            pub is_static: bool, // If true, render() won't loop on this edge
        }

        #[derive(Component)]
        pub struct EdgeRender(u32, u32, Real2D, Real2D);

        /// Allows rendering the edges of a graph as customizable lines through the Bevy Canvas plugin.
        pub trait NetworkRender<O: Hash + Eq + Clone + Display, L: Clone + Hash + Display, S: State> {
            // Specify how to fetch a reference to the network from the state.
            fn get_network(state: &S) -> &Network<O, L>;

            // Called for each edge to let the user specify how it should be rendered
            fn get_edge_info(edge: &Edge<L>, network: &Network<O, L>) -> EdgeRenderInfo;

            fn get_loc(network: &Network<O, L>, node: u32) -> Real2D;

            fn init_network_graphics(state: &S, commands: &mut Commands) {
                let network = Self::get_network(&*state);
                for node_edges in network.edges.values() {
                    for edge in node_edges {
                        let EdgeRenderInfo {
                            source_loc,
                            target_loc,
                            line_color,
                            line_width,
                            is_static,
                        } = Self::get_edge_info(edge, network);

                        let mut spawn_command = commands.spawn_bundle(GeometryBuilder::build_as(
                            &Line(
                                Vec2::new(source_loc.x, source_loc.y),
                                Vec2::new(target_loc.x, target_loc.y),
                            ),
                            DrawMode::Outlined {
                                fill_mode: FillMode::color(Color::BLACK), // ignored
                                outline_mode: StrokeMode::new(line_color, line_width),
                            },
                            Transform::default(),
                        ));
                        if !is_static {
                            spawn_command.insert(EdgeRender(edge.u, edge.v, source_loc, target_loc));
                        }
                    }
                }
            }

            /// If the nodes connected by the edge have moved, we regenerate the path mesh related to the edge.
            fn render(state_wrapper: Res<ActiveState<S>>, mut query: Query<(&mut Path, &EdgeRender)>) {
                let state = state_wrapper.0.lock().expect("error on lock");
                let network = Self::get_network(&*state);
                for (mut path, edge_render) in query.iter_mut() {
                    let source_loc = Self::get_loc(network, edge_render.0);
                    let target_loc = Self::get_loc(network, edge_render.1);
                    if source_loc != edge_render.2 || target_loc != edge_render.3 {
                        *path = ShapePath::build_as(&Line(
                            Vec2::new(source_loc.x, source_loc.y),
                            Vec2::new(target_loc.x, target_loc.y),
                        ));
                    }
                }
            }
        }

    }
}
