use crate::{
    aarp::{AarpError, AarpPacket},
    aep::{AepError, AepPacket},
    atp::{AtpError, AtpPacket},
    ddp::{DdpError, DdpPacket, DdpProtocolType},
    ethertalk::{EtherTalkError, EtherTalkPhase2Frame, EtherTalkPhase2Type},
};
use thiserror::Error;

#[derive(Debug)]
pub enum NetHeader {
    Aarp(AarpPacket),
    Ddp(DdpPacket),
}

#[derive(Debug)]
pub enum TransportHeader {
    Aep(AepPacket),
    Atp(AtpPacket),
}

#[derive(Debug)]
pub struct AppleTalkHeaders {
    pub link: EtherTalkPhase2Frame,
    pub net: NetHeader,
    pub transport: Option<TransportHeader>,
    pub payload: Option<Box<[u8]>>,
}

#[derive(Error, Debug)]
pub enum AppleTalkError {
    #[error("invalid input buffer size - expected {expected:?} bytes but found {found:?}")]
    InvalidSize { expected: usize, found: usize },
    #[error("Exceeded maximum possible packet size of 65536")]
    ExceededMax,
    #[error("failed to encode/decode link header")]
    LinkHeaderError(EtherTalkError),
    #[error("failed to encode DDP header")]
    DdpError(DdpError),
    #[error("failed to encode/decode AARP header")]
    AarpError(AarpError),
    #[error("failed to encode AEP header")]
    AepError(AepError),
    #[error("failed to encode ATP header")]
    AtpError(AtpError),
}

impl AppleTalkHeaders {
    pub fn encode(mut self, buffer: &mut [u8]) -> Result<usize, AppleTalkError> {
        let net_size = match self.net {
            NetHeader::Ddp(_) => DdpPacket::LEN,
            NetHeader::Aarp(_) => AarpPacket::LEN,
        };
        let transport_size = match self.transport {
            Some(TransportHeader::Aep(ref pkt)) => pkt.len(),
            Some(TransportHeader::Atp(_)) => AtpPacket::HEADER_LEN,
            None => 0,
        };
        let total_len = EtherTalkPhase2Frame::LLC_LEN
            + net_size
            + transport_size
            + self.payload.as_ref().map_or(0, |p| p.len());

        if total_len >= u16::MAX as usize {
            return Err(AppleTalkError::ExceededMax);
        }
        self.link.len = total_len as u16;

        let mut pos = self
            .link
            .to_bytes(buffer)
            .map_err(AppleTalkError::LinkHeaderError)?;

        pos += match self.net {
            NetHeader::Aarp(pkt) => pkt.to_bytes(&mut buffer[pos..]),
            NetHeader::Ddp(pkt) => pkt
                .to_bytes(&mut buffer[pos..])
                .map_err(AppleTalkError::DdpError)?,
        };

        pos += match self.transport {
            Some(TransportHeader::Aep(pkt)) => pkt
                .to_bytes(&mut buffer[pos..])
                .map_err(AppleTalkError::AepError)?,
            Some(TransportHeader::Atp(pkt)) => pkt
                .to_bytes(&mut buffer[pos..])
                .map_err(AppleTalkError::AtpError)?,
            None => 0,
        };

        if let Some(payload) = self.payload {
            if buffer.len() - pos < payload.len() {
                return Err(AppleTalkError::InvalidSize {
                    expected: pos + payload.len(),
                    found: buffer.len(),
                });
            }
            buffer[pos..(pos + payload.len())].copy_from_slice(&payload);
            pos += payload.len();
        }

        Ok(pos)
    }

    pub fn decode(pkt: &[u8]) -> Result<AppleTalkHeaders, AppleTalkError> {
        let link = EtherTalkPhase2Frame::parse(pkt).map_err(AppleTalkError::LinkHeaderError)?;

        let net = match link.protocol {
            EtherTalkPhase2Type::Ddp => {
                let ddp_pkt = DdpPacket::parse(&pkt[EtherTalkPhase2Frame::len()..])
                    .map_err(AppleTalkError::DdpError)?;
                NetHeader::Ddp(ddp_pkt)
            }
            EtherTalkPhase2Type::Aarp => {
                let aarp_pkt = AarpPacket::parse(&pkt[EtherTalkPhase2Frame::len()..])
                    .map_err(AppleTalkError::AarpError)?;
                NetHeader::Aarp(aarp_pkt)
            }
        };

        let (transport, payload) = match net {
            NetHeader::Ddp(ref ddp) => match ddp.protocol_typ {
                DdpProtocolType::Aep => (
                    Some(TransportHeader::Aep(
                        AepPacket::parse(&pkt[EtherTalkPhase2Frame::len() + DdpPacket::LEN..])
                            .map_err(AppleTalkError::AepError)?,
                    )),
                    None,
                ),
                DdpProtocolType::Atp => {
                    let offset = EtherTalkPhase2Frame::len() + DdpPacket::LEN;
                    let atp_pkt = AtpPacket::parse(&pkt[offset..])
                        .map_err(AppleTalkError::AtpError)?;
                    let payload_offset = offset + AtpPacket::HEADER_LEN;
                    let payload = if payload_offset < pkt.len() {
                        Some(pkt[payload_offset..].to_vec().into_boxed_slice())
                    } else {
                        None
                    };
                    (Some(TransportHeader::Atp(atp_pkt)), payload)
                }
                _ => (None, None),
            },
            _ => (None, None),
        };

        Ok(AppleTalkHeaders {
            link,
            net,
            transport,
            payload,
        })
    }
}
