use std::ops::{Deref, DerefMut};

use bytemuck::{Pod, Zeroable};
use glow::{HasContext, NativeBuffer, NativeVertexArray};

use crate::components::{fetchcode, CodeComponent, CodeType, Program, Shader, Snippet};
use crate::errors::*;
use crate::graphics::transforms::viewport::Viewport;
use crate::graphics::transforms::Transform;
use crate::graphics::ty::Ty;
use crate::graphics::{AttaWithBuffer, Config, Graphics};

use super::Colletion;

pub struct AggFastPath {
    program: Program,
    buffer: Vec<Point>,
    indice: Vec<[u32; 3]>,

    vao: Option<NativeVertexArray>,
    vbo: Option<NativeBuffer>,
    ebo: Option<NativeBuffer>,
}

impl AggFastPath {
    pub fn new() -> Result<Self> {
        let vertex = Shader::new(
            glow::VERTEX_SHADER,
            CodeType::<String>::Path("agg-fast-path.vert".into()),
        )?;

        let fragment = Shader::new(
            glow::FRAGMENT_SHADER,
            CodeType::<String>::Path("agg-fast-path.frag".into()),
        )?;

        let input_snippet = Snippet::new(
            "input",
            CodeType::Code(
                "
            layout(location = 0) in vec3 prev;
            layout(location = 1) in vec3 curr;
            layout(location = 2) in vec3 next;
            layout(location = 3) in float id;

            uniform float linewidth;

            uniform float antialias;

            uniform vec4 color;

        ",
            ),
            false,
            None,
        )?;

        let vertex = vertex.add_snippet_before(input_snippet);

        let program = Program::new(vertex, fragment, None, "330 core");

        // program.set_transform(transform);
        // program.set_viewport(viewport);

        Ok(Self {
            program,
            buffer: Vec::with_capacity(128),
            indice: Vec::with_capacity(128 * 2),
            ebo: None,
            vao: None,
            vbo: None,
        })
    }

    pub fn set_transform<T: Transform>(&mut self, transform: &T) {
        self.program.set_transform(transform);
    }

    pub fn set_viewport(&mut self, viewport: &Viewport) {
        self.program.set_viewport(viewport);
    }

    pub fn set_linecolor(&mut self, gl: &glow::Context, color: [f32; 4]) {
        let loc = self.program.get_uniform_location(gl, "color");
        unsafe {
            gl.uniform_4_f32(loc.as_ref(), color[0], color[1], color[2], color[3]);
        }
    }

    pub fn set_linewidth(&mut self, gl: &glow::Context, linewidth: f32) {
        let loc = self.program.get_uniform_location(gl, "linewidth");
        unsafe {
            gl.uniform_1_f32(loc.as_ref(), linewidth);
        }
    }

    pub fn set_anatialias(&mut self, gl: &glow::Context, antialias: f32) {
        let loc = self.program.get_uniform_location(gl, "antialias");
        unsafe {
            gl.uniform_1_f32(loc.as_ref(), antialias);
        }
    }

    pub fn program(&mut self) -> &mut Program {
        &mut self.program
    }

    pub fn program_ref(&self) -> &Program {
        &self.program
    }

    fn init(&mut self, gl: &glow::Context) {
        self.set_anatialias(gl, 0.005);
        self.set_linecolor(gl, [1.0, 1.0, 1.0, 1.0]);
        self.set_linewidth(gl, 0.02);
    }
}

impl Colletion for AggFastPath {
    type Item = Path;

    fn append(&mut self, item: Self::Item) {
        self.buffer.extend(item.points);
        self.indice.extend(item.ebo);
    }
}

