use crate::{err::ipv4_exts::ExtsWalkError, *};

/// IPv4 extension headers present after the ip header.
///
/// Currently supported:
/// * Authentication Header
///
/// Currently not supported:
/// - Encapsulating Security Payload Header (ESP)
#[derive(Clone, Debug, Eq, PartialEq, Default)]
pub struct Ipv4Extensions {
    pub auth: Option<IpAuthHeader>,
}

impl Ipv4Extensions {
    /// Minimum length required for extension header in bytes/octets.
    /// Which is zero as no extension headers are required.
    pub const MIN_LEN: usize = 0;

    /// Maximum summed up length of all extension headers in bytes/octets.
    pub const MAX_LEN: usize = IpAuthHeader::MAX_LEN;

    /// Read all known ipv4 extensions and return an `Ipv4Extensions` with the
    /// identified slices, the final ip number and a slice pointing to the non parsed data.
    pub fn from_slice(
        start_ip_number: IpNumber,
        slice: &[u8],
    ) -> Result<(Ipv4Extensions, IpNumber, &[u8]), err::ip_auth::HeaderSliceError> {
        Ipv4ExtensionsSlice::from_slice(start_ip_number, slice).map(|v| (v.0.to_header(), v.1, v.2))
    }

    /// Collects all known ipv4 extension headers in a slice until an error
    /// is encountered or a "non IP extension header" is found and
    /// returns the successfully parsed parts (+ the unparsed slice
    /// it's [`IpNumber`] and the error if one occurred).
    ///
    /// The returned values are
    ///
    /// * [`Ipv4Extensions`] containing the successfully parsed IPv6 extension headers
    /// * [`IpNumber`] of unparsed data
    /// * Slice with unparsed data
    /// * Optional with error if there was an error wich stoped the parsing.
    ///
    /// # Examples
    ///
    /// ```
    /// use etherparse::{Ipv4Extensions, IpAuthHeader, ip_number::{UDP, AUTHENTICATION_HEADER}};
    ///
    /// let auth_header = IpAuthHeader::new(UDP, 0, 0, &[]).unwrap();
    /// let data = auth_header.to_bytes();
    ///
    /// let (ipv4_exts, next_ip_num, next_data, err) =
    ///     Ipv4Extensions::from_slice_lax(AUTHENTICATION_HEADER, &data);
    ///
    /// // authentication header is separated and no error occurred
    /// assert!(ipv4_exts.auth.is_some());
    /// assert_eq!(next_ip_num, UDP);
    /// assert_eq!(next_data, &[]);
    /// assert!(err.is_none());
    /// ```
    ///
    /// It is also ok to pass in a "non ip extension":
    ///
    /// ```
    /// use etherparse::{Ipv4Extensions, ip_number::UDP};
    ///
    /// let data = [0,1,2,3];
    /// // passing a non "ip extension header" ip number
    /// let (ipv4_exts, next_ip_num, next_data, err) =
    ///     Ipv4Extensions::from_slice_lax(UDP, &data);
    ///
    /// // the original data gets returned as UDP is not a
    /// // an IP extension header
    /// assert!(ipv4_exts.is_empty());
    /// assert_eq!(next_ip_num, UDP);
    /// assert_eq!(next_data, &data);
    /// // no errors gets triggered as the data is valid
    /// assert!(err.is_none());
    /// ```
    ///
    /// In case an error occurred the original data gets
    /// returned together with the error:
    ///
    /// ```
    /// use etherparse::{
    ///     Ipv4Extensions,
    ///     IpAuthHeader,
    ///     ip_number::AUTHENTICATION_HEADER,
    ///     LenSource,
    ///     err::{ip_auth::HeaderSliceError::Len, LenError, Layer}
    /// };
    ///
    /// // providing not enough data
    /// let (ipv4_exts, next_ip_num, next_data, err) =
    ///     Ipv4Extensions::from_slice_lax(AUTHENTICATION_HEADER, &[]);
    ///
    /// // original data will be returned with no data parsed
    /// assert!(ipv4_exts.is_empty());
    /// assert_eq!(next_ip_num, AUTHENTICATION_HEADER);
    /// assert_eq!(next_data, &[]);
    /// // the error that stopped the parsing will also be returned
    /// assert_eq!(err, Some(Len(LenError{
    ///     required_len: IpAuthHeader::MIN_LEN,
    ///     len: 0,
    ///     len_source: LenSource::Slice,
    ///     layer: Layer::IpAuthHeader,
    ///     layer_start_offset: 0,
    /// })));
    /// ```
    pub fn from_slice_lax(
        start_ip_number: IpNumber,
        start_slice: &[u8],
    ) -> (
        Ipv4Extensions,
        IpNumber,
        &[u8],
        Option<err::ip_auth::HeaderSliceError>,
    ) {
        let (slice, next_ip_number, next_data, error) =
            Ipv4ExtensionsSlice::from_slice_lax(start_ip_number, start_slice);
        (slice.to_header(), next_ip_number, next_data, error)
    }

