use iced::{
    Element, Event, Length, Padding, Pixels, Rectangle, Size, Vector,
    advanced::{
        Clipboard, Layout, Shell, Widget, layout, mouse, overlay, renderer,
        widget::{Operation, Tree, tree},
    },
    alignment,
    widget::{
        self,
        container::{self, Catalog, Style, StyleFn},
    },
};

pub fn resize_watcher<'a, Message, Theme, Renderer>(
    content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> ResizeWatcher<'a, Message, Theme, Renderer>
where
    Theme: container::Catalog + 'a,
    Renderer: iced::advanced::Renderer,
{
    ResizeWatcher::new(content)
}

pub struct ResizeWatcher<'a, Message, Theme = iced::Theme, Renderer = iced::Renderer>
where
    Theme: container::Catalog + 'a,
    Renderer: iced::advanced::Renderer,
{
    c: container::Container<'a, Message, Theme, Renderer>,
    on_resize: Option<Box<dyn ResizeFn<Message> + 'a>>,
}

pub trait ResizeFn<Message> {
    fn resized(&self, size: Size) -> Message;
}
impl<F: Fn(Size) -> M, M> ResizeFn<M> for F {
    fn resized(&self, size: Size) -> M {
        self(size)
    }
}

impl<'a, Message, Theme, Renderer> ResizeWatcher<'a, Message, Theme, Renderer>
where
    Theme: container::Catalog + 'a,
    Renderer: iced::advanced::Renderer,
{
    pub fn new(content: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self {
        Self {
            c: widget::container(content),
            on_resize: None,
        }
    }

    pub fn on_resize(mut self, f: impl ResizeFn<Message> + 'a) -> Self {
        self.on_resize = Some(Box::new(f));
        self
    }

    /// Sets the [`widget::Id`] of the [`Container`].
    pub fn id(mut self, id: impl Into<widget::Id>) -> Self {
        self.c = self.c.id(id);
        self
    }

    /// Sets the [`Padding`] of the [`Container`].
    pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
        self.c = self.c.padding(padding);
        self
    }

    /// Sets the width of the [`Container`].
    pub fn width(mut self, width: impl Into<Length>) -> Self {
        self.c = self.c.width(width);
        // self.width = width.into();
        self
    }

    /// Sets the height of the [`Container`].
    pub fn height(mut self, height: impl Into<Length>) -> Self {
        self.c = self.c.height(height);
        self
    }

    /// Sets the maximum width of the [`Container`].
    pub fn max_width(mut self, max_width: impl Into<Pixels>) -> Self {
        self.c = self.c.max_width(max_width);
        self
    }

    /// Sets the maximum height of the [`Container`].
    pub fn max_height(mut self, max_height: impl Into<Pixels>) -> Self {
        self.c = self.c.max_height(max_height);
        self
    }

    /// Sets the width of the [`Container`] and centers its contents horizontally.
    pub fn center_x(mut self, width: impl Into<Length>) -> Self {
        self.c = self.c.center_x(width);
        self
    }

    /// Sets the height of the [`Container`] and centers its contents vertically.
    pub fn center_y(mut self, height: impl Into<Length>) -> Self {
        self.c = self.c.center_y(height);
        self
    }

    /// Sets the width and height of the [`Container`] and centers its contents in
    /// both the horizontal and vertical axes.
    ///
    /// This is equivalent to chaining [`center_x`] and [`center_y`].
    ///
    /// [`center_x`]: Self::center_x
    /// [`center_y`]: Self::center_y
    pub fn center(mut self, length: impl Into<Length>) -> Self {
        self.c = self.c.center(length);
        self
    }

    /// Sets the width of the [`Container`] and aligns its contents to the left.
    pub fn align_left(mut self, width: impl Into<Length>) -> Self {
        self.c = self.c.align_left(width);
        self
    }

    /// Sets the width of the [`Container`] and aligns its contents to the right.
    pub fn align_right(mut self, width: impl Into<Length>) -> Self {
        self.c = self.c.align_right(width);
        self
    }

    /// Sets the height of the [`Container`] and aligns its contents to the top.
    pub fn align_top(mut self, height: impl Into<Length>) -> Self {
        self.c = self.c.align_top(height);
        self
    }

    /// Sets the height of the [`Container`] and aligns its contents to the bottom.
    pub fn align_bottom(mut self, height: impl Into<Length>) -> Self {
        self.c = self.c.align_bottom(height);
        self
    }

    /// Sets the content alignment for the horizontal axis of the [`Container`].
    pub fn align_x(mut self, alignment: impl Into<alignment::Horizontal>) -> Self {
        self.c = self.c.align_x(alignment);
        self
    }

    /// Sets the content alignment for the vertical axis of the [`Container`].
    pub fn align_y(mut self, alignment: impl Into<alignment::Vertical>) -> Self {
        self.c = self.c.align_y(alignment);
        self
    }

    /// Sets whether the contents of the [`Container`] should be clipped on
    /// overflow.
    pub fn clip(mut self, clip: bool) -> Self {
        self.c = self.c.clip(clip);
        self
    }

    /// Sets the style of the [`Container`].
    #[must_use]
    pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
    where
        Theme::Class<'a>: From<StyleFn<'a, Theme>>,
    {
        self.c = self.c.style(style);
        self
    }

    /// Sets the style class of the [`Container`].
    #[must_use]
    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
        self.c = self.c.class(class);
        self
    }
}

