use bevy::{
    math::{cubic_splines::CubicCurve, vec3},
    prelude::*,
    render::{
        mesh::{Indices, PrimitiveTopology},
        render_asset::RenderAssetUsages,
        texture::{ImageAddressMode, ImageLoaderSettings, ImageSampler, ImageSamplerDescriptor},
    },
};
use std::f32::consts::PI;

use rand::{thread_rng, Rng};


#[derive(Resource)]
///  A data structure used to generate a mesh representing the track our racers must follow..
pub struct Track {
    /// The curve that defines the shape of the track.
    pub curve: CubicCurve<Vec3>,
   
    /// The radius of the generated mesh.
    radius: f32,

    /// The number of points along the curve to sample
    ///  to generate rings in the mesh.
    rings: usize,

    /// The index of the vertex containing the start of each ring.
    ring_indices: Vec<u32>,

    /// The number of points in each
    ///  cross-section of the mesh.
    circle_segments: u32,

    /// The generated Vec3s for our vertices.
    vertices: Vec<Vec3>,

    /// The UV texture coordinates at each vertex.
    uvs: Vec<[f32; 2]>,

    /// The vertex indices of the
    ///  triangles making up the mesh.
    vertex_indices: Vec<u32>,
}
impl Track {
    pub fn new(curve: CubicCurve<Vec3>, radius: f32, circle_segments: u32, rings: usize) -> Track {
        let mut track = Track {
            curve: curve,
            radius,
            rings,
            ring_indices: vec![],
            circle_segments,
            vertices: vec![],
            uvs: vec![],
            vertex_indices: vec![],
        };
        let mut forward = track.curve.velocity(0.0).normalize();
        let mut up = Vec3::new(0.0, 1.0, 0.0);
        if up.dot(forward) > 0.9 {
            up = Vec3::new(1.0, 0.0, 0.0);
        }
        let mut right = up.cross(forward);
	    let sz = track.curve.segments().len();

        for t in 0..track.rings {
            track.add_ring(
		sz as f32 * t as f32 / track.rings as f32,
                &mut forward,
                &mut right,
                t as f32,
            );
            if t == 0 || t == track.rings - 1 {
                let center_index = track.vertices.len() as u32;
                track.uvs.push([0.5, t as f32]);
                track.vertices.push(
                    track
                        .curve
                        .position(sz as f32 * t as f32 / track.rings as f32),
                );
                track.add_cap(center_index, track.ring_indices[t]);
            }

            if t > 0 {
                track.add_sides(track.ring_indices[t - 1], track.ring_indices[t]);
            }
        }
        track
    }
    /// Add the cap to the an end of the mesh.
    fn add_cap(&mut self, center_index: u32, ring_index: u32) {
        for i in 0..self.circle_segments - 1 {
            self.vertex_indices.push(center_index);
            if ring_index != 0 {
                self.vertex_indices.push(ring_index + i);
                self.vertex_indices.push(ring_index + i + 1);
            } else {
                self.vertex_indices.push(ring_index + i + 1);
                self.vertex_indices.push(ring_index + i);
            }
        }
        self.vertex_indices.push(center_index);
        if ring_index != 0 {
            self.vertex_indices
                .push(ring_index + self.circle_segments - 1);
            self.vertex_indices.push(ring_index);
        } else {
            self.vertex_indices.push(ring_index);
            self.vertex_indices
                .push(ring_index + self.circle_segments - 1);
        }
    }

    ///  Draw the quads connecting two rings in our mesh
    ///  first ring
    ///  a--b first_segment
    ///  |  |
    ///  |  |
    ///  c--d next segment
    ///     next ring
    fn add_sides(&mut self, first_ring: u32, next_ring: u32) {
        for i in 0..self.circle_segments - 1 {
            let a = first_ring + i;
            let b = next_ring + i;
            let c = first_ring + i + 1;
            let d = next_ring + i + 1;
            self.vertex_indices.extend([c, d, a, a, d, b].into_iter());
        }
        let a = first_ring + self.circle_segments - 1;
        let b = next_ring + self.circle_segments - 1;
        let c = first_ring;
        let d = next_ring;
        self.vertex_indices.extend([c, d, a, a, d, b].into_iter());
    }
    fn add_ring(&mut self, t: f32, forward: &mut Vec3, right: &mut Vec3, uv_y: f32) {
        self.ring_indices.push(self.vertices.len() as u32);
        let center = self.curve.position(t);
        let up = -right.normalize().cross(*forward);
        *forward = self.curve.velocity(t).normalize();
        *right = up.cross(*forward);
        for i in 0..self.circle_segments + 1 {
            let angle = i as f32 * 2.0 * PI / self.circle_segments as f32;
            let x = angle.cos() * self.radius;
            let y = angle.sin() * self.radius;
            self.vertices.push(center + *right * x + up * y);
            self.uvs
                .push([i as f32 / self.circle_segments as f32, uv_y]);
        }
    }
    pub fn mesh(&self) -> Mesh {
        let mut mesh = Mesh::new(
            PrimitiveTopology::TriangleList,
            RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
        )
        .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, self.vertices.clone())
        .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, self.uvs.clone())
        .with_inserted_indices(Indices::U32(self.vertex_indices.clone()));
        mesh.compute_normals();
        mesh
    }
}