    /// Reads the known ipv4 extension headers from the reader and returns the
    /// headers together with the internet protocol number identifying the protocol
    /// that will be next.
    #[cfg(feature = "std")]
    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
    pub fn read<T: std::io::Read + Sized>(
        reader: &mut T,
        start_ip_number: IpNumber,
    ) -> Result<(Ipv4Extensions, IpNumber), err::ip_auth::HeaderReadError> {
        use ip_number::*;
        if AUTH == start_ip_number {
            let header = IpAuthHeader::read(reader)?;
            let next_ip_number = header.next_header;
            Ok((Ipv4Extensions { auth: Some(header) }, next_ip_number))
        } else {
            Ok((Default::default(), start_ip_number))
        }
    }

    /// Reads the known ipv4 extension headers from a length limited reader and returns the
    /// headers together with the internet protocol number identifying the protocol
    /// that will be next.
    #[cfg(feature = "std")]
    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
    pub fn read_limited<T: std::io::Read + Sized>(
        reader: &mut crate::io::LimitedReader<T>,
        start_ip_number: IpNumber,
    ) -> Result<(Ipv4Extensions, IpNumber), err::ip_auth::HeaderLimitedReadError> {
        use ip_number::*;
        if AUTH == start_ip_number {
            let header = IpAuthHeader::read_limited(reader)?;
            let next_ip_number = header.next_header;
            Ok((Ipv4Extensions { auth: Some(header) }, next_ip_number))
        } else {
            Ok((Default::default(), start_ip_number))
        }
    }

    /// Write the extensions to the writer.
    #[cfg(feature = "std")]
    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
    pub fn write<T: std::io::Write + Sized>(
        &self,
        writer: &mut T,
        start_ip_number: IpNumber,
    ) -> Result<(), err::ipv4_exts::HeaderWriteError> {
        use err::ipv4_exts::{ExtsWalkError::*, HeaderWriteError::*};
        use ip_number::*;
        match self.auth {
            Some(ref header) => {
                if AUTH == start_ip_number {
                    header.write(writer).map_err(Io)
                } else {
                    Err(Content(ExtNotReferenced {
                        missing_ext: IpNumber::AUTHENTICATION_HEADER,
                    }))
                }
            }
            None => Ok(()),
        }
    }

    ///Length of the all present headers in bytes.
    pub fn header_len(&self) -> usize {
        if let Some(ref header) = self.auth {
            header.header_len()
        } else {
            0
        }
    }

    /// Sets all the next_header fields of the headers based on the adviced default order
    /// with the given protocol number as last "next header" value. The return value is the protocol
    /// number of the first existing extension header that should be entered in the ipv4 header as
    /// protocol_number.
    ///
    /// If no extension headers are present the value of the argument is returned.
    pub fn set_next_headers(&mut self, last_protocol_number: IpNumber) -> IpNumber {
        use ip_number::*;

        let mut next = last_protocol_number;

        if let Some(ref mut header) = self.auth {
            header.next_header = next;
            next = AUTH;
        }

        next
    }

    /// Return next header based on the extension headers and
    /// the first ip protocol number.
    ///
    /// In case a header is never referenced a
    /// [`err::ipv4_exts::ExtsWalkError::ExtNotReferenced`] is returned.
    pub fn next_header(&self, first_next_header: IpNumber) -> Result<IpNumber, ExtsWalkError> {
        use ip_number::*;
        if let Some(ref auth) = self.auth {
            if first_next_header == AUTH {
                Ok(auth.next_header)
            } else {
                Err(ExtsWalkError::ExtNotReferenced {
                    missing_ext: IpNumber::AUTHENTICATION_HEADER,
                })
            }
        } else {
            Ok(first_next_header)
        }
    }

    /// Returns true if no IPv4 extension header is present (all fields `None`).
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.auth.is_none()
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::ip_number::*;
    use crate::test_gens::*;
    use alloc::vec::Vec;
    use proptest::prelude::*;
    use std::io::Cursor;

