// SPDX-License-Identifier: MIT

use crate::{
    packet_route::link::{
        InfoData, InfoKind, InfoVlan, VlanFlags, VlanProtocol, VlanQosMapping,
    },
    LinkMessageBuilder,
};

/// A quality-of-service mapping between the internal priority `from` to the
/// external vlan priority `to`.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct QosMapping {
    pub from: u32,
    pub to: u32,
}

impl From<QosMapping> for VlanQosMapping {
    fn from(QosMapping { from, to }: QosMapping) -> Self {
        Self::Mapping(from, to)
    }
}

/// Represent VLAN interface.
/// Example code on creating a VLAN interface
/// ```no_run
/// use rtnetlink::{new_connection, LinkVlan};
/// #[tokio::main]
/// async fn main() -> Result<(), String> {
///     let (connection, handle, _) = new_connection().unwrap();
///     tokio::spawn(connection);
///
///     handle
///         .link()
///         .add(
///             LinkVlan::new("vlan100", 10, 100)
///                 .up()
///                 .build()
///         )
///         .execute()
///         .await
///         .map_err(|e| format!("{e}"))
/// }
/// ```
///
/// Please check LinkMessageBuilder::<LinkVlan> for more detail.
#[derive(Debug)]
pub struct LinkVlan;

impl LinkVlan {
    /// Wrapper of `LinkMessageBuilder::<LinkVlan>::new().id().dev()`
    pub fn new(
        name: &str,
        base_iface_index: u32,
        vlan_id: u16,
    ) -> LinkMessageBuilder<Self> {
        LinkMessageBuilder::<LinkVlan>::new(name)
            .id(vlan_id)
            .link(base_iface_index)
    }
}

impl LinkMessageBuilder<LinkVlan> {
    /// Create [LinkMessageBuilder] for VLAN
    pub fn new(name: &str) -> Self {
        LinkMessageBuilder::<LinkVlan>::new_with_info_kind(InfoKind::Vlan)
            .name(name.to_string())
    }

    pub fn append_info_data(mut self, info: InfoVlan) -> Self {
        if let InfoData::Vlan(infos) = self
            .info_data
            .get_or_insert_with(|| InfoData::Vlan(Vec::new()))
        {
            infos.push(info);
        }
        self
    }

    /// VLAN ID
    pub fn id(self, vlan_id: u16) -> Self {
        self.append_info_data(InfoVlan::Id(vlan_id))
    }

    /// VLAN protocol
    pub fn protocol(self, protocol: VlanProtocol) -> Self {
        self.append_info_data(InfoVlan::Protocol(protocol))
    }

    /// VLAN flags
    pub fn flags(self, flags: VlanFlags, mask: VlanFlags) -> Self {
        self.append_info_data(InfoVlan::Flags((flags, mask)))
    }

    /// ingress QoS and egress QoS
    pub fn qos<I, E>(self, ingress_qos: I, egress_qos: E) -> Self
    where
        I: IntoIterator<Item = QosMapping>,
        E: IntoIterator<Item = QosMapping>,
    {
        let mut ret = self;
        let ingress: Vec<_> =
            ingress_qos.into_iter().map(VlanQosMapping::from).collect();
        if !ingress.is_empty() {
            ret = ret.append_info_data(InfoVlan::IngressQos(ingress));
        }

        let egress: Vec<_> =
            egress_qos.into_iter().map(VlanQosMapping::from).collect();

        if !egress.is_empty() {
            ret = ret.append_info_data(InfoVlan::EgressQos(egress));
        }
        ret
    }
}
