// SPDX-License-Identifier: MIT

use anyhow::Context;
use netlink_packet_utils::{
    traits::{Parseable, ParseableParametrized},
    DecodeError,
};

use crate::{constants::*, inet, unix, SockDiagMessage};

const BUF_MIN_LEN: usize = 2;

pub struct SockDiagBuffer<T> {
    buffer: T,
}

impl<T: AsRef<[u8]>> SockDiagBuffer<T> {
    pub fn new(buffer: T) -> SockDiagBuffer<T> {
        SockDiagBuffer { buffer }
    }

    pub fn length(&self) -> usize {
        self.buffer.as_ref().len()
    }

    pub fn new_checked(buffer: T) -> Result<Self, DecodeError> {
        let packet = Self::new(buffer);
        packet.check_len()?;
        Ok(packet)
    }

    pub(crate) fn check_len(&self) -> Result<(), DecodeError> {
        let len = self.buffer.as_ref().len();
        if len < BUF_MIN_LEN {
            return Err(format!(
                "invalid buffer: length is {len} but packets are at least {BUF_MIN_LEN} bytes"
            )
            .into());
        }
        Ok(())
    }

    pub(crate) fn family(&self) -> u8 {
        self.buffer.as_ref()[0]
    }
}

impl<'a, T: AsRef<[u8]> + ?Sized> SockDiagBuffer<&'a T> {
    pub fn inner(&self) -> &'a [u8] {
        self.buffer.as_ref()
    }
}

impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> SockDiagBuffer<&'a mut T> {
    pub fn inner_mut(&mut self) -> &mut [u8] {
        self.buffer.as_mut()
    }
}

impl<'a, T: AsRef<[u8]>> ParseableParametrized<SockDiagBuffer<&'a T>, u16>
    for SockDiagMessage
{
    fn parse_with_param(
        buf: &SockDiagBuffer<&'a T>,
        message_type: u16,
    ) -> Result<Self, DecodeError> {
        use self::SockDiagMessage::*;
        buf.check_len()?;
        let message = match (message_type, buf.family()) {
            (SOCK_DIAG_BY_FAMILY, AF_INET) => {
                let err = "invalid AF_INET response";
                let buf = inet::InetResponseBuffer::new_checked(buf.inner())
                    .context(err)?;
                InetResponse(Box::new(
                    inet::InetResponse::parse(&buf).context(err)?,
                ))
            }
            (SOCK_DIAG_BY_FAMILY, AF_INET6) => {
                let err = "invalid AF_INET6 response";
                let buf = inet::InetResponseBuffer::new_checked(buf.inner())
                    .context(err)?;
                InetResponse(Box::new(
                    inet::InetResponse::parse(&buf).context(err)?,
                ))
            }
            (SOCK_DIAG_BY_FAMILY, AF_UNIX) => {
                let err = "invalid AF_UNIX response";
                let buf = unix::UnixResponseBuffer::new_checked(buf.inner())
                    .context(err)?;
                UnixResponse(Box::new(
                    unix::UnixResponse::parse(&buf).context(err)?,
                ))
            }
            (SOCK_DIAG_BY_FAMILY, af) => {
                return Err(format!("unknown address family {af}").into())
            }
            _ => {
                return Err(
                    format!("unknown message type {message_type}").into()
                )
            }
        };
        Ok(message)
    }
}
