// Shortwave - volume_control.rs
// Copyright (C) 2024  Felix Häcker <haeckerfelix@gnome.org>
//               2022  Emmanuele Bassi (Original Author, Amberol)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

use std::cell::Cell;
use std::marker::PhantomData;

use adw::subclass::prelude::*;
use glib::clone;
use glib::{Properties, subclass::Signal};
use gtk::{CompositeTemplate, gdk, gio, glib, prelude::*};

mod imp {
    use std::sync::LazyLock;

    use super::*;

    #[derive(Debug, Default, CompositeTemplate, Properties)]
    #[template(resource = "/de/haeckerfelix/Shortwave/gtk/volume_control.ui")]
    #[properties(wrapper_type = super::SwVolumeControl)]
    pub struct SwVolumeControl {
        #[template_child]
        volume_low_button: TemplateChild<gtk::Button>,
        #[template_child]
        volume_scale: TemplateChild<gtk::Scale>,
        #[template_child]
        volume_high_image: TemplateChild<gtk::Image>,

        #[property(get=Self::volume, set=Self::set_volume, minimum = 0.0, maximum = 1.0, default = 1.0)]
        volume: PhantomData<f64>,
        #[property(get, set=Self::set_toggle_mute)]
        toggle_mute: Cell<bool>,

        prev_volume: Cell<f64>,
    }

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

        fn class_init(klass: &mut Self::Class) {
            Self::bind_template(klass);

            klass.set_layout_manager_type::<gtk::BoxLayout>();
            klass.set_css_name("volume");
            klass.set_accessible_role(gtk::AccessibleRole::Group);

            klass.install_property_action("volume.toggle-mute", "toggle-mute");

            klass.install_action("volume.increase", None, |obj, _, _| {
                obj.set_volume((obj.volume() + 0.05).clamp(0.0, 1.0));
            });

            klass.install_action("volume.decrease", None, |obj, _, _| {
                obj.set_volume((obj.volume() - 0.05).clamp(0.0, 1.0));
            });
        }

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

    #[glib::derived_properties]
    impl ObjectImpl for SwVolumeControl {
        fn constructed(&self) {
            self.parent_constructed();

            let adj = gtk::Adjustment::builder()
                .lower(0.0)
                .upper(1.0)
                .step_increment(0.05)
                .value(1.0)
                .build();
            self.volume_scale.set_adjustment(&adj);

            adj.connect_notify_local(
                Some("value"),
                clone!(
                    #[weak(rename_to = imp)]
                    self,
                    move |adj, _| {
                        let value = adj.value();
                        if value == adj.lower() {
                            imp.volume_low_button
                                .set_icon_name("audio-volume-muted-symbolic");
                        } else {
                            imp.volume_low_button
                                .set_icon_name("audio-volume-low-symbolic");
                        }
                        imp.obj().notify_volume();
                        imp.obj().emit_by_name::<()>("volume-changed", &[&value]);
                    }
                ),
            );

            let event_controller = gtk::EventControllerScroll::builder()
                .name("volume-scroll")
                .flags(gtk::EventControllerScrollFlags::VERTICAL)
                .build();

            event_controller.connect_scroll(clone!(
                #[weak(rename_to = imp)]
                self,
                #[upgrade_or_panic]
                move |_, _, dy| {
                    let adj = imp.volume_scale.adjustment();
                    let delta = dy * adj.step_increment();
                    let d = (adj.value() - delta).clamp(adj.lower(), adj.upper());
                    adj.set_value(d);
                    glib::Propagation::Stop
                }
            ));
            self.volume_scale.add_controller(event_controller);

            let shortcut_controller = gtk::ShortcutController::new();
            shortcut_controller.set_scope(gtk::ShortcutScope::Global);

            shortcut_controller.add_shortcut(gtk::Shortcut::new(
                Some(gtk::KeyvalTrigger::new(
                    gdk::Key::m,
                    gdk::ModifierType::CONTROL_MASK,
                )),
                Some(gtk::NamedAction::new("volume.toggle-mute")),
            ));
            shortcut_controller.add_shortcut(gtk::Shortcut::new(
                Some(gtk::KeyvalTrigger::new(
                    gdk::Key::plus,
                    gdk::ModifierType::CONTROL_MASK,
                )),
                Some(gtk::NamedAction::new("volume.increase")),
            ));
            shortcut_controller.add_shortcut(gtk::Shortcut::new(
                Some(gtk::KeyvalTrigger::new(
                    gdk::Key::minus,
                    gdk::ModifierType::CONTROL_MASK,
                )),
                Some(gtk::NamedAction::new("volume.decrease")),
            ));
            self.obj().add_controller(shortcut_controller);
        }

        fn dispose(&self) {
            while let Some(child) = self.obj().first_child() {
                child.unparent();
            }
        }

        fn signals() -> &'static [Signal] {
            static SIGNALS: LazyLock<Vec<Signal>> = LazyLock::new(|| {
                vec![
                    Signal::builder("volume-changed")
                        .param_types([f64::static_type()])
                        .build(),
                ]
            });

            SIGNALS.as_ref()
        }
    }

    impl WidgetImpl for SwVolumeControl {}

    impl SwVolumeControl {
        fn set_toggle_mute(&self, muted: bool) {
            if muted != self.toggle_mute.replace(muted) {
                if muted {
                    let prev_value = self.volume_scale.value();
                    self.prev_volume.replace(prev_value);
                    self.volume_scale.set_value(0.0);
                } else {
                    let prev_value = self.prev_volume.get();
                    self.volume_scale.set_value(prev_value);
                }
                self.obj().notify_toggle_mute();
            }
        }

        pub fn volume(&self) -> f64 {
            self.volume_scale.value()
        }

        pub fn set_volume(&self, value: f64) {
            self.volume_scale.set_value(value);
        }
    }
}

glib::wrapper! {
    pub struct SwVolumeControl(ObjectSubclass<imp::SwVolumeControl>)
        @extends gtk::Widget,
        @implements gio::ActionGroup, gio::ActionMap, gtk::Buildable,  gtk::ConstraintTarget, gtk::Accessible;
}

impl SwVolumeControl {
    pub fn new() -> Self {
        glib::Object::new()
    }
}

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