use crate::{
    hash,
    types::{Rect, Vector2},
    widgets::Editbox,
    Id, Layout, Ui,
};
use std::ops::Range;

pub struct Slider<'a> {
    id: Id,
    label: &'a str,
    range: Range<f32>,
}

impl<'a> Slider<'a> {
    pub fn new(id: Id, range: Range<f32>) -> Slider<'a> {
        Slider {
            id,
            range,
            label: "",
        }
    }

    pub fn label<'b>(self, label: &'b str) -> Slider<'b> {
        Slider {
            id: self.id,
            range: self.range,
            label,
        }
    }

    pub fn ui(self, ui: &mut Ui, data: &mut f32) {
        let context = ui.get_active_window_context();

        let size = Vector2::new(
            context.window.cursor.area.w
                - context.global_style.margin * 3.
                - context.window.cursor.ident,
            19.,
        );
        let pos = context.window.cursor.fit(size, Layout::Vertical);

        let editbox_width = 50.;
        let label_width = 100.;
        let slider_width = size.x - editbox_width - label_width;
        let margin = 5.;

        let mut temp_string = context
            .storage_any
            .get_or_insert_with::<String, _>(self.id, || format!("{:.2}", *data))
            .clone();

        let editbox_id = hash!(self.id, "editbox");
        if context.window.input_focused(editbox_id) == false {
            use std::fmt::Write;

	    temp_string.clear();
            let _ = write!(&mut temp_string, "{:.2}", *data);
        }

        Editbox::new(editbox_id, Vector2::new(50., size.y))
            .position(pos)
            .multiline(false)
            .filter(&|character| character.is_digit(10) || character == '.' || character == '-')
            .ui(ui, &mut temp_string);

        let context = ui.get_active_window_context();
        let old_string = context.storage_any.get_or_default::<String>(self.id);
        if *old_string != temp_string {
            if let Ok(num) = temp_string.parse::<f32>() {
                if num > self.range.end {
                    *data = self.range.end;
                } else if num < self.range.start {
                    *data = self.range.start;
                } else {
                    *data = num;
                }
            }
        }

        let dragging = context
            .storage_u32
            .entry(hash!(self.id, "dragging"))
            .or_insert(0);

        let slider_start_x = editbox_width + pos.x + margin;
        let data_pos = (*data - self.range.start) / (self.range.end - self.range.start)
            * slider_width
            + slider_start_x;

        let bar_rect = Rect::new(data_pos - 4., pos.y, 8., 20.);
        let hovered = bar_rect.contains(context.input.mouse_position);

        if hovered && context.input.is_mouse_down() {
            *dragging = 1;
	    context.window.input_focus = Some(self.id);
            context.input.cursor_grabbed = true;
        }

        if *dragging == 1 && context.input.is_mouse_down == false {
            context.input.cursor_grabbed = false;
            *dragging = 0;
	    context.window.input_focus = None;
        }

        if *dragging == 1 {
            let mouse_position = ((context.input.mouse_position.x - slider_start_x) / slider_width)
                .min(1.)
                .max(0.);
            let old_data = *data;
            *data = self.range.start + (self.range.end - self.range.start) * mouse_position;

            if old_data != *data {
                use std::fmt::Write;

		temp_string.clear();
                let _ = write!(&mut temp_string, "{:.2}", *data);
            }
        }

        context.window.draw_commands.draw_line(
            Vector2::new(pos.x + editbox_width + margin, pos.y + size.y / 2.),
            Vector2::new(
                pos.x + editbox_width + slider_width + margin,
                pos.y + size.y / 2.,
            ),
            context.global_style.text(context.focused),
        );

        context.window.draw_commands.draw_rect(
            bar_rect,
            None,
            context.global_style.slider_bar(context.focused, hovered),
        );

        context.window.draw_commands.draw_label(
            self.label,
            Vector2::new(
                pos.x + editbox_width + slider_width + margin * 2.,
                pos.y + 2.,
            ),
            context.global_style.text(context.focused),
        );

        *old_string = temp_string;
    }
}

impl Ui {
    pub fn slider(&mut self, id: Id, label: &str, range: Range<f32>, data: &mut f32) {
        Slider::new(id, range).label(label).ui(self, data)
    }
}
