use bevy::prelude::*;
use bevy_mod_debugdump::{render_graph, render_graph_dot};
use core::{fmt, ops};
use std::{fs, path::Path};

mod basis;
mod plugin;
mod transform_f64;
mod types;
mod vector2;
mod vector2_i32;
mod vector3;
mod vector3_i32;

pub use basis::*;
pub use plugin::*;
pub use transform_f64::*;
pub use types::*;
pub use vector2::*;
pub use vector2_i32::*;
pub use vector3::*;
pub use vector3_i32::*;

pub trait VectorComponent:
    Sized
    + Copy
    + PartialOrd
    + Reflect
    + fmt::Display
    + ops::Add<Output = Self>
    + ops::Neg<Output = Self>
    + ops::Sub<Output = Self>
    + ops::Mul<Output = Self>
    + ops::Div<Output = Self>
    + num_traits::identities::Zero
    + num_traits::identities::One
    + num_traits::sign::Signed
{
    fn min(self, b: Self) -> Self;
    fn max(self, b: Self) -> Self;
}

impl<T> VectorComponent for T
where
    T: Sized
        + Copy
        + PartialOrd
        + Reflect
        + fmt::Display
        + ops::Neg<Output = T>
        + ops::Add<Output = T>
        + ops::Sub<Output = T>
        + ops::Mul<Output = T>
        + ops::Div<Output = T>
        + num_traits::identities::Zero
        + num_traits::identities::One
        + num_traits::sign::Signed,
{
    fn min(self, b: Self) -> Self {
        if self < b { self } else { b }
    }

    fn max(self, b: Self) -> Self {
        if self > b { self } else { b }
    }
}

pub fn lerp<
    T: Copy
        + ops::Add<Output = T>
        + ops::Sub<Output = T>
        + ops::Mul<Output = T>
        + num_traits::identities::One,
>(
    a: T,
    b: T,
    t: T,
) -> T {
    a * (T::one() - t) + b * t
}

pub fn inverse_lerp<T: Copy + ops::Sub<Output = T> + ops::Div<Output = T>>(
    a: T,
    b: T,
    value: T,
) -> T {
    (value - a) / (b - a)
}

pub fn vec2_lerp(a: Vec2, b: Vec2, t: f32) -> Vec2 {
    Vec2 {
        x: lerp(a.x, b.x, t),
        y: lerp(a.y, b.y, t),
    }
}

pub fn vec3_lerp(a: Vec3, b: Vec3, t: f32) -> Vec3 {
    Vec3 {
        x: lerp(a.x, b.x, t),
        y: lerp(a.y, b.y, t),
        z: lerp(a.z, b.z, t),
    }
}

pub fn move_towards_vec2(from: Vec2, to: Vec2, amount: f32) -> Vec2 {
    let diff = to - from;
    let length = diff.length();
    if length <= f32::EPSILON {
        return from;
    }
    from + diff.normalize() * length.min(amount)
}

pub fn move_towards_vec3(from: Vec3, to: Vec3, amount: f32) -> Vec3 {
    let diff = to - from;
    let length = diff.length();
    if length <= f32::EPSILON {
        return from;
    }
    from + diff.normalize() * length.min(amount)
}

/// Get the intersection point (if any) of 2d lines a and b.
/// Lines are defined by 2 points on the line
pub fn vec2_intersection(a1: Vec2, a2: Vec2, b1: Vec2, b2: Vec2) -> Option<Vec2> {
    let a_dir = a2 - a1;
    let b_dir = b2 - b1;
    let determinant = a_dir.perp_dot(b_dir);
    if determinant.abs() <= f32::EPSILON {
        return None;
    }
    Some(
        Vec2 {
            x: a_dir.x * (b1.x * b2.y - b1.y * b2.x) - (a1.x * a2.y - a1.y * a2.x) * b_dir.x,
            y: (a1.x * a2.y - a1.y * a2.x) * -b_dir.y + a_dir.y * (b1.x * b2.y - b1.y * b2.x),
        } / determinant,
    )
}

pub fn loop_value(from: f32, to: f32, value: f32) -> f32 {
    let range = to - from;
    if !range.is_normal() {
        return from;
    }
    value - inverse_lerp(from, to, value).floor() * range
}

pub fn create_app_graphs(app: &mut App) {
    // TODO: Figure out how to list schedules under the new interned ScheduleLabel system
    println!("Writing render graph");
    {
        let path = Path::new("graphs").join("render.dot");
        write_render_graph(app, &path);
        println!("\t- {}", path.to_string_lossy());
    }
}

fn write_render_graph(app: &mut App, path: &Path) {
    fs::create_dir_all(path.parent().unwrap()).unwrap();
    fs::write(
        path,
        render_graph_dot(app, &render_graph::settings::Settings::default()),
    )
    .unwrap();
}
