use super::Padding;
use crate::{
    geometry::{
        rect::RectF,
        vector::{vec2f, Vector2F},
    },
    platform::CursorStyle,
    platform::MouseButton,
    scene::{
        CursorRegion, HandlerSet, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseHover,
        MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
    },
    AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, SceneBuilder,
    SizeConstraint, View, ViewContext,
};
use serde_json::json;
use std::{marker::PhantomData, ops::Range};

pub struct MouseEventHandler<Tag: 'static, V: View> {
    child: AnyElement<V>,
    region_id: usize,
    cursor_style: Option<CursorStyle>,
    handlers: HandlerSet,
    hoverable: bool,
    notify_on_hover: bool,
    notify_on_click: bool,
    above: bool,
    padding: Padding,
    _tag: PhantomData<Tag>,
}

/// Element which provides a render_child callback with a MouseState and paints a mouse
/// region under (or above) it for easy mouse event handling.
impl<Tag, V: View> MouseEventHandler<Tag, V> {
    pub fn for_child(child: impl Element<V>, region_id: usize) -> Self {
        Self {
            child: child.into_any(),
            region_id,
            cursor_style: None,
            handlers: Default::default(),
            notify_on_hover: false,
            notify_on_click: false,
            hoverable: false,
            above: false,
            padding: Default::default(),
            _tag: PhantomData,
        }
    }

    pub fn new<E, F>(region_id: usize, cx: &mut ViewContext<V>, render_child: F) -> Self
    where
        E: Element<V>,
        F: FnOnce(&mut MouseState, &mut ViewContext<V>) -> E,
    {
        let mut mouse_state = cx.mouse_state::<Tag>(region_id);
        let child = render_child(&mut mouse_state, cx).into_any();
        let notify_on_hover = mouse_state.accessed_hovered();
        let notify_on_click = mouse_state.accessed_clicked();
        Self {
            child,
            region_id,
            cursor_style: None,
            handlers: Default::default(),
            notify_on_hover,
            notify_on_click,
            hoverable: true,
            above: false,
            padding: Default::default(),
            _tag: PhantomData,
        }
    }

    /// Modifies the MouseEventHandler to render the MouseRegion above the child element. Useful
    /// for drag and drop handling and similar events which should be captured before the child
    /// gets the opportunity
    pub fn above<D, F>(region_id: usize, cx: &mut ViewContext<V>, render_child: F) -> Self
    where
        D: Element<V>,
        F: FnOnce(&mut MouseState, &mut ViewContext<V>) -> D,
    {
        let mut handler = Self::new(region_id, cx, render_child);
        handler.above = true;
        handler
    }

    pub fn with_cursor_style(mut self, cursor: CursorStyle) -> Self {
        self.cursor_style = Some(cursor);
        self
    }

    pub fn capture_all(mut self) -> Self {
        self.handlers = HandlerSet::capture_all();
        self
    }

    pub fn on_move(
        mut self,
        handler: impl Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
    ) -> Self {
        self.handlers = self.handlers.on_move(handler);
        self
    }

    pub fn on_move_out(
        mut self,
        handler: impl Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
    ) -> Self {
        self.handlers = self.handlers.on_move_out(handler);
        self
    }

    pub fn on_down(
        mut self,
        button: MouseButton,
        handler: impl Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
    ) -> Self {
        self.handlers = self.handlers.on_down(button, handler);
        self
    }

    pub fn on_up(
        mut self,
        button: MouseButton,
        handler: impl Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
    ) -> Self {
        self.handlers = self.handlers.on_up(button, handler);
        self
    }

    pub fn on_click(
        mut self,
        button: MouseButton,
        handler: impl Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
    ) -> Self {
        self.handlers = self.handlers.on_click(button, handler);
        self
    }

    pub fn on_down_out(
        mut self,
        button: MouseButton,
        handler: impl Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
    ) -> Self {
        self.handlers = self.handlers.on_down_out(button, handler);
        self
    }

    pub fn on_up_out(
        mut self,
        button: MouseButton,
        handler: impl Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
    ) -> Self {
        self.handlers = self.handlers.on_up_out(button, handler);
        self
    }

    pub fn on_drag(
        mut self,
        button: MouseButton,
        handler: impl Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
    ) -> Self {
        self.handlers = self.handlers.on_drag(button, handler);
        self
    }

    pub fn on_hover(
        mut self,
        handler: impl Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
    ) -> Self {
        self.handlers = self.handlers.on_hover(handler);
        self
    }

    pub fn on_scroll(
        mut self,
        handler: impl Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
    ) -> Self {
        self.handlers = self.handlers.on_scroll(handler);
        self
    }

    pub fn with_hoverable(mut self, is_hoverable: bool) -> Self {
        self.hoverable = is_hoverable;
        self
    }

    pub fn with_padding(mut self, padding: Padding) -> Self {
        self.padding = padding;
        self
    }

    fn hit_bounds(&self, bounds: RectF) -> RectF {
        RectF::from_points(
            bounds.origin() - vec2f(self.padding.left, self.padding.top),
            bounds.lower_right() + vec2f(self.padding.right, self.padding.bottom),
        )
        .round_out()
    }

    fn paint_regions(
        &self,
        scene: &mut SceneBuilder,
        bounds: RectF,
        visible_bounds: RectF,
        cx: &mut ViewContext<V>,
    ) {
        let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
        let hit_bounds = self.hit_bounds(visible_bounds);

        if let Some(style) = self.cursor_style {
            scene.push_cursor_region(CursorRegion {
                bounds: hit_bounds,
                style,
            });
        }
        scene.push_mouse_region(
            MouseRegion::from_handlers::<Tag>(
                cx.view_id(),
                self.region_id,
                hit_bounds,
                self.handlers.clone(),
            )
            .with_hoverable(self.hoverable)
            .with_notify_on_hover(self.notify_on_hover)
            .with_notify_on_click(self.notify_on_click),
        );
    }
}

impl<Tag, V: View> Element<V> for MouseEventHandler<Tag, V> {
    type LayoutState = ();
    type PaintState = ();

    fn layout(
        &mut self,
        constraint: SizeConstraint,
        view: &mut V,
        cx: &mut LayoutContext<V>,
    ) -> (Vector2F, Self::LayoutState) {
        (self.child.layout(constraint, view, cx), ())
    }

    fn paint(
        &mut self,
        scene: &mut SceneBuilder,
        bounds: RectF,
        visible_bounds: RectF,
        _: &mut Self::LayoutState,
        view: &mut V,
        cx: &mut ViewContext<V>,
    ) -> Self::PaintState {
        if self.above {
            self.child
                .paint(scene, bounds.origin(), visible_bounds, view, cx);

            scene.paint_layer(None, |scene| {
                self.paint_regions(scene, bounds, visible_bounds, cx);
            });
        } else {
            self.paint_regions(scene, bounds, visible_bounds, cx);
            self.child
                .paint(scene, bounds.origin(), visible_bounds, view, cx);
        }
    }

    fn rect_for_text_range(
        &self,
        range_utf16: Range<usize>,
        _: RectF,
        _: RectF,
        _: &Self::LayoutState,
        _: &Self::PaintState,
        view: &V,
        cx: &ViewContext<V>,
    ) -> Option<RectF> {
        self.child.rect_for_text_range(range_utf16, view, cx)
    }

    fn debug(
        &self,
        _: RectF,
        _: &Self::LayoutState,
        _: &Self::PaintState,
        view: &V,
        cx: &ViewContext<V>,
    ) -> serde_json::Value {
        json!({
            "type": "MouseEventHandler",
            "child": self.child.debug(view, cx),
        })
    }
}
