//! The structs/enum values defined here are derived from a number of sources:
//! * SCAT's construction of MAC GSMTAP packets: https://github.com/fgsect/scat/blob/9763cb5b1dcd5ee980f5b0ead9a8d520c8c51a51/src/scat/parsers/qualcomm/diagltelogparser.py#L562-L640
//! * https://www.sharetechnote.com/html/MAC_LTE.html#MAC_PDU_Structure_RAR
//! * 3GPP's TS 36.321, mostly sections 6.1.4, 6.1.5, and 6.1.6

use deku::prelude::*;

use crate::{
    diag::diaglog::mac::SubpacketBody,
    gsmtap::{GsmtapHeader, GsmtapMessage, GsmtapType},
};
use deku::{DekuContainerWrite, DekuError};

#[derive(DekuRead, DekuWrite)]
pub struct Header {
    pub radio_type: RadioType,
    pub direction: Direction,
    pub rnti_type: RntiType,
}

#[derive(DekuRead, DekuWrite)]
#[deku(id_type = "u8")]
pub enum RadioType {
    #[deku(id = "1")]
    Fdd,
    #[deku(id = "2")]
    Tdd,
}

#[derive(DekuRead, DekuWrite)]
#[deku(id_type = "u8")]
pub enum Direction {
    #[deku(id = "0")]
    Uplink,
    #[deku(id = "1")]
    Downlink,
}

#[derive(DekuRead, DekuWrite)]
#[deku(id_type = "u8")]
pub enum RntiType {
    #[deku(id = "0")]
    No,
    #[deku(id = "1")]
    P,
    #[deku(id = "2")]
    Ra,
    #[deku(id = "3")]
    C,
    #[deku(id = "4")]
    Si,
    #[deku(id = "5")]
    Sps,
    #[deku(id = "6")]
    M,
    #[deku(id = "7")]
    Sl,
    #[deku(id = "9")]
    Sc,
    #[deku(id = "10")]
    G,
}

#[derive(DekuRead, DekuWrite)]
#[deku(endian = "big")]
pub struct ETRAPIDSubheader {
    #[deku(bits = 1)]
    pub extended: bool,
    #[deku(bits = 1)]
    pub type_field: bool,
    #[deku(bits = 6)]
    pub rapid: u8,
}

#[derive(DekuRead, DekuWrite)]
#[deku(endian = "big")]
pub struct RACHResponse {
    #[deku(pad_bits_before = "1", bits = 11)]
    pub tac: u16,
    #[deku(bits = 20)]
    pub ul_grant: u32,
    pub tc_rnti: u16,
}

pub fn mac_subpacket_to_gsmtap(
    subpacket: &SubpacketBody,
) -> Result<Option<GsmtapMessage>, DekuError> {
    match subpacket {
        SubpacketBody::RachAttempt(attempt) => {
            let (Some(msg1), Some(msg2), Some(msg3)) =
                (attempt.get_msg1(), attempt.get_msg2(), attempt.get_msg3())
            else {
                return Ok(None);
            };
            let mut payload = Vec::new();
            payload.extend(
                Header {
                    radio_type: RadioType::Fdd,
                    direction: Direction::Downlink,
                    rnti_type: RntiType::Ra,
                }
                .to_bytes()?,
            );
            payload.push(0x01); // MAC Payload Tag
            payload.extend(
                ETRAPIDSubheader {
                    extended: false,
                    type_field: true,
                    rapid: msg1.get_preamble_index() & 0b111111,
                }
                .to_bytes()?,
            );
            payload.extend(
                RACHResponse {
                    tac: msg2.ta & 0b11111111111,
                    ul_grant: msg3.get_grant(),
                    tc_rnti: msg2.tc_rnti,
                }
                .to_bytes()?,
            );
            Ok(Some(GsmtapMessage {
                header: GsmtapHeader::new(GsmtapType::LteMacFramed),
                payload,
            }))
        }
        _ => Ok(None),
    }
}

#[cfg(test)]
mod tests {
    use crate::diag::diaglog::mac::Packet;
    use crate::diag::diaglog::mac::test::mac_rach_test_packets_from_scat;
    use crate::test_util::unhexlify;

    use super::*;

    fn assert_mac_gsmtap(packet: &Packet, expected_hexstr: Option<&str>) {
        assert_eq!(packet.subpackets.len(), 1);
        let subpacket = &packet.subpackets[0];
        let result = mac_subpacket_to_gsmtap(&subpacket.body).unwrap();
        match (result, expected_hexstr) {
            (Some(msg), Some(hexstr)) => {
                let (_, data) = unhexlify(hexstr);
                // SCAT's test cases use GSMTAP v3, but we're on V2, so skip
                // their GSMTAP header and just compare the payloads
                let expected_payload = &data.into_inner().into_inner()[34..];
                assert_eq!(&msg.payload, expected_payload);
            }
            (Some(msg), None) => panic!("expected no GSMTAP message, got {msg:?}"),
            (None, Some(_)) => panic!("expected GSMTAP message, got None"),
            _ => {}
        }
    }

    #[test]
    fn test_mac_rach() {
        // test data from SCAT unit tests: https://github.com/fgsect/scat/blob/9763cb5b1dcd5ee980f5b0ead9a8d520c8c51a51/tests/test_diagltelogparser.py#L129
        let test_packets = mac_rach_test_packets_from_scat();
        assert_mac_gsmtap(
            &test_packets[0],
            Some(
                "03000009040000000000000c0000000012d53d80000000000002000400000000fffe010102015b00411c181a23",
            ),
        );
        assert_mac_gsmtap(
            &test_packets[1],
            Some(
                "03000009040000000000000c0000000012d53d80000000000002000400000000fffe010102015800b0a2b461c6",
            ),
        );
        assert_mac_gsmtap(&test_packets[2], None);
        assert_mac_gsmtap(
            &test_packets[3],
            Some(
                "03000009040000000000000c0000000012d53d80000000000002000400000ea5fffe010102014a0070e218481c",
            ),
        );
        assert_mac_gsmtap(&test_packets[4], None);
        assert_mac_gsmtap(
            &test_packets[5],
            Some(
                "03000009040000000000000c0000000012d53d80000000000002000400000d16fffe0101020153005146b45aad",
            ),
        );
    }
}
