use crate::render::cms::CMS;
use crate::{coords::Range, render::Render};
use femtovg::Paint;
use femtovg::{renderer::OpenGl, Canvas, ImageId};
use ndarray::AssignElem;
use std::{
    cell::{Ref, RefCell},
    fmt::Debug,
    future::Future,
    pin::Pin,
    sync::{Arc, Mutex},
};

type PrepareFunc = Box<
    dyn FnOnce(
            Box<dyn LayerImpl>,
            &mut Canvas<OpenGl>,
            CMS,
        ) -> Pin<Box<dyn Future<Output = Target>>>
        + Sync
        + Send,
>;
type DrawFunc = Box<dyn Fn(&Layer, Render, (f32, f32)) + Send + Sync>;
type LayerImplSync = Box<dyn LayerImpl + Send + Sync>;

#[derive(Clone)]
pub struct Layer {
    pub visiable: bool,
    pub name: String,
    snapshot: Option<gtk::gdk::Texture>,
    target: RefCell<Option<Target>>,
    prepare: Arc<Option<PrepareFunc>>,
    imp: RefCell<Option<Arc<LayerImplSync>>>,
    draw: Arc<DrawFunc>,
}

impl Debug for Layer {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Layer")
            .field("visiable", &self.visiable)
            .field("target", &self.target)
            .field("imp", &self.imp)
            .finish()
    }
}

pub trait LayerImpl: Debug {
    fn draw(&self, canvas: &mut Canvas<OpenGl>, cms: &CMS) -> Option<Target>;
}

impl Layer {
    pub fn new<
        FU: Future<Output = Target> + Send + Sync + 'static,
        F: 'static + Fn(&Self, Render, (f32, f32)) + Send + Sync,
        PREPARE: FnOnce(Box<dyn LayerImpl>, &mut Canvas<OpenGl>, CMS) -> FU + Send + Sync + 'static,
        IMP: LayerImpl + Sync + Send + 'static,
    >(
        visiable: bool,
        draw: F,
        layer_name: String,
        prepare: Option<PREPARE>,
        imp: Option<IMP>,
    ) -> Self {
        Layer {
            visiable,
            target: RefCell::new(None),
            name: layer_name,
            snapshot: None,
            // prepare: Arc::new(prepare),
            prepare: Arc::new(prepare.map(|p| {
                Box::new(
                    move |layer: Arc<Layer>, render: Render| -> Pin<Box<dyn Future<Output = ()>>> {
                        Box::pin(p(layer, render))
                    },
                ) as PrepareFunc
            })),
            draw: Arc::new(Box::new(draw)),
            imp: RefCell::new(
                imp.map(|x| Arc::new(Box::new(x) as Box<dyn LayerImpl + Send + Sync>)),
            ),
        }
    }

    pub fn draw(&self, render: &Render, window_size: (f32, f32)) {
        if self.visiable {
            let drawer = &self.draw;
            drawer(self, render.clone(), window_size);
        }
    }

    pub fn get_prepare(&self) -> Option<&PrepareFunc> {
        self.prepare.as_ref().as_ref()
    }

    pub fn set_render_target(&self, target: Target) {
        *self.target.borrow_mut() = Some(target);
    }

    pub fn render_target(&self) -> Option<Target> {
        self.target.borrow().clone()
    }

    pub fn get_imp(&self) -> Ref<Option<Arc<LayerImplSync>>> {
        let im = self.imp.borrow();
        im
    }
}

#[derive(Clone, Copy, Debug)]
pub struct Target {
    pub target: ImageId,
    width: f32,
    height: f32,
    bounds: (Range, Range),
}

impl Target {
    pub fn new(target: ImageId, width: f32, height: f32, bounds: (Range, Range)) -> Self {
        Self {
            target,
            width,
            height,
            bounds,
        }
    }

    pub fn size(&self, render: &Render) -> (f32, f32) {
        let (x, y) = self.bounds;

        let p1 = (x.0, y.0);
        let p2 = (x.1, y.1);

        let (x1, y1) = render.map(p1).unwrap();
        let (x2, y2) = render.map(p2).unwrap();

        ((x2 - x1).abs(), (y2 - y1).abs())
    }

    pub fn origin(&self, render: &Render) -> (f32, f32) {
        let (x, y) = self.bounds;
        let p1 = (x.0, y.1);
        render.map(p1).unwrap()
    }
}