impl Graphics for AggFastPath {
    fn compile(&mut self, gl: &glow::Context) -> Result<()> {
        use bytemuck::cast_slice;
        self.program.compile(gl)?;

        unsafe {
            let vao = gl.create_vertex_array().unwrap();

            gl.bind_vertex_array(Some(vao));
            for p in self.buffer.iter() {
                println!("{}", p);
            }
            println!("{:?}", self.indice);

            let vbo = gl.create_buffer().unwrap();
            gl.bind_buffer(glow::ARRAY_BUFFER, Some(vbo));
            gl.buffer_data_u8_slice(
                glow::ARRAY_BUFFER,
                cast_slice(&self.buffer),
                glow::STATIC_DRAW,
            );

            gl.enable_vertex_attrib_array(0);
            gl.vertex_attrib_pointer_f32(0, 3, glow::FLOAT, false, 40, 0);

            gl.enable_vertex_attrib_array(1);
            gl.vertex_attrib_pointer_f32(1, 3, glow::FLOAT, false, 40, 12);

            gl.enable_vertex_attrib_array(2);
            gl.vertex_attrib_pointer_f32(2, 3, glow::FLOAT, false, 40, 24);

            gl.enable_vertex_attrib_array(3);
            gl.vertex_attrib_pointer_f32(3, 1, glow::FLOAT, false, 40, 36);

            let ebo = gl.create_buffer().unwrap();
            gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(ebo));

            gl.buffer_data_u8_slice(
                glow::ELEMENT_ARRAY_BUFFER,
                cast_slice(&self.indice),
                glow::STATIC_DRAW,
            );

            self.vao = Some(vao);
            self.vbo = Some(vbo);
            self.ebo = Some(ebo);

            gl.bind_vertex_array(None);
        }

        Ok(())
    }

    fn draw(&self, gl: &glow::Context, count: i32) -> Result<()> {
        unsafe {
            gl.clear(glow::COLOR_BUFFER_BIT);
            gl.bind_vertex_array(Some(self.vao.unwrap()));
            gl.draw_elements(
                glow::TRIANGLES,
                (self.indice.len() * 3) as i32,
                glow::UNSIGNED_INT,
                0,
            );
            gl.bind_vertex_array(None);
        }

        Ok(())
    }

    fn destroy(&mut self, gl: &glow::Context) -> Result<()> {
        self.program.destroy(gl);

        unsafe {
            self.ebo.map(|ebo| gl.delete_buffer(ebo));
            self.vao.map(|vao| gl.delete_vertex_array(vao));
            self.vbo.map(|vbo| gl.delete_buffer(vbo));
        }

        Ok(())
    }

    fn program_ref(&self) -> &Program {
        &self.program
    }

    fn program_mut(&mut self) -> &mut Program {
        &mut self.program
    }

    fn set_config(&mut self, gl: &glow::Context, config: Option<&Config>) -> Result<()> {
        Ok(())
    }

    fn mount(&mut self, gl: &glow::Context) -> Result<()> {
        unsafe {
            gl.use_program(self.program.native_program);
            self.init(gl);
        }
        Ok(())
    }

    fn unmount(&mut self, gl: &glow::Context) -> Result<()> {
        unsafe {
            gl.use_program(None);
        }
        Ok(())
    }
}

impl AttaWithBuffer for AggFastPath {
    type Data = ();

    fn bake(&self, data: &Self::Data) -> Result<(Vec<f32>, Option<Vec<u32>>, i32)> {
        Ok((vec![], None, 0))
    }

    fn init(&self, gl: &glow::Context) -> (NativeVertexArray, NativeBuffer, Option<NativeBuffer>) {
        (self.vao.unwrap(), self.vbo.unwrap(), self.ebo)
    }
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
pub struct Point {
    prev: [f32; 3],
    curr: [f32; 3],
    next: [f32; 3],
    id: f32,
}

impl std::fmt::Display for Point {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Point {{ prev: {:?}, curr: {:?}, next: {:?}, id: {} }}",
            self.prev, self.curr, self.next, self.id
        )
    }
}

impl Ty for Point {}

#[derive(Debug)]
pub struct Path {
    points: Vec<Point>,
    ebo: Vec<[u32; 3]>,

