//! # USB On-The-Go (USB OTG)
//!
//! ## Overview
//! The USB OTG Full-speed (FS) peripheral allows communication
//! with USB devices using either blocking (usb-device) or asynchronous
//! (embassy-usb) APIs.
//!
//! It can operate as either a USB Host or Device, and supports full-speed (FS)
//! and low-speed (LS) data rates of the USB 2.0 specification.
//!
//! The blocking driver uses the `esp_synopsys_usb_otg` crate, which provides
//! the `USB bus` implementation and `USB peripheral traits`.
//!
//! The asynchronous driver uses the `embassy_usb_synopsys_otg` crate, which
//! provides the `USB bus` and `USB device` implementations.
//!
//! The module also relies on other peripheral modules, such as `GPIO`,
//! `system`, and `clock control`, to configure and enable the `USB` peripheral.
//!
//! ## Configuration
//! To use the USB OTG Full-speed peripheral driver, you need to initialize the
//! peripheral and configure its settings. The [`Usb`] struct represents the USB
//! peripheral and requires the GPIO pins that implement [`UsbDp`], and
//! [`UsbDm`], which define the specific types used for USB pin selection.
//!
//! The returned `Usb` instance can be used with the `usb-device` crate, or it
//! can be further configured with [`asynch::Driver`] to be used with the
//! `embassy-usb` crate.
//!
//! ## Examples
//! Visit the [USB Serial] example for an example of using the USB OTG
//! peripheral.
//!
//! [USB Serial]: https://github.com/esp-rs/esp-hal/blob/main/examples/peripheral/usb_serial/src/main.rs
//!
//! ## Implementation State
//! - Low-speed (LS) is not supported.

pub use esp_synopsys_usb_otg::UsbBus;
use esp_synopsys_usb_otg::UsbPeripheral;

use crate::{
    gpio::InputSignal,
    peripherals,
    system::{GenericPeripheralGuard, Peripheral as PeripheralEnable},
};

#[doc(hidden)]
/// Trait representing the USB D+ (data plus) pin.
pub trait UsbDp: crate::private::Sealed {}

#[doc(hidden)]
/// Trait representing the USB D- (data minus) pin.
pub trait UsbDm: crate::private::Sealed {}

for_each_analog_function! {
    (USB_DM, $gpio:ident) => {
        impl UsbDm for crate::peripherals::$gpio<'_> {}
    };
    (USB_DP, $gpio:ident) => {
        impl UsbDp for crate::peripherals::$gpio<'_> {}
    };
}

/// USB peripheral.
pub struct Usb<'d> {
    _usb0: peripherals::USB0<'d>,
    _guard: GenericPeripheralGuard<{ PeripheralEnable::Usb as u8 }>,
}

impl<'d> Usb<'d> {
    /// Creates a new `Usb` instance.
    pub fn new(
        usb0: peripherals::USB0<'d>,
        _usb_dp: impl UsbDp + 'd,
        _usb_dm: impl UsbDm + 'd,
    ) -> Self {
        let guard = GenericPeripheralGuard::new();

        Self {
            _usb0: usb0,
            _guard: guard,
        }
    }

    fn _enable() {
        peripherals::USB_WRAP::regs().otg_conf().modify(|_, w| {
            w.usb_pad_enable().set_bit();
            w.phy_sel().clear_bit();
            w.clk_en().set_bit();
            w.ahb_clk_force_on().set_bit();
            w.phy_clk_force_on().set_bit()
        });

        #[cfg(esp32s3)]
        peripherals::LPWR::regs().usb_conf().modify(|_, w| {
            w.sw_hw_usb_phy_sel().set_bit();
            w.sw_usb_phy_sel().set_bit()
        });

        use crate::gpio::Level;

        InputSignal::USB_OTG_IDDIG.connect_to(&Level::High); // connected connector is mini-B side
        InputSignal::USB_SRP_BVALID.connect_to(&Level::High); // HIGH to force USB device mode
        InputSignal::USB_OTG_VBUSVALID.connect_to(&Level::High); // receiving a valid Vbus from device
        InputSignal::USB_OTG_AVALID.connect_to(&Level::Low);
    }

    fn _disable() {
        // TODO
    }
}

unsafe impl Sync for Usb<'_> {}

unsafe impl UsbPeripheral for Usb<'_> {
    const REGISTERS: *const () = peripherals::USB0::PTR.cast();

    const HIGH_SPEED: bool = false;
    const FIFO_DEPTH_WORDS: usize = 256;
    const ENDPOINT_COUNT: usize = 5;

    fn enable() {
        Self::_enable();
    }

    fn ahb_frequency_hz(&self) -> u32 {
        // unused
        80_000_000
    }
}
/// Async functionality
pub mod asynch {
    use embassy_usb_driver::{
        EndpointAddress,
        EndpointAllocError,
        EndpointType,
        Event,
        Unsupported,
    };
    pub use embassy_usb_synopsys_otg::Config;
    use embassy_usb_synopsys_otg::{
        Bus as OtgBus,
        ControlPipe,
        Driver as OtgDriver,
        Endpoint,
        In,
        OtgInstance,
        Out,
        PhyType,
        State,
        on_interrupt,
        otg_v1::Otg,
    };
    use procmacros::handler;

    use super::*;
    use crate::system::Cpu;

    // From ESP32-S3 TRM:
    // Six additional endpoints (endpoint numbers 1 to 6), configurable as IN or OUT
    const MAX_EP_COUNT: usize = 7;

