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

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

use crate::components::{fetchcode, CodeComponent, CodeType, Program, Shader, Snippet};
use crate::errors::*;
use crate::graphics::ty::Ty;
use crate::graphics::Graphics;

use super::{AttaWithBuffer, Config};

pub struct Hello {
    program: Program,
    buffer: Vec<Point>,

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

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

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

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

        println!("Vertex:\n{}", program.vertex());
        println!("Fragment:\n{}", program.fragment());

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

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

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

impl Graphics for Hello {
    fn compile(&mut self, gl: &glow::Context) -> Result<()> {
        self.program.compile(gl)?;
        unsafe {
            let vertex_array = gl
                .create_vertex_array()
                .expect("Cannot create vertex array");

            let buffer: Vec<[f32; 3]> = vec![
                [-0.5, -0.5, 0.0],
                [-0.5, 0.5, 0.0],
                [0.5, 0.5, 0.0],
                [0.5, -0.5, 0.0],
            ];

            gl.bind_vertex_array(Some(vertex_array));

            let vbo = gl.create_buffer().unwrap();

            gl.bind_buffer(glow::ARRAY_BUFFER, Some(vbo));
            gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, cast_slice(&buffer), glow::STATIC_DRAW);
            gl.enable_vertex_attrib_array(0);
            gl.vertex_attrib_pointer_f32(0, 3, glow::FLOAT, false, 12, 0);

            let ebo = gl.create_buffer().unwrap();
            // let e: [[u32; 3]; 2] = [[0, 1, 3], [0, 2, 3]];
            let e: [[u32; 3]; 2] = [[0, 1, 2], [2, 3, 0]];
            gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(ebo));
            gl.buffer_data_u8_slice(
                glow::ELEMENT_ARRAY_BUFFER,
                cast_slice(&e),
                glow::STATIC_DRAW,
            );

            self.vao = Some(vertex_array);
            self.vbo = Some(vbo);
        }

        Ok(())
    }

    fn draw(&self, gl: &glow::Context, count: i32) -> Result<()> {
        unsafe {
            gl.clear_color(0.05, 0.05, 0.1, 1.0);
            gl.clear(glow::COLOR_BUFFER_BIT);
            gl.bind_vertex_array(self.vao);

            gl.draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_INT, 0);
        }

        Ok(())
    }

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

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

        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);
        }

        Ok(())
    }

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

impl AttaWithBuffer for Hello {
    type Data = ();

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

    fn init(&self, gl: &glow::Context) -> (NativeVertexArray, NativeBuffer, Option<NativeBuffer>) {
        panic!("Not implemented");
    }
}

#[repr(C)]
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
pub struct Point {
    prev: [f32; 3],
    curr: [f32; 3],
    next: [f32; 3],
}

impl Ty for Point {}

#[derive(Debug)]
pub struct Path {
    points: Vec<Point>,
    is_closed: bool,
    is_empty: bool,
}

impl Default for Path {
    fn default() -> Self {
        Self {
            points: 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,
            is_empty: true,
        }
    }
}

impl Path {
    pub fn new(is_closed: bool) -> Self {
        Self {
            points: 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,
            });

            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 });
            self.points[len - 1].next = curr;

            if len == 2 {
                self.points[0].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 });
            self.points[len - 1].next = curr;
        }
    }

    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();
    }
}