    #[test]
    fn from_slice() {
        let auth_header = IpAuthHeader::new(UDP, 0, 0, &[]).unwrap();

        let buffer = {
            let mut buffer = Vec::with_capacity(auth_header.header_len());
            auth_header.write(&mut buffer).unwrap();
            buffer.push(1);
            buffer.push(2);
            buffer
        };

        // no auth header
        {
            let (header, next, rest) = Ipv4Extensions::from_slice(TCP, &buffer).unwrap();
            assert!(header.auth.is_none());
            assert_eq!(TCP, next);
            assert_eq!(rest, &buffer);
        }

        // with auth header
        {
            let (actual, next, rest) = Ipv4Extensions::from_slice(AUTH, &buffer).unwrap();
            assert_eq!(actual.auth.unwrap(), auth_header);
            assert_eq!(UDP, next);
            assert_eq!(rest, &buffer[auth_header.header_len()..]);
        }

        // too small
        {
            use err::ip_auth::HeaderSliceError::Len;
            const AUTH_HEADER_LEN: usize = 12;
            assert_eq!(
                Ipv4Extensions::from_slice(AUTH, &buffer[..auth_header.header_len() - 1])
                    .unwrap_err(),
                Len(err::LenError {
                    required_len: AUTH_HEADER_LEN,
                    len: auth_header.header_len() - 1,
                    len_source: LenSource::Slice,
                    layer: err::Layer::IpAuthHeader,
                    layer_start_offset: 0,
                })
            );
        }
    }

    proptest! {
        #[test]
        fn from_slice_lax(auth in ip_auth_any()) {
            use crate::ip_number::{UDP, AUTHENTICATION_HEADER};
            use crate::err::{*, ip_auth::HeaderSliceError::Len};

            // normal read
            {
                let data = auth.to_bytes();

                let (ipv4_exts, next_ip_num, next_data, err) =
                    Ipv4Extensions::from_slice_lax(AUTHENTICATION_HEADER, &data);

                // authentication header is separated and no error occurred
                assert_eq!(ipv4_exts.auth, Some(auth.clone()));
                assert_eq!(next_ip_num, auth.next_header);
                assert_eq!(next_data, &[]);
                assert!(err.is_none());
            }
            // normal read with no extension header
            {
                let data = [0,1,2,3];
                // passing a non "ip extension header" ip number
                let (ipv4_exts, next_ip_num, next_data, err) =
                    Ipv4Extensions::from_slice_lax(UDP, &data);

                // the original data gets returned as UDP is not a
                // an IP extension header
                assert!(ipv4_exts.is_empty());
                assert_eq!(next_ip_num, UDP);
                assert_eq!(next_data, &data);
                // no errors gets triggered as the data is valid
                assert!(err.is_none());
            }
            // len error during parsing
            {
                // providing not enough data
                let (ipv4_exts, next_ip_num, next_data, err) =
                    Ipv4Extensions::from_slice_lax(AUTHENTICATION_HEADER, &[]);

                // original data will be returned with no data parsed
                assert!(ipv4_exts.is_empty());
                assert_eq!(next_ip_num, AUTHENTICATION_HEADER);
                assert_eq!(next_data, &[]);
                // the error that stopped the parsing will also be returned
                assert_eq!(err, Some(Len(LenError{
                    required_len: IpAuthHeader::MIN_LEN,
                    len: 0,
                    len_source: LenSource::Slice,
                    layer: Layer::IpAuthHeader,
                    layer_start_offset: 0,
                })));
            }
        }
    }

    proptest! {
        #[test]
        fn read(auth in ip_auth_any()) {
            // None
            {
                let mut cursor = Cursor::new(&[]);
                let (actual, next) = Ipv4Extensions::read(&mut cursor, UDP).unwrap();
                assert_eq!(next, UDP);
                assert_eq!(
                    actual,
                    Ipv4Extensions{
                        auth: None,
                    }
                );
            }

            // Some sucessfull
            {
                let buffer = {
                    let mut buffer = Vec::with_capacity(auth.header_len());
                    auth.write(&mut buffer).unwrap();
                    buffer.push(1);
                    buffer
                };
                let mut cursor = Cursor::new(&buffer);
                let (actual, next) = Ipv4Extensions::read(&mut cursor, AUTH).unwrap();
                assert_eq!(auth.header_len(), cursor.position() as usize);
                assert_eq!(next, auth.next_header);
                assert_eq!(
                    actual,
                    Ipv4Extensions{
                        auth: Some(auth.clone()),
                    }
                );
            }

            // Some error
            {
                let mut cursor = Cursor::new(&[]);
                assert!(Ipv4Extensions::read(&mut cursor, AUTH).is_err());
            }
        }
    }