    static STATE: State<MAX_EP_COUNT> = State::new();

    /// Asynchronous USB driver.
    pub struct Driver<'d> {
        inner: OtgDriver<'d, MAX_EP_COUNT>,
        _usb: Usb<'d>,
    }

    impl<'d> Driver<'d> {
        const REGISTERS: Otg = unsafe { Otg::from_ptr(Usb::REGISTERS.cast_mut()) };

        /// Initializes USB OTG peripheral with internal Full-Speed PHY, for
        /// asynchronous operation.
        ///
        /// # Arguments
        ///
        /// * `ep_out_buffer` - An internal buffer used to temporarily store received packets.
        ///
        /// Must be large enough to fit all OUT endpoint max packet sizes.
        /// Endpoint allocation will fail if it is too small.
        pub fn new(peri: Usb<'d>, ep_out_buffer: &'d mut [u8], config: Config) -> Self {
            // From `synopsys-usb-otg` crate:
            // This calculation doesn't correspond to one in a Reference Manual.
            // In fact, the required number of words is higher than indicated in RM.
            // The following numbers are pessimistic and were figured out empirically.
            const RX_FIFO_EXTRA_SIZE_WORDS: u16 = 30;

            let instance = OtgInstance {
                regs: Self::REGISTERS,
                state: &STATE,
                fifo_depth_words: Usb::FIFO_DEPTH_WORDS as u16,
                extra_rx_fifo_words: RX_FIFO_EXTRA_SIZE_WORDS,
                endpoint_count: Usb::ENDPOINT_COUNT,
                phy_type: PhyType::InternalFullSpeed,
                calculate_trdt_fn: |_| 5,
            };
            Self {
                inner: OtgDriver::new(ep_out_buffer, instance, config),
                _usb: peri,
            }
        }
    }

    impl<'d> embassy_usb_driver::Driver<'d> for Driver<'d> {
        type EndpointOut = Endpoint<'d, Out>;
        type EndpointIn = Endpoint<'d, In>;
        type ControlPipe = ControlPipe<'d>;
        type Bus = Bus<'d>;

        fn alloc_endpoint_in(
            &mut self,
            ep_type: EndpointType,
            ep_addr: Option<EndpointAddress>,
            max_packet_size: u16,
            interval_ms: u8,
        ) -> Result<Self::EndpointIn, EndpointAllocError> {
            self.inner
                .alloc_endpoint_in(ep_type, ep_addr, max_packet_size, interval_ms)
        }

        fn alloc_endpoint_out(
            &mut self,
            ep_type: EndpointType,
            ep_addr: Option<EndpointAddress>,
            max_packet_size: u16,
            interval_ms: u8,
        ) -> Result<Self::EndpointOut, EndpointAllocError> {
            self.inner
                .alloc_endpoint_out(ep_type, ep_addr, max_packet_size, interval_ms)
        }

        fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) {
            let (bus, cp) = self.inner.start(control_max_packet_size);

            let mut bus = Bus {
                inner: bus,
                _usb: self._usb,
            };

            bus.init();

            (bus, cp)
        }
    }

    /// Asynchronous USB bus mainly used internally by `embassy-usb`.
    // We need a custom wrapper implementation to handle custom initialization.
    pub struct Bus<'d> {
        inner: OtgBus<'d, MAX_EP_COUNT>,
        _usb: Usb<'d>,
    }

    impl Bus<'_> {
        fn init(&mut self) {
            Usb::_enable();

            let r = Driver::REGISTERS;

            // Wait for AHB ready.
            while !r.grstctl().read().ahbidl() {}

            // Configure as device.
            r.gusbcfg().modify(|w| {
                // Force device mode
                w.set_fdmod(true);
                w.set_srpcap(false);
            });

            // Perform core soft-reset
            while !r.grstctl().read().ahbidl() {}
            r.grstctl().modify(|w| w.set_csrst(true));
            while r.grstctl().read().csrst() {}

            self.inner.config_v1();

            // Enable PHY clock
            r.pcgcctl().write(|w| w.0 = 0);

            crate::interrupt::bind_handler(crate::peripherals::Interrupt::USB, interrupt_handler);
        }

        fn disable(&mut self) {
            crate::interrupt::disable(Cpu::ProCpu, peripherals::Interrupt::USB);

            #[cfg(multi_core)]
            crate::interrupt::disable(Cpu::AppCpu, peripherals::Interrupt::USB);

            Usb::_disable();
        }
    }

    impl embassy_usb_driver::Bus for Bus<'_> {
        async fn poll(&mut self) -> Event {
            self.inner.poll().await
        }

        fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) {
            self.inner.endpoint_set_stalled(ep_addr, stalled)
        }

        fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool {
            self.inner.endpoint_is_stalled(ep_addr)
        }

        fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) {
            self.inner.endpoint_set_enabled(ep_addr, enabled)
        }

        async fn enable(&mut self) {
            self.inner.enable().await
        }

        async fn disable(&mut self) {
            self.inner.disable().await
        }

        async fn remote_wakeup(&mut self) -> Result<(), Unsupported> {
            self.inner.remote_wakeup().await
        }
    }

    impl Drop for Bus<'_> {
        fn drop(&mut self) {
            Bus::disable(self);
        }
    }

    #[handler(priority = crate::interrupt::Priority::max())]
    fn interrupt_handler() {
        unsafe { on_interrupt(Driver::REGISTERS, &STATE, Usb::ENDPOINT_COUNT) }
    }
}
