//! A widget for selecting a single value along some linear range.
use conrod_core::{
    builder_methods, image,
    widget::{self, Image, Rectangle},
    widget_ids, Color, Colorable, Positionable, Rect, Sizeable, UiCell, Widget, WidgetCommon,
};

#[derive(Clone, WidgetCommon)]
pub struct ImageFrame {
    #[conrod(common_builder)]
    common: widget::CommonBuilder,
    // Edge images [t, b, r, l]
    edges: [image::Id; 4],
    edge_src_rects: [Option<Rect>; 4],
    // Corner images [tr, tl, br, bl]
    corners: [image::Id; 4],
    corner_src_rects: [Option<Rect>; 4],
    // Center
    center: Center,
    // Thickness of the frame border, determines the size used for the edge and corner images
    border_size: BorderSize,
    // Color to apply to all images making up the image frame
    color: Option<Color>,
    // TODO: would it be useful to have an optional close button be a part of this?
}

#[derive(Clone)]
pub enum Center {
    Plain(Color),
    Image(image::Id, Option<Rect>),
}
impl From<Color> for Center {
    fn from(color: Color) -> Self { Center::Plain(color) }
}
impl From<image::Id> for Center {
    fn from(image: image::Id) -> Self { Center::Image(image, None) }
}
impl From<(image::Id, Rect)> for Center {
    fn from((image, src_rect): (image::Id, Rect)) -> Self { Center::Image(image, Some(src_rect)) }
}

#[derive(Clone)]
pub struct BorderSize {
    top: f64,
    bottom: f64,
    right: f64,
    left: f64,
}
impl From<f64> for BorderSize {
    fn from(thickness: f64) -> Self {
        BorderSize {
            top: thickness,
            bottom: thickness,
            right: thickness,
            left: thickness,
        }
    }
}
impl From<[f64; 2]> for BorderSize {
    fn from([vertical, horizontal]: [f64; 2]) -> Self {
        BorderSize {
            top: horizontal,
            bottom: horizontal,
            right: vertical,
            left: vertical,
        }
    }
}
impl From<[f64; 4]> for BorderSize {
    fn from(vals: [f64; 4]) -> Self {
        BorderSize {
            top: vals[0],
            bottom: vals[1],
            right: vals[2],
            left: vals[3],
        }
    }
}

widget_ids! {
    struct Ids {
        center_plain,
        center_image,
        right,
        top_right,
        top,
        top_left,
        left,
        bottom_left,
        bottom,
        bottom_right,
    }
}

/// Represents the state of the ImageFrame widget.
pub struct State {
    ids: Ids,
}

impl ImageFrame {
    builder_methods! {
        pub edge_src_rects { edge_src_rects = [Option<Rect>; 4] }
        pub corner_src_rects { corner_src_rects = [Option<Rect>; 4] }
    }

    pub fn new(
        edges: [image::Id; 4],
        corners: [image::Id; 4],
        center: impl Into<Center>,
        border_size: impl Into<BorderSize>,
    ) -> Self {
        Self {
            common: widget::CommonBuilder::default(),
            edges,
            edge_src_rects: [None; 4],
            corners,
            corner_src_rects: [None; 4],
            center: center.into(),
            border_size: border_size.into(),
            color: None,
        }
    }
}

impl Widget for ImageFrame {
    type Event = ();
    type State = State;
    type Style = ();

    fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
        State {
            ids: Ids::new(id_gen),
        }
    }

    #[allow(clippy::unused_unit)] // TODO: Pending review in #587
    fn style(&self) -> Self::Style { () }

    /// Update the state of the ImageFrame
    fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
        let widget::UpdateArgs {
            id,
            state,
            rect,
            ui,
            ..
        } = args;

        let (frame_w, frame_h) = rect.w_h();

        let t_height = self.border_size.top.min(frame_h);
        let b_height = self.border_size.bottom.min(frame_h);
        let r_width = self.border_size.right.min(frame_w);
        let l_width = self.border_size.left.min(frame_w);
        let inner_width = (frame_w - r_width - l_width).max(0.0);
        let inner_height = (frame_h - t_height - b_height).max(0.0);

        let r_rect = Rect::from_xy_dim([rect.x() + (inner_width + r_width) / 2.0, rect.y()], [
            r_width,
            inner_height,
        ]);
        let tr_rect = Rect::from_xy_dim(
            [
                rect.x() + (inner_width + r_width) / 2.0,
                rect.y() + (inner_height + t_height) / 2.0,
            ],
            [r_width, t_height],
        );
        let t_rect = Rect::from_xy_dim([rect.x(), rect.y() + (inner_height + t_height) / 2.0], [
            inner_width,
            t_height,
        ]);
        let tl_rect = Rect::from_xy_dim(
            [
                rect.x() - (inner_width + l_width) / 2.0,
                rect.y() + (inner_height + t_height) / 2.0,
            ],
            [l_width, t_height],
        );
        let l_rect = Rect::from_xy_dim([rect.x() - (inner_width + l_width) / 2.0, rect.y()], [
            l_width,
            inner_height,
        ]);
        let bl_rect = Rect::from_xy_dim(
            [
                rect.x() - (inner_width + l_width) / 2.0,
                rect.y() - (inner_height + b_height) / 2.0,
            ],
            [l_width, b_height],
        );
        let b_rect = Rect::from_xy_dim([rect.x(), rect.y() - (inner_height + b_height) / 2.0], [
            inner_width,
            b_height,
        ]);
        let br_rect = Rect::from_xy_dim(
            [
                rect.x() + (inner_width + r_width) / 2.0,
                rect.y() - (inner_height + b_height) / 2.0,
            ],
            [r_width, b_height],
        );

        let maybe_color = self.color;
        let set_image = |image_id, rect: Rect, maybe_src_rect, widget_id, ui: &mut UiCell| {
            Image::new(image_id)
                .xy(rect.xy())
                .wh(rect.dim())
                .parent(id)
                .graphics_for(id)
                .and_then(maybe_src_rect, |w, r| w.source_rectangle(r))
                .color(maybe_color)
                .set(widget_id, ui);
        };
        // Right edge
        set_image(
            self.edges[2],
            r_rect,
            self.edge_src_rects[2],
            state.ids.right,
            ui,
        );
        // Top-right corner
        set_image(
            self.corners[0],
            tr_rect,
            self.corner_src_rects[0],
            state.ids.top_right,
            ui,
        );
        // Top edge
        set_image(
            self.edges[0],
            t_rect,
            self.edge_src_rects[0],
            state.ids.top,
            ui,
        );
        // Top-left corner
        set_image(
            self.corners[1],
            tl_rect,
            self.corner_src_rects[1],
            state.ids.top_left,
            ui,
        );
        // Left edge
        set_image(
            self.edges[3],
            l_rect,
            self.edge_src_rects[3],
            state.ids.left,
            ui,
        );
        // Bottom-left corner
        set_image(
            self.corners[3],
            bl_rect,
            self.corner_src_rects[3],
            state.ids.bottom_left,
            ui,
        );
        // Bottom edge
        set_image(
            self.edges[1],
            b_rect,
            self.edge_src_rects[1],
            state.ids.bottom,
            ui,
        );
        // Bottom-right corner
        set_image(
            self.corners[2],
            br_rect,
            self.corner_src_rects[2],
            state.ids.bottom_right,
            ui,
        );

        // Center,
        match self.center {
            Center::Plain(color) => {
                Rectangle::fill_with([inner_width, inner_height], color)
                    .xy(rect.xy())
                    .parent(id)
                    .graphics_for(id)
                    .and_then(maybe_color, |w, c| {
                        w.color(color.alpha(match c {
                            Color::Rgba(_, _, _, a) => a,
                            Color::Hsla(_, _, _, a) => a,
                        }))
                    })
                    .set(state.ids.center_plain, ui);
            },
            Center::Image(image_id, maybe_src_rect) => {
                set_image(
                    image_id,
                    Rect::from_xy_dim(rect.xy(), [inner_width, inner_height]),
                    maybe_src_rect,
                    state.ids.center_image,
                    ui,
                );
            },
        }
    }
}

impl Colorable for ImageFrame {
    fn color(mut self, color: Color) -> Self {
        self.color = Some(color);
        self
    }
}
