use std::{
    fmt::Display,
    ops::{Deref, DerefMut},
};
use crate::{
    canvas::{BorderRadius, BorderWidth, Canvas, Curve, FillRule, Mask, Paint, Stroke},
    layout::{Affine, Point, Rect, Size, Vector},
    text::{FontAttributes, Paragraph, TextAlign, TextWrap},
    view::{ViewId, ViewState},
    window::Window,
};
use super::BaseCx;
pub struct DrawCx<'a, 'b> {
    pub(crate) base: &'a mut BaseCx<'b>,
    pub(crate) view_state: &'a mut ViewState,
    pub(crate) transform: Affine,
    pub(crate) canvas: &'a mut Canvas,
    pub(crate) visible: Rect,
}
impl<'b> Deref for DrawCx<'_, 'b> {
    type Target = BaseCx<'b>;
    fn deref(&self) -> &Self::Target {
        self.base
    }
}
impl DerefMut for DrawCx<'_, '_> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.base
    }
}
impl<'a, 'b> DrawCx<'a, 'b> {
    const EVERYTHING: Rect = Rect::new(Point::all(f32::NEG_INFINITY), Point::all(f32::INFINITY));
    pub fn new(
        base: &'a mut BaseCx<'b>,
        view_state: &'a mut ViewState,
        canvas: &'a mut Canvas,
    ) -> Self {
        let window_size = base.context::<Window>().size;
        let visible = Rect::min_size(Point::ZERO, window_size);
        Self {
            base,
            view_state,
            transform: Affine::IDENTITY,
            canvas,
            visible,
        }
    }
    pub fn child(&mut self) -> DrawCx<'_, 'b> {
        DrawCx {
            base: self.base,
            view_state: self.view_state,
            transform: self.transform,
            canvas: self.canvas,
            visible: self.visible,
        }
    }
    pub fn is_visible(&self, rect: Rect) -> bool {
        self.visible.intersects(rect)
    }
    pub fn transform(&self) -> Affine {
        self.transform
    }
    pub fn size(&self) -> Size {
        self.view_state.size
    }
    pub fn rect(&self) -> Rect {
        Rect::min_size(Point::ZERO, self.size())
    }
    pub fn canvas(&mut self) -> &mut Canvas {
        self.canvas
    }
    pub fn fill_rect(&mut self, rect: Rect, paint: impl Into<Paint>) {
        if !self.is_visible(rect) {
            return;
        }
        self.canvas.rect(rect, paint.into());
    }
    pub fn trigger(&mut self, rect: Rect) {
        if !self.is_visible(rect) {
            return;
        }
        self.canvas.trigger(rect, self.id());
    }
    pub fn fill(&mut self, curve: Curve, fill: FillRule, paint: impl Into<Paint>) {
        if !self.is_visible(curve.bounds()) {
            return;
        }
        self.canvas.fill(curve, fill, paint.into());
    }
    pub fn stroke(&mut self, curve: Curve, stroke: impl Into<Stroke>, paint: impl Into<Paint>) {
        let stroke = stroke.into();
        if !self.is_visible(curve.bounds().expand(stroke.width * 2.0)) {
            return;
        }
        self.canvas.stroke(curve, stroke, paint.into());
    }
    pub fn text(&mut self, text: impl Display, rect: Rect, font: FontAttributes) {
        let mut paragraph = Paragraph::new(1.2, TextAlign::Center, TextWrap::Word);
        paragraph.set_text(text, font);
        self.paragraph(¶graph, rect);
    }
    pub fn paragraph(&mut self, paragraph: &Paragraph, rect: Rect) {
        let lines = self.fonts().layout(paragraph, rect.width());
        let mut bounds: Option<Rect> = None;
        for line in lines.iter() {
            let line_rect = Rect::new(
                Point::new(line.left, line.baseline - line.ascent),
                Point::new(line.left + line.width, line.baseline + line.descent),
            );
            if let Some(ref mut rect) = bounds {
                *rect = rect.union(line_rect);
            } else {
                bounds = Some(line_rect);
            }
        }
        (self.canvas).paragraph(paragraph.clone(), rect, bounds.unwrap_or_default());
    }
    pub fn quad(
        &mut self,
        rect: Rect,
        paint: impl Into<Paint>,
        border_radius: impl Into<BorderRadius>,
        border_width: impl Into<BorderWidth>,
        border_paint: impl Into<Paint>,
    ) {
        let radius = border_radius.into();
        let width = border_width.into();
        let rect = rect.round();
        let mut curve = Curve::new();
        curve.push_rect_with_radius(rect, radius);
        self.fill(curve, FillRule::NonZero, paint);
        let mut curve = Curve::new();
        curve.push_rect_with_borders(rect, radius, width);
        self.fill(curve, FillRule::NonZero, border_paint);
    }
    pub fn draw_canvas(&mut self, canvas: Canvas) {
        self.canvas.draw_canvas(canvas);
    }
    pub fn overlay<T>(&mut self, index: i32, f: impl FnOnce(&mut DrawCx<'_, 'b>) -> T) -> T {
        self.canvas.overlay(index, |canvas| {
            let mut cx = DrawCx {
                base: self.base,
                view_state: self.view_state,
                transform: Affine::IDENTITY,
                canvas,
                visible: Self::EVERYTHING,
            };
            f(&mut cx)
        })
    }
    pub fn layer<T>(
        &mut self,
        transform: Affine,
        mask: Option<Mask>,
        view: Option<ViewId>,
        f: impl FnOnce(&mut DrawCx<'_, 'b>) -> T,
    ) -> T {
        let visible = match self.visible.is_infinite() {
            false => self.visible.transform(transform.inverse()),
            true => self.visible,
        };
        let visible = match mask {
            Some(ref mask) => visible.intersection(mask.curve.bounds()),
            None => visible,
        };
        self.canvas.layer(transform, mask, view, |canvas| {
            let mut cx = DrawCx {
                base: self.base,
                view_state: self.view_state,
                transform: self.transform * transform,
                canvas,
                visible,
            };
            f(&mut cx)
        })
    }
    pub fn hoverable<T>(&mut self, f: impl FnOnce(&mut DrawCx<'_, 'b>) -> T) -> T {
        self.layer(Affine::IDENTITY, None, Some(self.id()), f)
    }
    pub fn transformed<T>(
        &mut self,
        transform: Affine,
        f: impl FnOnce(&mut DrawCx<'_, 'b>) -> T,
    ) -> T {
        let visible = match self.visible.is_infinite() {
            false => self.visible.transform(transform.inverse()),
            true => self.visible,
        };
        self.canvas.layer(transform, None, None, |canvas| {
            let mut cx = DrawCx {
                base: self.base,
                view_state: self.view_state,
                transform: self.transform * transform,
                canvas,
                visible,
            };
            f(&mut cx)
        })
    }
    pub fn translated<T>(
        &mut self,
        translation: Vector,
        f: impl FnOnce(&mut DrawCx<'_, 'b>) -> T,
    ) -> T {
        self.transformed(Affine::translate(translation), f)
    }
    pub fn rotated<T>(&mut self, angle: f32, f: impl FnOnce(&mut DrawCx<'_, 'b>) -> T) -> T {
        self.transformed(Affine::rotate(angle), f)
    }
    pub fn scaled<T>(&mut self, scale: Vector, f: impl FnOnce(&mut DrawCx<'_, 'b>) -> T) -> T {
        self.transformed(Affine::scale(scale), f)
    }
    pub fn masked<T>(
        &mut self,
        mask: impl Into<Mask>,
        f: impl FnOnce(&mut DrawCx<'_, 'b>) -> T,
    ) -> T {
        self.layer(Affine::IDENTITY, Some(mask.into()), None, f)
    }
}