struct State {
    last_size: Size,
    cur_size: Size,
}

impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
    for ResizeWatcher<'_, Message, Theme, Renderer>
where
    Theme: Catalog,
    Renderer: iced::advanced::Renderer,
{
    fn tag(&self) -> tree::Tag {
        tree::Tag::of::<State>()
    }

    fn state(&self) -> tree::State {
        tree::State::new(State {
            last_size: Size::new(0., 0.),
            cur_size: Size::new(0., 0.),
        })
    }

    fn children(&self) -> Vec<Tree> {
        vec![Tree::new(&self.c as &dyn Widget<_, _, _>)]
    }

    fn diff(&self, tree: &mut Tree) {
        tree.diff_children(&[&self.c as &dyn Widget<_, _, _>]);
    }

    fn size(&self) -> Size<Length> {
        self.c.size()
    }

    fn layout(
        &mut self,
        tree: &mut Tree,
        renderer: &Renderer,
        limits: &layout::Limits,
    ) -> layout::Node {
        let tmp = layout::Node::container(
            self.c.layout(&mut tree.children[0], renderer, limits),
            Padding::new(0.),
        );
        let s: &mut State = tree.state.downcast_mut();
        s.cur_size = tmp.size();
        tmp
    }

    fn operate(
        &mut self,
        tree: &mut Tree,
        layout: Layout<'_>,
        renderer: &Renderer,
        operation: &mut dyn Operation,
    ) {
        operation.container(None, layout.bounds());
        self.c
            .operate(&mut tree.children[0], layout.child(0), renderer, operation);
    }

    fn update(
        &mut self,
        tree: &mut Tree,
        event: &Event,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        renderer: &Renderer,
        clipboard: &mut dyn Clipboard,
        shell: &mut Shell<'_, Message>,
        viewport: &Rectangle,
    ) {
        if let Some(m) = &self.on_resize {
            let s: &mut State = tree.state.downcast_mut();
            if s.cur_size != s.last_size {
                shell.publish(m.resized(s.cur_size));
            }
            s.last_size = s.cur_size;
        }
        self.c.update(
            &mut tree.children[0],
            event,
            layout.child(0),
            cursor,
            renderer,
            clipboard,
            shell,
            viewport,
        );
    }

    fn mouse_interaction(
        &self,
        tree: &Tree,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        viewport: &Rectangle,
        renderer: &Renderer,
    ) -> mouse::Interaction {
        self.c.mouse_interaction(
            &tree.children[0],
            layout.child(0),
            cursor,
            viewport,
            renderer,
        )
    }

    fn draw(
        &self,
        tree: &Tree,
        renderer: &mut Renderer,
        theme: &Theme,
        style: &renderer::Style,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        viewport: &Rectangle,
    ) {
        self.c.draw(
            &tree.children[0],
            renderer,
            theme,
            style,
            layout.child(0),
            cursor,
            viewport,
        );
    }

    fn overlay<'b>(
        &'b mut self,
        tree: &'b mut Tree,
        layout: Layout<'b>,
        renderer: &Renderer,
        viewport: &Rectangle,
        translation: Vector,
    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
        self.c.overlay(
            &mut tree.children[0],
            layout.child(0),
            renderer,
            viewport,
            translation,
        )
    }
}

impl<'a, Message, Theme, Renderer> From<ResizeWatcher<'a, Message, Theme, Renderer>>
    for Element<'a, Message, Theme, Renderer>
where
    Message: 'a,
    Theme: Catalog + 'a,
    Renderer: iced::advanced::Renderer + 'a,
{
    fn from(
        container: ResizeWatcher<'a, Message, Theme, Renderer>,
    ) -> Element<'a, Message, Theme, Renderer> {
        Element::new(container)
    }
}
