use std::num::NonZeroU32;
use std::sync::mpsc::Receiver;
use std::sync::{mpsc, Arc};
use std::thread;
use std::time::Duration;

use duration_toggler::Toggler;
use esp_idf_svc::hal::peripherals::Peripherals;
use esp_idf_svc::hal::task::notification::{Notification, Notifier};
use esp_idf_svc::hal::{delay, gpio::*};
use led_button_manager::led_button_toggle_handler::LedButtonToggle;
use led_button_manager::LedButtonsManager; // wokwi
                                           // use led_button_manager::manager::LedButtonsManager; // local

mod duration_toggler;
mod led_button_manager;

const TOGGLE_LED_NOTIFICATION_CODE: u32 = 1;

struct PeripheralsToUse {
    led: PinDriver<'static, Gpio4, Output>,
    led_blinking_duration_change_button: PinDriver<'static, Gpio0, Input>,
}

fn prepare_peripherals(peripherals: Peripherals, notifier: Arc<Notifier>) -> PeripheralsToUse {
    let led = PinDriver::output(peripherals.pins.gpio4).unwrap();
    println!("Prepared LED.");
    let mut led_blinking_duration_change_button = PinDriver::input(peripherals.pins.gpio0).unwrap();
    println!("Prepared led blinking .");
    led_blinking_duration_change_button
        .set_pull(Pull::Up)
        .unwrap();
    led_blinking_duration_change_button
        .set_interrupt_type(InterruptType::PosEdge)
        .unwrap();
    unsafe {
        let notifier_clone = notifier.clone();
        led_blinking_duration_change_button
            .subscribe(move || {
                notifier_clone
                    .notify_and_yield(NonZeroU32::new(TOGGLE_LED_NOTIFICATION_CODE).unwrap());
            })
            .unwrap();
    }
    led_blinking_duration_change_button
        .enable_interrupt()
        .unwrap();

    PeripheralsToUse {
        led,
        led_blinking_duration_change_button,
    }
}

fn main() {
    // It is necessary to call this function once. Otherwise some patches to the runtime
    // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
    esp_idf_svc::sys::link_patches();

    let (transmitter, receiver) = mpsc::channel::<Duration>();
    let toggler = Toggler::new(vec![]);
    let default_led_toggle_duration = toggler.get_duration();
    let mut manager = LedButtonsManager::new(vec![Box::new(LedButtonToggle::new(
        1,
        transmitter,
        toggler,
    ))])
    .unwrap();

    let peripherals = Peripherals::take().unwrap();
    let notification = Notification::new();
    let notifier = notification.notifier();
    let notifier_clone = notifier.clone();
    let mut peripherals_to_use = prepare_peripherals(peripherals, notifier_clone);

    let blinking_led_handle = thread::spawn(move || {
        blinking_led(
            receiver,
            peripherals_to_use.led,
            default_led_toggle_duration,
        )
    });

    loop {
        peripherals_to_use
            .led_blinking_duration_change_button
            .enable_interrupt()
            .unwrap();
        let noification_code = notification.wait(delay::BLOCK);

        if let Some(code) = noification_code {
            manager.handle(code.into()).unwrap();
        }

        blinking_led_handle.thread().unpark();
    }
}

fn blinking_led(
    receiver: Receiver<Duration>,
    mut led: PinDriver<'static, Gpio4, Output>,
    default_duration: Duration,
) {
    let original_duration = default_duration;
    loop {
        let new_duration = receiver.try_recv().unwrap_or(original_duration);

        led.toggle().unwrap();
        std::thread::park_timeout(new_duration);
    }
}