use std::collections::HashMap;
use std::sync::{Arc, Mutex};

use gtk::prelude::*;

use super::model;
use crate::audio::sources::metronome;

pub struct Menu {
    pub widget: gtk::MenuButton,
}

impl Menu {
    pub fn new(model: model::MenuModel) -> Self {
        let model = Arc::new(Mutex::new(model));
        let mut model_lock = model.lock().unwrap();
        model_lock.commit();

        // Pressed fade in slider
        let menu_pressed_fade_in = {
            let container_vbox = gtk::Box::new(gtk::Orientation::Vertical, 5);
            let switch_hbox = gtk::Box::new(gtk::Orientation::Horizontal, 5);
            let fade_switch = gtk::SwitchBuilder::new()
                .active(*model_lock.pressed_fade_in_enabled.get())
                .build();
            switch_hbox.pack_start(&fade_switch, false, false, 0);
            switch_hbox.pack_start(&gtk::Label::new(Some("Fade in")), false, false, 0);

            let value = *model_lock.pressed_fade_in_duration.get() as f64;
            let duration_slider = gtk::ScaleBuilder::new()
                .adjustment(&gtk::Adjustment::new(value, 0.1, 1.0, 0.1, 0.5, 0.0))
                .digits(1)
                .build();
            container_vbox.pack_start(&switch_hbox, false, false, 0);
            container_vbox.pack_start(&duration_slider, false, false, 0);

            let model_refdup = model.clone();
            duration_slider.connect_value_changed(move |slider| {
                let mut model_lock = model_refdup.lock().unwrap();
                model_lock
                    .pressed_fade_in_duration
                    .set(slider.value() as f32);
                model_lock.commit();
            });

            let model_refdup = model.clone();
            let slider_refdup = duration_slider.clone();
            fade_switch.connect_state_set(move |_, enabled| {
                let mut model_lock = model_refdup.lock().unwrap();
                model_lock.pressed_fade_in_enabled.set(enabled);
                model_lock.commit();
                slider_refdup.set_visible(enabled);
                Inhibit(false)
            });
            container_vbox.show_all();
            duration_slider.set_visible(fade_switch.is_active());
            container_vbox
        };

        // Pressed fade out slider
        let menu_pressed_fade_out = {
            let container_vbox = gtk::Box::new(gtk::Orientation::Vertical, 5);
            let switch_hbox = gtk::Box::new(gtk::Orientation::Horizontal, 5);
            let fade_switch = gtk::SwitchBuilder::new()
                .active(*model_lock.pressed_fade_out_enabled.get())
                .build();
            switch_hbox.pack_start(&fade_switch, false, false, 0);
            switch_hbox.pack_start(
                &gtk::Label::new(Some("Fade automatically")),
                false,
                false,
                0,
            );

            let value = *model_lock.pressed_fade_out_duration.get() as f64;
            let duration_slider = gtk::ScaleBuilder::new()
                .adjustment(&gtk::Adjustment::new(value, 0.1, 10.0, 0.1, 1.0, 0.0))
                .digits(1)
                .build();
            container_vbox.pack_start(&switch_hbox, false, false, 0);
            container_vbox.pack_start(&duration_slider, false, false, 0);

            let model_refdup = model.clone();
            duration_slider.connect_value_changed(move |slider| {
                let mut model_lock = model_refdup.lock().unwrap();
                model_lock
                    .pressed_fade_out_duration
                    .set(slider.value() as f32);
                model_lock.commit();
            });

            let model_refdup = model.clone();
            let slider_refdup = duration_slider.clone();
            fade_switch.connect_state_set(move |_, enabled| {
                let mut model_lock = model_refdup.lock().unwrap();
                model_lock.pressed_fade_out_enabled.set(enabled);
                model_lock.commit();
                slider_refdup.set_visible(enabled);
                Inhibit(false)
            });
            container_vbox.show_all();
            duration_slider.set_visible(fade_switch.is_active());
            container_vbox
        };

        // Released fade out slider
        let menu_released_fade_out = {
            let container_vbox = gtk::Box::new(gtk::Orientation::Vertical, 5);
            let switch_hbox = gtk::Box::new(gtk::Orientation::Horizontal, 5);
            let fade_switch = gtk::SwitchBuilder::new()
                .active(*model_lock.released_fade_out_enabled.get())
                .build();
            switch_hbox.pack_start(&fade_switch, false, false, 0);
            switch_hbox.pack_start(
                &gtk::Label::new(Some("Fade when releasing notes")),
                false,
                false,
                0,
            );

            let value = *model_lock.released_fade_out_duration.get() as f64;
            let duration_slider = gtk::ScaleBuilder::new()
                .adjustment(&gtk::Adjustment::new(value, 0.1, 10.0, 0.1, 1.0, 0.0))
                .digits(1)
                .build();
            container_vbox.pack_start(&switch_hbox, false, false, 0);
            container_vbox.pack_start(&duration_slider, false, false, 0);

            let model_refdup = model.clone();
            duration_slider.connect_value_changed(move |slider| {
                let mut model_lock = model_refdup.lock().unwrap();
                model_lock
                    .released_fade_out_duration
                    .set(slider.value() as f32);
                model_lock.commit();
            });

            let model_refdup = model.clone();
            let slider_refdup = duration_slider.clone();
            fade_switch.connect_state_set(move |_, enabled| {
                let mut model_lock = model_refdup.lock().unwrap();
                model_lock.released_fade_out_enabled.set(enabled);
                model_lock.commit();
                slider_refdup.set_visible(enabled);
                Inhibit(false)
            });
            container_vbox.show_all();
            duration_slider.set_visible(fade_switch.is_active());
            container_vbox
        };

        // Metronome switch
        let menu_metronome_switch = {
            let container_hbox = gtk::Box::new(gtk::Orientation::Horizontal, 5);
            let metronome_switch = gtk::SwitchBuilder::new()
                .active(*model_lock.metronome_enabled.get())
                .build();
            container_hbox.pack_start(&metronome_switch, false, false, 0);
            container_hbox.pack_start(&gtk::Label::new(Some("Metronome")), false, false, 0);

            let model_refdup = model.clone();
            metronome_switch.connect_state_set(move |_, enabled| {
                let mut model_lock = model_refdup.lock().unwrap();
                model_lock.metronome_enabled.set(enabled);
                model_lock.commit();
                Inhibit(false)
            });
            container_hbox.show_all();
            container_hbox
        };

        // BPM spin button
        let menu_metronome_bpm = {
            let container_hbox = gtk::Box::new(gtk::Orientation::Horizontal, 5);
            let bpm_input = gtk::SpinButtonBuilder::new()
                .adjustment(&gtk::Adjustment::new(30.0, 30.0, 480.0, 1.0, 10.0, 10.0))
                .value(*model_lock.metronome_bpm.get() as f64)
                .digits(0)
                .build();
            container_hbox.pack_start(&gtk::Label::new(Some("BPM:")), false, false, 0);
            container_hbox.pack_start(&bpm_input, false, false, 0);

            let model_refdup = model.clone();
            bpm_input.connect_value_notify(move |wgt| {
                let mut model_lock = model_refdup.lock().unwrap();
                model_lock.metronome_bpm.set(wgt.value() as u32);
                model_lock.commit();
            });
            container_hbox.show_all();
            container_hbox
        };

        // Meter combo box
        let menu_metronome_meter = {
            let container_hbox = gtk::Box::new(gtk::Orientation::Horizontal, 5);
            let meter_combo = gtk::ComboBoxText::new();
            let meters = [
                metronome::Meter::_2_4,
                metronome::Meter::_3_4,
                metronome::Meter::_4_4,
                metronome::Meter::_6_8,
            ];
            let meters_map = meters
                .iter()
                .map(|meter| {
                    let name = meter.to_string();
                    meter_combo.append(Some(name.as_str()), &name);
                    (name, *meter)
                })
                .collect::<HashMap<_, _>>();
            meter_combo.set_active_id(Some(model_lock.metronome_meter.get().to_string().as_str()));
            container_hbox.pack_start(&gtk::Label::new(Some("Meter:")), false, false, 0);
            container_hbox.pack_start(&meter_combo, false, false, 0);

            let model_refdup = model.clone();
            meter_combo.connect_changed(move |wgt| {
                if let Some(Some(&meter)) = wgt.active_id().map(|m| meters_map.get(m.as_str()))
                {
                    let mut model_lock = model_refdup.lock().unwrap();
                    model_lock.metronome_meter.set(meter);
                    model_lock.commit();
                }
            });
            container_hbox.show_all();
            container_hbox
        };

        // Construct menu
        let menu_button = gtk::MenuButton::new();

        let items = gtk::Box::new(gtk::Orientation::Vertical, 5);
        items.add(&menu_pressed_fade_in);
        items.add(&gtk::Separator::new(gtk::Orientation::Horizontal));
        items.add(&menu_pressed_fade_out);
        items.add(&gtk::Separator::new(gtk::Orientation::Horizontal));
        items.add(&menu_released_fade_out);
        items.add(&gtk::Separator::new(gtk::Orientation::Horizontal));
        items.add(&menu_metronome_switch);
        items.add(&menu_metronome_bpm);
        items.add(&menu_metronome_meter);
        items.set_margin(5);
        items.show();

        let menu_popover = gtk::Popover::new(Some(&menu_button));
        menu_popover.add(&items);

        menu_button.set_popover(Some(&menu_popover));

        Menu {
            widget: menu_button,
        }
    }
}