    #[test]
    fn write() {
        // None
        {
            let mut buffer = Vec::new();
            Ipv4Extensions { auth: None }
                .write(&mut buffer, UDP)
                .unwrap();
            assert_eq!(0, buffer.len());
        }

        // Some
        let auth_header = IpAuthHeader::new(UDP, 0, 0, &[]).unwrap();
        {
            let mut buffer = Vec::with_capacity(auth_header.header_len());
            Ipv4Extensions {
                auth: Some(auth_header.clone()),
            }
            .write(&mut buffer, AUTH)
            .unwrap();
            let (read_header, _) = IpAuthHeader::from_slice(&buffer).unwrap();
            assert_eq!(auth_header, read_header);
        }

        // Some bad start number
        {
            use crate::err::ipv4_exts::ExtsWalkError::ExtNotReferenced;

            let mut buffer = Vec::new();
            let err = Ipv4Extensions {
                auth: Some(auth_header.clone()),
            }
            .write(&mut buffer, UDP)
            .unwrap_err();
            assert_eq!(
                err.content().unwrap(),
                &ExtNotReferenced {
                    missing_ext: IpNumber::AUTHENTICATION_HEADER,
                }
            );
        }

        // Some: Write error
        {
            let mut buffer = Vec::with_capacity(auth_header.header_len() - 1);
            buffer.resize(auth_header.header_len() - 1, 0);
            let mut cursor = Cursor::new(&mut buffer[..]);
            let err = Ipv4Extensions {
                auth: Some(auth_header.clone()),
            }
            .write(&mut cursor, AUTH)
            .unwrap_err();
            assert!(err.io().is_some());
        }
    }

    #[test]
    fn header_len() {
        // None
        assert_eq!(0, Ipv4Extensions { auth: None }.header_len());

        // Some
        {
            let auth = IpAuthHeader::new(UDP, 0, 0, &[]).unwrap();
            assert_eq!(
                auth.header_len(),
                Ipv4Extensions { auth: Some(auth) }.header_len()
            );
        }
        // Some with paylaod
        {
            let auth = IpAuthHeader::new(UDP, 0, 0, &[1, 2, 3, 4]).unwrap();
            assert_eq!(
                auth.header_len(),
                Ipv4Extensions { auth: Some(auth) }.header_len()
            );
        }
    }

    #[test]
    fn set_next_headers() {
        // None
        {
            let mut exts = Ipv4Extensions { auth: None };
            assert_eq!(UDP, exts.set_next_headers(UDP));
        }

        // Some
        {
            let mut exts = Ipv4Extensions {
                auth: Some(IpAuthHeader::new(TCP, 0, 0, &[]).unwrap()),
            };
            assert_eq!(TCP, exts.auth.as_ref().unwrap().next_header);
            // change from TCP to UDP
            let re = exts.set_next_headers(UDP);
            assert_eq!(AUTH, re);
            assert_eq!(UDP, exts.auth.as_ref().unwrap().next_header);
        }
    }

    #[test]
    fn next_header() {
        // None
        {
            let exts = Ipv4Extensions { auth: None };
            assert_eq!(UDP, exts.next_header(UDP).unwrap());
        }
        // Some
        {
            let exts = Ipv4Extensions {
                auth: Some(IpAuthHeader::new(TCP, 0, 0, &[]).unwrap()),
            };

            // auth referenced
            assert_eq!(TCP, exts.next_header(AUTH).unwrap());

            // auth not referenced (error)
            use crate::err::ipv4_exts::ExtsWalkError::ExtNotReferenced;
            assert_eq!(
                ExtNotReferenced {
                    missing_ext: IpNumber::AUTHENTICATION_HEADER
                },
                exts.next_header(TCP).unwrap_err()
            );
        }
    }

    #[test]
    fn is_empty() {
        // empty
        assert!(Ipv4Extensions { auth: None }.is_empty());

        // auth
        assert_eq!(
            false,
            Ipv4Extensions {
                auth: Some(IpAuthHeader::new(ip_number::UDP, 0, 0, &[]).unwrap()),
            }
            .is_empty()
        );
    }

    proptest! {
        #[test]
        fn debug(auth in ip_auth_any()) {
            use alloc::format;

            // None
            assert_eq!(
                &format!("Ipv4Extensions {{ auth: {:?} }}", Option::<IpAuthHeader>::None),
                &format!(
                    "{:?}",
                    Ipv4Extensions {
                        auth: None,
                    }
                )
            );

            // Some
            assert_eq!(
                &format!("Ipv4Extensions {{ auth: {:?} }}", Some(auth.clone())),
                &format!(
                    "{:?}",
                    Ipv4Extensions {
                        auth: Some(auth.clone()),
                    }
                )
            );
        }
    }

    proptest! {
        #[test]
        fn clone_eq(auth in ip_auth_any()) {
            // None
            {
                let header = Ipv4Extensions{
                    auth: None,
                };
                assert_eq!(
                    header.clone(),
                    Ipv4Extensions{
                        auth: None,
                    }
                );
            }

            // Some
            {
                let header = Ipv4Extensions{
                    auth: Some(auth.clone()),
                };
                assert_eq!(
                    header.clone(),
                    Ipv4Extensions{
                        auth: Some(auth.clone()),
                    }
                );
            }
        }
    }
}
