Documentation
use crate::ota::{
    config::Config,
    control_interface::ControlInterface,
    data_interface::DataInterface,
    pal::OtaPal,
    state::{SmContext, StateMachine},
};

use super::{agent::OtaAgent, data_interface::NoInterface, pal::ImageState};

pub struct NoTimer;

impl<const TIMER_HZ: u32> fugit_timer::Timer<TIMER_HZ> for NoTimer {
    type Error = ();

    fn now(&mut self) -> fugit_timer::TimerInstantU32<TIMER_HZ> {
        todo!()
    }

    fn start(
        &mut self,
        _duration: fugit_timer::TimerDurationU32<TIMER_HZ>,
    ) -> Result<(), Self::Error> {
        todo!()
    }

    fn cancel(&mut self) -> Result<(), Self::Error> {
        todo!()
    }

    fn wait(&mut self) -> nb::Result<(), Self::Error> {
        todo!()
    }
}

pub struct OtaAgentBuilder<'a, C, DP, DS, T, ST, PAL, const TIMER_HZ: u32>
where
    C: ControlInterface,
    DP: DataInterface,
    DS: DataInterface,
    T: fugit_timer::Timer<TIMER_HZ>,
    ST: fugit_timer::Timer<TIMER_HZ>,
    PAL: OtaPal,
{
    control: &'a C,
    data_primary: DP,
    #[cfg(all(feature = "ota_mqtt_data", feature = "ota_http_data"))]
    data_secondary: Option<DS>,
    #[cfg(not(all(feature = "ota_mqtt_data", feature = "ota_http_data")))]
    data_secondary: core::marker::PhantomData<DS>,
    pal: PAL,
    request_timer: T,
    self_test_timer: Option<ST>,
    config: Config,
}

impl<'a, C, DP, T, PAL, const TIMER_HZ: u32>
    OtaAgentBuilder<'a, C, DP, NoInterface, T, NoTimer, PAL, TIMER_HZ>
where
    C: ControlInterface,
    DP: DataInterface,
    T: fugit_timer::Timer<TIMER_HZ>,
    PAL: OtaPal,
{
    pub fn new(control_interface: &'a C, data_primary: DP, request_timer: T, pal: PAL) -> Self {
        Self {
            control: control_interface,
            data_primary,
            #[cfg(all(feature = "ota_mqtt_data", feature = "ota_http_data"))]
            data_secondary: None,
            #[cfg(not(all(feature = "ota_mqtt_data", feature = "ota_http_data")))]
            data_secondary: core::marker::PhantomData,
            pal,
            request_timer,
            self_test_timer: None,
            config: Config::default(),
        }
    }
}

impl<'a, C, DP, DS, T, ST, PAL, const TIMER_HZ: u32>
    OtaAgentBuilder<'a, C, DP, DS, T, ST, PAL, TIMER_HZ>
where
    C: ControlInterface,
    DP: DataInterface,
    DS: DataInterface,
    T: fugit_timer::Timer<TIMER_HZ>,
    ST: fugit_timer::Timer<TIMER_HZ>,
    PAL: OtaPal,
{
    #[cfg(all(feature = "ota_mqtt_data", feature = "ota_http_data"))]
    pub fn data_secondary<D: DataInterface>(
        self,
        interface: D,
    ) -> OtaAgentBuilder<'a, C, DP, D, T, ST, PAL, TIMER_HZ> {
        OtaAgentBuilder {
            control: self.control,
            data_primary: self.data_primary,
            data_secondary: Some(interface),
            pal: self.pal,
            request_timer: self.request_timer,
            self_test_timer: self.self_test_timer,
            config: self.config,
        }
    }

    pub fn block_size(self, block_size: usize) -> Self {
        Self {
            config: Config {
                block_size,
                ..self.config
            },
            ..self
        }
    }

    pub fn max_request_momentum(self, max_request_momentum: u8) -> Self {
        Self {
            config: Config {
                max_request_momentum,
                ..self.config
            },
            ..self
        }
    }

    pub fn activate_delay(self, activate_delay: u8) -> Self {
        Self {
            config: Config {
                activate_delay,
                ..self.config
            },
            ..self
        }
    }

    pub fn request_wait_ms(self, request_wait_ms: u32) -> Self {
        Self {
            config: Config {
                request_wait_ms,
                ..self.config
            },
            ..self
        }
    }

    pub fn status_update_frequency(self, status_update_frequency: u32) -> Self {
        Self {
            config: Config {
                status_update_frequency,
                ..self.config
            },
            ..self
        }
    }

    pub fn allow_downgrade(self) -> Self {
        Self {
            config: Config {
                allow_downgrade: true,
                ..self.config
            },
            ..self
        }
    }

    pub fn with_self_test_timeout<NST>(
        self,
        timer: NST,
        timeout_ms: u32,
    ) -> OtaAgentBuilder<'a, C, DP, DS, T, NST, PAL, TIMER_HZ>
    where
        NST: fugit_timer::Timer<TIMER_HZ>,
    {
        OtaAgentBuilder {
            control: self.control,
            data_primary: self.data_primary,
            data_secondary: self.data_secondary,
            pal: self.pal,
            request_timer: self.request_timer,
            self_test_timer: Some(timer),
            config: Config {
                self_test_timeout_ms: timeout_ms,
                ..self.config
            },
        }
    }

    pub fn build(self) -> OtaAgent<'a, C, DP, DS, T, ST, PAL, TIMER_HZ> {
        OtaAgent {
            state: StateMachine::new(SmContext {
                events: heapless::spsc::Queue::new(),
                control: self.control,
                data_secondary: self.data_secondary,
                data_primary: self.data_primary,
                active_interface: None,
                request_momentum: 0,
                request_timer: self.request_timer,
                self_test_timer: self.self_test_timer,
                pal: self.pal,
                config: self.config,
                image_state: ImageState::Unknown,
            }),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use crate::{
        ota::test::mock::{MockPal, MockTimer},
        test::MockMqtt,
    };

    #[test]
    fn enables_allow_downgrade() {
        let mqtt = MockMqtt::new();

        let request_timer = MockTimer::new();
        let self_test_timer = MockTimer::new();
        let pal = MockPal {};

        let builder = OtaAgentBuilder::new(&mqtt, &mqtt, request_timer, pal)
            .with_self_test_timeout(self_test_timer, 32000)
            .allow_downgrade();

        assert!(builder.config.allow_downgrade);
        assert!(builder.self_test_timer.is_some());
        assert_eq!(builder.config.self_test_timeout_ms, 32000);

        let agent = builder.build();

        assert!(agent.state.context().config.allow_downgrade);
    }
}