use crate::wire::{Code, PPPPayload, Packet, Payload, ProtocolType};

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum State {
    Closed,
    ReqSent,
    Opened,
}
pub struct PAP<'a> {
    state: State,
    id: u8,

    username: &'a [u8],
    password: &'a [u8],
}

impl<'a> PAP<'a> {
    pub fn new(username: &'a [u8], password: &'a [u8]) -> Self {
        assert!(username.len() <= u8::MAX as usize);
        assert!(password.len() <= u8::MAX as usize);
        Self {
            state: State::Closed,
            id: 1,
            username,
            password,
        }
    }

    pub fn state(&self) -> State {
        self.state
    }

    pub fn open(&mut self) -> Packet<'_> {
        assert!(self.state == State::Closed);
        self.state = State::ReqSent;
        self.send_configure_request()
    }

    pub fn close(&mut self) {
        self.state = State::Closed;
    }

    pub fn handle(&mut self, pkt: &mut [u8], mut tx: impl FnMut(Packet<'_>)) {
        if pkt.len() < 6 {
            warn!("PAP packet too short");
            return;
        }
        let code = Code::from(pkt[2]);
        let _id = pkt[3];
        let len = u16::from_be_bytes(pkt[4..6].try_into().unwrap()) as usize;
        if len > pkt.len() {
            warn!("PAP packet len too short");
            return;
        }
        let _pkt = &mut pkt[..len + 2];

        debug!("PAP: rx {:?}", code);
        let old_state = self.state;
        match (code, self.state) {
            (Code::ConfigureAck, State::ReqSent) => self.state = State::Opened,
            (Code::ConfigureNack, State::ReqSent) => tx(self.send_configure_request()),
            _ => {}
        }

        if old_state != self.state {
            debug!("PAP: state {:?} -> {:?}", old_state, self.state);
        }
    }

    fn next_id(&mut self) -> u8 {
        self.id = self.id.wrapping_add(1);
        self.id
    }

    fn send_configure_request(&mut self) -> Packet<'a> {
        debug!("PAP: tx {:?}", Code::ConfigureReq);
        Packet {
            proto: ProtocolType::PAP,
            payload: Payload::PPP(
                Code::ConfigureReq,
                self.next_id(),
                PPPPayload::PAP(self.username, self.password),
            ),
        }
    }
}
