use gettextrs::gettext;
use gtk::{glib, graphene, gsk, pango, prelude::*, subclass::prelude::*};

#[derive(Debug)]
pub struct Marker {
    pub label: String,
    pub position: f32,
}

mod imp {
    use std::cell::{Cell, RefCell};

    use super::*;

    #[derive(Debug, Default, glib::Properties, gtk::CompositeTemplate)]
    #[properties(wrapper_type = super::LevelBar)]
    #[template(string = r#"
    <interface>
      <template class="LevelBar" parent="GtkWidget">
        <property name="valign">start</property>
        <child>
            <object class="AdwBin" id="bar">
                <property name="valign">start</property>
                <style>
                    <class name="bar" />
                </style>
                <child>
                    <object class="AdwBin" id="filled">
                        <property name="height-request">14</property>
                        <property name="valign">start</property>
                        <style>
                            <class name="filled" />
                        </style>
                    </object>
                </child>
            </object>
        </child>
      </template>
    </interface>
    "#)]
    pub struct LevelBar {
        pub markers: RefCell<Vec<Marker>>,
        #[property(get, set = Self::set_max_value, explicit_notify, minimum = 0.0, default = 0.0, construct_only)]
        pub max_value: Cell<f32>,
        #[property(get, set = Self::set_value, minimum = 0.0, default = 0.0)]
        pub value: Cell<f32>,
        #[template_child]
        pub bar: TemplateChild<adw::Bin>,
        #[template_child]
        pub filled: TemplateChild<adw::Bin>,
    }

    impl LevelBar {
        fn set_max_value(&self, value: f32) {
            if value != self.max_value.replace(value) {
                self.obj().notify_max_value();
                self.obj()
                    .update_property(&[gtk::accessible::Property::ValueMax(value.into())]);
            }
        }
    }

    #[glib::object_subclass]
    impl ObjectSubclass for LevelBar {
        const NAME: &'static str = "LevelBar";
        type Type = super::LevelBar;
        type ParentType = gtk::Widget;

        fn class_init(klass: &mut Self::Class) {
            klass.bind_template();
            klass.set_css_name("levelbar");
            klass.set_accessible_role(gtk::AccessibleRole::Meter);
        }

        fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
            obj.init_template();
        }
    }

    #[glib::derived_properties]
    impl ObjectImpl for LevelBar {
        fn constructed(&self) {
            self.parent_constructed();
            self.obj().set_direction(gtk::TextDirection::Ltr);
            self.filled.set_direction(gtk::TextDirection::Ltr);

            self.obj().update_property(&[
                gtk::accessible::Property::Label(&gettext("Contrast Ratio")),
                gtk::accessible::Property::ValueMin(0.0),
            ]);
        }

        fn dispose(&self) {
            self.dispose_template();
        }
    }

    impl WidgetImpl for LevelBar {
        fn request_mode(&self) -> gtk::SizeRequestMode {
            gtk::SizeRequestMode::HeightForWidth
        }

        fn measure(&self, _orientation: gtk::Orientation, _for_size: i32) -> (i32, i32, i32, i32) {
            (50, 50, -1, -1)
        }

        fn size_allocate(&self, width: i32, height: i32, baseline: i32) {
            self.parent_size_allocate(width, height, baseline);
            let widget = self.obj();
            let n_blocks = widget.max_value().log(10.0) as f64;
            let value = widget.value().log(10.0) as f64;
            let block_width = (width as f64) / n_blocks;
            self.bar
                .size_allocate(&gtk::Allocation::new(0, 25, width, height), baseline);
            self.filled.size_allocate(
                &gtk::Allocation::new(0, 0, (block_width * value) as i32, height),
                baseline,
            );
        }

        fn snapshot(&self, snapshot: &gtk::Snapshot) {
            self.parent_snapshot(snapshot);
            let widget = self.obj();
            let pango_context = widget.pango_context();
            let width = widget.width() as f32;

            let block_width = width / widget.max_value().log(10.0);

            let color = widget.color();
            // ticks width
            // ticks label font description
            let mut font_description = pango_context.font_description().unwrap();
            font_description.set_absolute_size((13 * pango::SCALE).into());
            font_description.set_weight(pango::Weight::Semibold);

            for marker in self.markers.borrow().iter() {
                let marker_x = block_width * marker.position.log(10.0);
                // draw the tick's label
                let label_length = marker.label.chars().count() as f32;

                let layout = pango::Layout::new(&pango_context);
                layout.set_font_description(Some(&font_description));
                layout.set_text(&marker.label);

                snapshot.save();
                snapshot.translate(&graphene::Point::new(marker_x - label_length * 3.5, 4.0));
                snapshot.append_layout(&layout, &color);
                snapshot.restore();

                // Draw the ticks
                let path_builder = gsk::PathBuilder::new();
                path_builder.move_to(marker_x, 19.0);
                path_builder.line_to(marker_x, 25.0);
                snapshot.append_stroke(&path_builder.to_path(), &gsk::Stroke::new(1.0), &color);
            }
            widget.queue_allocate();
        }
    }

    impl LevelBar {
        fn set_value(&self, value: f32) {
            self.value.set(value);
            self.obj().queue_allocate();
            self.obj()
                .update_property(&[gtk::accessible::Property::ValueNow(value.into())]);
        }
    }
}

glib::wrapper! {
    pub struct LevelBar(ObjectSubclass<imp::LevelBar>)
        @extends gtk::Widget, @implements gtk::Accessible;
}

impl LevelBar {
    pub fn new(max_value: f32) -> Self {
        glib::Object::builder()
            .property("max-value", max_value)
            .build()
    }

    pub fn add_marker_at(&self, label: &str, position: f32) {
        let marker = Marker {
            label: label.to_string(),
            position,
        };
        self.imp().markers.borrow_mut().push(marker);
    }
}

impl Default for LevelBar {
    fn default() -> Self {
        Self::new(21.0)
    }
}
