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

use bytemuck::{Pod, Zeroable};

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::Graphics;

use super::Colletion;

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

impl AggFastPath {
    pub fn new<T: Transform>(transform: &T, viewport: &Viewport) -> 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;

            uniform float linewidth;

            uniform float antialias;

            uniform vec4 color;

        ") ,false, None)?;

        let vertex = vertex.add_snippet_before(input_snippet);

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

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

        println!("{}", program.vertex());

        println!("{}", program.fragment());

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

impl Colletion for AggFastPath {
    type Item = Path;

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

impl Graphics for AggFastPath {

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


    fn draw(&self) -> Result<()> {

        use bytemuck::cast_slice;
        let b:&[u8] = cast_slice(&self.buffer);
        
        Ok(())
    }
}

#[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.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;
        }

        self.points.push(self.points.last().unwrap().clone());
    }

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


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

    }
}