// SPDX-License-Identifier: MIT

use futures_util::{
    future::{self, Either},
    stream::{Stream, StreamExt},
    FutureExt,
};
use netlink_packet_core::{NetlinkMessage, NLM_F_DUMP, NLM_F_REQUEST};
use netlink_packet_route::{
    link::{LinkAttribute, LinkExtentMask, LinkMessage},
    AddressFamily, RouteNetlinkMessage,
};

use crate::{try_rtnl, Error, Handle};

pub struct LinkGetRequest {
    handle: Handle,
    message: LinkMessage,
    // There are two ways to retrieve links: we can either dump them
    // all and filter the result, or if we already know the index or
    // the name of the link we're looking for, we can just retrieve
    // that one. If `dump` is `true`, all the links are fetched.
    // Otherwise, only the link that match the given index or name
    // is fetched.
    dump: bool,
}

impl LinkGetRequest {
    pub(crate) fn new(handle: Handle) -> Self {
        LinkGetRequest {
            handle,
            message: LinkMessage::default(),
            dump: true,
        }
    }

    /// Setting filter mask
    pub fn set_filter_mask(
        mut self,
        family: AddressFamily,
        filter_mask: Vec<LinkExtentMask>,
    ) -> Self {
        self.message.header.interface_family = family;
        self.message
            .attributes
            .push(LinkAttribute::ExtMask(filter_mask));
        self
    }

    /// Execute the request
    pub fn execute(self) -> impl Stream<Item = Result<LinkMessage, Error>> {
        let LinkGetRequest {
            mut handle,
            message,
            dump,
        } = self;

        let mut req =
            NetlinkMessage::from(RouteNetlinkMessage::GetLink(message));

        if dump {
            req.header.flags = NLM_F_REQUEST | NLM_F_DUMP;
        } else {
            req.header.flags = NLM_F_REQUEST;
        }

        match handle.request(req) {
            Ok(response) => Either::Left(response.map(move |msg| {
                Ok(try_rtnl!(msg, RouteNetlinkMessage::NewLink))
            })),
            Err(e) => Either::Right(
                future::err::<LinkMessage, Error>(e).into_stream(),
            ),
        }
    }

    /// Return a mutable reference to the request
    pub fn message_mut(&mut self) -> &mut LinkMessage {
        &mut self.message
    }

    /// Lookup a link by index
    pub fn match_index(mut self, index: u32) -> Self {
        self.dump = false;
        self.message.header.index = index;
        self
    }

    /// Lookup a link by name
    ///
    /// This function requires support from your kernel (>= 2.6.33). If yours is
    /// older, consider filtering the resulting stream of links.
    pub fn match_name(mut self, name: String) -> Self {
        self.dump = false;
        self.message.attributes.push(LinkAttribute::IfName(name));
        self
    }
}
