use adw::subclass::prelude::*;
use gtk::{gdk, gio, glib, prelude::*};
use rand::Rng;

use super::{
    application::Application,
    colour,
    colour_entry::ColourEntry,
    config::{APP_ID, PROFILE},
};

// Default colors, randomized whenever you launch the app
// (bg, fg)
type PaletteColor = ((f32, f32, f32), (f32, f32, f32));

static CSS: &str = r#"
{win-class}, {win-class} headerbar {
  background: {bg};
  color: {fg};
}

{win-class} entry selection {
  color: {bg};
  background-color: {fg};
}

{win-class} .fg-entry image.left {
  color: {fg};
}

{win-class} .bg-entry image.left {
  color: {bg};
}

{win-class} levelbar .bar {
  background: alpha({fg}, 0.2);
}

{win-class} levelbar .filled {
  background-color: {fg};
}
"#;

const DEFAULT_COLORS: [PaletteColor; 12] = [
    ((228.0, 240.0, 252.0), (29.0, 87.0, 119.0)),
    ((228.0, 220.0, 171.0), (97.0, 53.0, 131.0)),
    ((251.0, 204.0, 231.0), (36.0, 28.0, 140.0)),
    ((230.0, 182.0, 133.0), (99.0, 69.0, 44.0)),
    ((130.0, 108.0, 158.0), (227.0, 222.0, 206.0)),
    ((141.0, 199.0, 149.0), (104.0, 62.0, 38.0)),
    ((160.0, 231.0, 225.0), (72.0, 12.0, 122.0)),
    ((51.0, 14.0, 99.0), (157.0, 233.0, 192.0)),
    ((140.0, 49.0, 82.0), (255.0, 155.0, 123.0)),
    ((174.0, 213.0, 33.0), (89.0, 70.0, 177.0)),
    ((255.0, 112.0, 30.0), (0.0, 43.0, 149.0)),
    ((153.0, 221.0, 238.0), (79.0, 44.0, 18.0)),
];

mod imp {
    use std::cell::Cell;

    use glib::subclass;

    use super::*;
    use crate::{contrast_level::ContrastLevelBar, contrast_preview::ContrastPreview};

    #[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)]
    #[template(resource = "/org/gnome/design/Contrast/window.ui")]
    #[properties(wrapper_type = super::Window)]
    pub struct Window {
        #[template_child]
        pub fg_entry: TemplateChild<ColourEntry>,
        #[template_child]
        pub bg_entry: TemplateChild<ColourEntry>,
        #[property(get, set, minimum = 0.0, maximum = 21.0, default = 0.0)]
        pub contrast_level: Cell<f32>,
        #[property(get, set, construct_only)]
        pub counter: Cell<u8>,
        pub(super) provider: gtk::CssProvider,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for Window {
        const NAME: &'static str = "Window";
        type ParentType = adw::ApplicationWindow;
        type Type = super::Window;

        fn class_init(klass: &mut Self::Class) {
            ContrastPreview::ensure_type();
            ContrastLevelBar::ensure_type();
            klass.bind_template();
            klass.bind_template_instance_callbacks();

            klass.install_action("win.reverse-colors", None, |win, _, _| {
                let imp = win.imp();
                let fg_colour = imp.fg_entry.colour();
                let bg_colour = imp.bg_entry.colour();
                imp.fg_entry.set_colour(bg_colour);
                imp.bg_entry.set_colour(fg_colour);
                win.colour_changed(fg_colour, bg_colour);
            });
        }

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

    #[glib::derived_properties]
    impl ObjectImpl for Window {
        fn constructed(&self) {
            self.parent_constructed();
            let obj = self.obj();

            if PROFILE == "Devel" {
                obj.add_css_class("devel");
            }
            obj.set_icon_name(Some(APP_ID));
            let display = obj.display();
            gtk::style_context_add_provider_for_display(
                &display,
                &self.provider,
                gtk::STYLE_PROVIDER_PRIORITY_APPLICATION + 1,
            );
            let counter = obj.counter();
            obj.add_css_class(&format!("window-{counter}"));
            obj.setup_state();
        }

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

    impl WidgetImpl for Window {}

    impl WindowImpl for Window {}

    impl ApplicationWindowImpl for Window {}

    impl AdwApplicationWindowImpl for Window {}
}

glib::wrapper! {
    pub struct Window(ObjectSubclass<imp::Window>)
        @extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow,
        @implements gio::ActionMap;
}

#[gtk::template_callbacks]
impl Window {
    pub fn new(app: &Application, counter: u8) -> Self {
        glib::Object::builder()
            .property("application", app)
            .property("counter", counter)
            .build()
    }

    fn colour_changed(&self, bg_colour: gdk::RGBA, fg_colour: gdk::RGBA) {
        let imp = self.imp();

        let contrast_level = colour::calc_contrast_level(&bg_colour, &fg_colour);
        self.set_contrast_level(contrast_level);

        let counter = imp.counter.get();
        let class = format!(".window-{counter}");
        let mut css = CSS.to_owned();
        css = css.replace("{bg}", &bg_colour.to_string());
        css = css.replace("{fg}", &fg_colour.to_string());
        css = css.replace("{win-class}", &class);
        css.push_str(&format!(
            "{class} {{ background-color: {bg_colour}; color: {fg_colour};}}"
        ));
        imp.provider
            .load_from_bytes(&glib::Bytes::from(css.as_bytes()));
    }

    #[template_callback]
    fn on_color_entry_changed(&self, _entry: &ColourEntry) {
        let imp = self.imp();
        let fg_colour = imp.fg_entry.colour();
        let bg_colour = imp.bg_entry.colour();

        self.colour_changed(bg_colour, fg_colour);
    }

    fn setup_state(&self) {
        let imp = self.imp();

        let mut rng = rand::thread_rng();
        let color_index = rng.gen_range(0..DEFAULT_COLORS.len());
        let (bg_colour, fg_colour) = DEFAULT_COLORS[color_index];

        let bg_colour = gdk::RGBA::builder()
            .red(bg_colour.0 / 255.0)
            .green(bg_colour.1 / 255.0)
            .blue(bg_colour.2 / 255.0)
            .build();
        let fg_colour = gdk::RGBA::builder()
            .red(fg_colour.0 / 255.0)
            .green(fg_colour.1 / 255.0)
            .blue(fg_colour.2 / 255.0)
            .build();
        self.colour_changed(bg_colour, fg_colour);
        imp.fg_entry.set_colour(fg_colour);
        imp.bg_entry.set_colour(bg_colour);
    }
}