    is_closed: bool,
    is_empty: bool,
}

impl Default for Path {
    fn default() -> Self {
        Self {
            points: Vec::with_capacity(500),
            ebo: Vec::with_capacity(500),

            is_closed: false,
            is_empty: true,
        }
    }
}

pub struct PathBuilder {
    is_closed: bool,
}

impl Default for PathBuilder {
    fn default() -> Self {
        Self { is_closed: false }
    }
}

impl PathBuilder {
    pub fn is_closed(&mut self, is_closed: bool) -> &mut Self {
        self.is_closed = is_closed;
        self
    }

    pub fn build(&self) -> Path {
        Path {
            points: Vec::with_capacity(500),
            is_closed: self.is_closed,
            ebo: Vec::with_capacity(500),

            is_empty: true,
        }
    }
}

impl Path {
    pub fn new(is_closed: bool) -> Self {
        Self {
            points: Vec::with_capacity(500),
            ebo: Vec::with_capacity(500),
            is_closed,
            is_empty: true,
        }
    }

    pub fn builder() -> PathBuilder {
        PathBuilder::default()
    }

    pub fn push(&mut self, point: [f32; 3]) {
        if self.is_empty {
            self.points.push(Point {
                prev: point,
                curr: point,
                next: point,
                id: 1.0,
            });

            self.points.push(Point {
                prev: point,
                curr: point,
                next: point,
                id: -1.0,
            });

            self.is_empty = false;
        } else {
            let len = self.points.len();
            let prev = self.points[len - 1].curr;
            let curr = point;
            let next = point;
            self.points.push(Point {
                prev,
                curr,
                next,
                id: 1.0,
            });
            self.points.push(Point {
                prev,
                curr,
                next,
                id: -1.0,
            });

            self.points[len - 1].next = curr;
            self.points[len - 2].next = curr;
        }
    }

    pub fn finish(&mut self) {
        if self.is_closed {
            let len = self.points.len();
            let curr = self.points.first().unwrap().curr;
            let prev = self.points[len - 1].curr;
            let next = self.points[1].next;
            self.points.push(Point {
                prev,
                curr,
                next,
                id: 1.0,
            });
            self.points.push(Point {
                prev,
                curr,
                next,
                id: -1.0,
            });
            self.points[len - 1].next = curr;
            self.points[len - 2].next = curr;
        }
        for s in 0..(self.points.len() / 2) - 1 {
            let s = s as u32;
            self.ebo.push([s * 2, s * 2 + 1, (s + 1) * 2 + 1]);
            self.ebo.push([s * 2, (s + 1) * 2 + 1, (s + 1) * 2]);
        }
    }

    pub fn len(&self) -> usize {
        self.points.len()
    }
}

impl Deref for Path {
    type Target = [u8];

    fn deref(&self) -> &Self::Target {
        use bytemuck::cast_slice;
        cast_slice(&self.points)
    }
}

impl DerefMut for Path {
    fn deref_mut(&mut self) -> &mut Self::Target {
        use bytemuck::cast_slice_mut;
        cast_slice_mut(&mut self.points)
    }
}

mod test {

    use crate::graphics::transforms::{polar::Polar, position::Position, trackball::Trackball};

    use super::*;

    #[test]
    fn test_path() {
        let mut path = Path::builder().is_closed(false).build();

        path.push([9.0, 9.0, 9.0]);

        path.push([9.0, 19.0, 9.0]);

        path.finish();

        println!("{:?}, len: {}", path, path.len());
    }

    #[test]
    fn test_agg() {
        // let viewport = Trackball::new().unwrap();
        // let trans = Polar::new().unwrap();
        // let position = Position::new().unwrap();

        // let transform = viewport.chain(trans.chain(position));

        // let viewport = &Viewport::new().unwrap();

        // let agg_path = AggFastPath::new(&transform, &viewport).unwrap();
    }
}
