// SPDX-License-Identifier: MIT

use netlink_packet_core::Nla;

use crate::{
    Nl80211Attr, Nl80211AttrsBuilder, Nl80211Handle, Nl80211ScanFlags,
    Nl80211ScanGetRequest, Nl80211ScanScheduleRequest,
    Nl80211ScanScheduleStopRequest, Nl80211ScanTriggerRequest,
    Nl80211SchedScanMatch, Nl80211SchedScanPlan,
};

#[derive(Debug, Clone)]
pub struct Nl80211ScanHandle(Nl80211Handle);

impl Nl80211ScanHandle {
    pub fn new(handle: Nl80211Handle) -> Self {
        Nl80211ScanHandle(handle)
    }

    /// Retrieve the current scan data
    /// (equivalent to `iw dev DEVICE scan dump`)
    pub fn dump(&mut self, if_index: u32) -> Nl80211ScanGetRequest {
        Nl80211ScanGetRequest::new(self.0.clone(), if_index)
    }

    /// Trigger a scan (equivalent to `iw dev DEVICE scan trigger`)
    /// The return of this function only means the scan trigger request
    /// sent, it does not mean the scan is finished.
    /// The `attributes: Vec<Nl80211Attr>` could be generated by
    /// [Nl80211Scan]. For example:
    /// ```no_run
    #[doc = include_str!("../../examples/nl80211_trigger_scan.rs")]
    /// ```
    pub fn trigger(
        &mut self,
        attributes: Vec<Nl80211Attr>,
    ) -> Nl80211ScanTriggerRequest {
        Nl80211ScanTriggerRequest::new(self.0.clone(), attributes)
    }

    /// Start a scan schedule (equivalent to `iw dev DEVICE scan sched_start`)
    pub fn schedule_start(
        &mut self,
        attributes: Vec<Nl80211Attr>,
    ) -> Nl80211ScanScheduleRequest {
        Nl80211ScanScheduleRequest::new(self.0.clone(), attributes)
    }

    /// Stop all scan schedule (equivalent to `iw dev DEVICE scan sched_stop`)
    pub fn schedule_stop_all(&mut self) -> Nl80211ScanScheduleStopRequest {
        Nl80211ScanScheduleStopRequest::new(self.0.clone(), Vec::new())
    }
}

#[derive(Debug)]
pub struct Nl80211Scan;

impl Nl80211Scan {
    /// Perform active scan on specified interface
    pub fn new(if_index: u32) -> Nl80211AttrsBuilder<Self> {
        Nl80211AttrsBuilder::<Self>::new()
            .if_index(if_index)
            .ssids(vec!["".to_string()])
    }
}

impl Nl80211AttrsBuilder<Nl80211Scan> {
    /// SSIDs to send probe request during active scan.
    /// `vec!["".to_string()]` means wildcard.
    pub fn ssids(self, ssids: Vec<String>) -> Self {
        self.replace(Nl80211Attr::ScanSsids(ssids))
    }

    pub fn scan_flags(self, flags: Nl80211ScanFlags) -> Self {
        self.replace(Nl80211Attr::ScanFlags(flags))
    }

    /// Enable passive scan or active scan.
    /// During an active scan, the client radio transmits a probe request and
    /// listens for a probe response from an AP. With a passive scan, the client
    /// radio listens on each channel for beacons sent periodically by an AP.
    /// This method will override existing ssids defined before:
    ///  * Enable passive scan will remove all SSIDs
    ///  * Disable passive scan will replace existing SSIDs with
    ///    `vec!["".to_string()]`
    pub fn passive(self, value: bool) -> Self {
        if value {
            self.remove(Nl80211Attr::ScanSsids(Vec::new()).kind())
        } else {
            self.replace(Nl80211Attr::ScanSsids(vec!["".to_string()]))
        }
    }

    /// Duration in unit of TU(1024 microseconds(µs)
    pub fn duration(self, value: u16) -> Self {
        self.replace(Nl80211Attr::MeasurementDuration(value))
    }

    /// Scan interval in millisecond(ms), only available for schedule scan
    pub fn interval(self, value: u32) -> Self {
        self.replace(Nl80211Attr::SchedScanInterval(value))
    }

    /// Delay before the first cycle of a scheduled scan is started.  Or the
    /// delay before a WoWLAN net-detect scan is started, counting from the
    /// moment the system is suspended. This value is in seconds.
    pub fn delay(self, value: u32) -> Self {
        self.replace(Nl80211Attr::SchedScanDelay(value))
    }

    /// Scan frequencies in MHz.
    pub fn scan_frequencies(self, freqs: Vec<u32>) -> Self {
        self.replace(Nl80211Attr::ScanFrequencies(freqs))
    }

    /// Sets of attributes to match during scheduled scans. Only BSSs
    /// that match any of the sets will be reported. These are pass-thru
    /// filter rules. For a match to succeed, the BSS must match all
    /// attributes of a set. Since not every hardware supports matching all
    /// types of attributes, there is no guarantee that the reported BSSs are
    /// fully complying with the match sets and userspace needs to be able to
    /// ignore them by itself. Thus, the implementation is somewhat
    /// hardware-dependent, but this is only an optimization and the userspace
    /// application needs to handle all the non-filtered results anyway.
    /// If omitted, no filtering is done.
    pub fn schedule_scan_match(
        self,
        matches: Vec<Nl80211SchedScanMatch>,
    ) -> Self {
        self.replace(Nl80211Attr::SchedScanMatch(matches))
    }

    /// A list of scan plans for scheduled scan. Each scan plan defines the
    /// number of scan iterations and the interval between scans. The last scan
    /// plan will always run infinitely, thus it must not specify the number of
    /// iterations, only the interval between scans. The scan plans are
    /// executed sequentially.
    pub fn schedule_scan_plan(self, plans: Vec<Nl80211SchedScanPlan>) -> Self {
        self.replace(Nl80211Attr::SchedScanPlans(plans))
    }
}
