//! Runtime representation for the catalogued abstract packet compatibility type.

use libremetaverse_structured_data::{OSD, OSDMap, OSDParser};
use libremetaverse_types::Error;

use crate::interfaces::IByteBufferPool;
use crate::packet_catalog::{self, BlockRepetition, PacketType};
use crate::packets::{Header, Packet};

fn body(packet: &Packet) -> Result<&OSDMap, Error> {
    packet.native_body.as_ref().ok_or(Error::InvalidOperation)
}

fn assign_body(packet: &mut Packet, body: OSDMap) -> Result<(), Error> {
    let descriptor =
        packet_catalog::descriptor_by_type(packet.type_).ok_or(Error::InvalidOperation)?;
    let validated = packet_catalog::build_packet_from_osd(descriptor.name, &body)?
        .ok_or(Error::InvalidOperation)?;
    packet.native_payload_length = validated.native_payload_length;
    packet.native_body = Some(body);
    Ok(())
}

pub(crate) fn packet_to_osd(packet: &Packet) -> Result<OSDMap, Error> {
    Ok(body(packet)?.clone())
}

pub(crate) fn packet_from_osd(packet: &mut Packet, osd: OSDMap) -> Result<(), Error> {
    assign_body(packet, osd)
}

pub(crate) fn get_llsd(packet: &Packet) -> Result<OSD, Error> {
    let descriptor =
        packet_catalog::descriptor_by_type(packet.type_).ok_or(Error::InvalidOperation)?;
    let mut wrapper = std::collections::HashMap::with_capacity(1);
    wrapper.insert(
        descriptor.name.to_owned(),
        OSD::Map(body(packet)?.snapshot()),
    );
    Ok(OSD::Map(wrapper))
}

pub(crate) fn from_llsd(osd: OSD) -> Result<Option<Packet>, Error> {
    let OSD::Map(wrapper) = osd else {
        return Ok(None);
    };
    let mut entries = wrapper.into_iter().collect::<Vec<_>>();
    entries.sort_by(|left, right| left.0.cmp(&right.0));
    for (name, value) in entries {
        if let OSD::Map(values) = value {
            let body = OSDMap::new_with_dictionary(values)?;
            return packet_catalog::build_packet_from_osd(&name, &body);
        }
    }
    Ok(None)
}

pub(crate) fn from_binary(binary: Vec<u8>) -> Result<Option<Packet>, Error> {
    from_llsd(OSDParser::deserialize_llsd_binary_with_bytes(binary)?)
}

pub(crate) fn from_xml_string(xml: String) -> Result<Option<Packet>, Error> {
    from_llsd(OSDParser::deserialize_llsd_xml_with_string(xml)?)
}

pub(crate) fn to_binary(packet: &Packet) -> Result<Vec<u8>, Error> {
    OSDParser::serialize_llsd_binary_with_osd(get_llsd(packet)?)
}

pub(crate) fn to_xml_string(packet: &Packet) -> Result<String, Error> {
    OSDParser::serialize_llsd_xml_string(get_llsd(packet)?)
}

pub(crate) fn to_bytes(packet: &Packet) -> Result<Vec<u8>, Error> {
    packet_catalog::encode_packet_body(
        packet.type_,
        crate::packet_wire::clone_header(&packet.header),
        body(packet)?,
    )
}

pub(crate) fn to_bytes_multiple(packet: &Packet) -> Result<Vec<Vec<u8>>, Error> {
    packet_catalog::encode_packet_body_multiple(
        packet.type_,
        crate::packet_wire::clone_header(&packet.header),
        body(packet)?,
    )
}

pub(crate) fn to_bytes_pooled(
    packet: &Packet,
    pool: &dyn IByteBufferPool,
    size: &mut i32,
) -> Result<Vec<u8>, Error> {
    let encoded = to_bytes(packet)?;
    *size = i32::try_from(encoded.len()).map_err(|_| Error::Argument)?;
    copy_to_pool(pool, &encoded)
}

pub(crate) fn to_bytes_multiple_pooled(
    packet: &Packet,
    pool: &dyn IByteBufferPool,
    sizes: &mut Vec<i32>,
) -> Result<Vec<Vec<u8>>, Error> {
    let encoded = to_bytes_multiple(packet)?;
    sizes.clear();
    sizes
        .try_reserve_exact(encoded.len())
        .map_err(|_| Error::InvalidOperation)?;
    let mut output = Vec::new();
    output
        .try_reserve_exact(encoded.len())
        .map_err(|_| Error::InvalidOperation)?;
    for bytes in encoded {
        sizes.push(i32::try_from(bytes.len()).map_err(|_| Error::Argument)?);
        output.push(copy_to_pool(pool, &bytes)?);
    }
    Ok(output)
}

fn copy_to_pool(pool: &dyn IByteBufferPool, encoded: &[u8]) -> Result<Vec<u8>, Error> {
    let minimum = i32::try_from(encoded.len()).map_err(|_| Error::Argument)?;
    let mut buffer = pool.lease_bytes(minimum)?;
    if buffer.len() < encoded.len() {
        buffer
            .try_reserve_exact(encoded.len() - buffer.len())
            .map_err(|_| Error::InvalidOperation)?;
        buffer.resize(encoded.len(), 0);
    }
    buffer[..encoded.len()].copy_from_slice(encoded);
    Ok(buffer)
}

pub(crate) fn from_bytes_with_header(
    packet: &mut Packet,
    header: Header,
    bytes: &[u8],
    position: &mut i32,
    packet_end: &mut i32,
) -> Result<(), Error> {
    let packet_type = packet_catalog::packet_type(header.id, header.frequency);
    let native_body = packet_catalog::decode_packet_body(
        packet_type,
        crate::packet_wire::clone_header(&header),
        bytes,
        position,
        packet_end,
    )?;
    assign_decoded(packet, header, packet_type, native_body)
}

pub(crate) fn from_bytes(
    packet: &mut Packet,
    bytes: &[u8],
    position: &mut i32,
    packet_end: &mut i32,
    zero_buffer: Option<&mut [u8]>,
) -> Result<(), Error> {
    let header = crate::packet_wire::decode_header(bytes, position, packet_end)?;
    let packet_type = packet_catalog::packet_type(header.id, header.frequency);
    let native_body = if header.zerocoded {
        let buffer = zero_buffer.ok_or(Error::ArgumentNull)?;
        let decoded_length = crate::packet_wire::zero_decode(
            Some(bytes),
            packet_end.checked_add(1).ok_or(Error::Argument)?,
            Some(buffer),
        )?;
        *packet_end = decoded_length.checked_sub(1).ok_or(Error::Argument)?;
        let end = usize::try_from(decoded_length).map_err(|_| Error::Argument)?;
        packet_catalog::decode_packet_body(
            packet_type,
            crate::packet_wire::clone_header(&header),
            &buffer[..end],
            position,
            packet_end,
        )?
    } else {
        packet_catalog::decode_packet_body(
            packet_type,
            crate::packet_wire::clone_header(&header),
            bytes,
            position,
            packet_end,
        )?
    };
    assign_decoded(packet, header, packet_type, native_body)
}

fn assign_decoded(
    packet: &mut Packet,
    header: Header,
    packet_type: PacketType,
    native_body: OSDMap,
) -> Result<(), Error> {
    let descriptor =
        packet_catalog::descriptor_by_type(packet_type).ok_or(Error::InvalidOperation)?;
    packet.has_variable_blocks = descriptor
        .blocks
        .iter()
        .any(|block| matches!(block.repetition, BlockRepetition::Variable));
    packet.header = header;
    packet.type_ = packet_type;
    packet.native_payload_length =
        packet_catalog::packet_payload_length(packet_type, &native_body)?;
    packet.native_body = Some(native_body);
    Ok(())
}

#[must_use]
pub(crate) fn length(packet: &Packet) -> i32 {
    let header = i32::try_from(crate::packet_wire::header_length(packet.header.frequency))
        .unwrap_or(i32::MAX);
    let acks = packet.header.ack_list.as_ref().map_or(0_i32, |acks| {
        if acks.is_empty() {
            0
        } else {
            i32::try_from(acks.len())
                .unwrap_or(i32::MAX)
                .saturating_mul(4)
                .saturating_add(1)
        }
    });
    header
        .saturating_add(packet.native_payload_length)
        .saturating_add(acks)
}

pub(crate) fn block_from_bytes(
    data: &mut Vec<u8>,
    bytes: &[u8],
    position: &mut i32,
) -> Result<(), Error> {
    let start = usize::try_from(*position).map_err(|_| Error::Argument)?;
    let source = bytes.get(start..).ok_or(Error::IndexOutOfRange)?;
    data.clear();
    data.try_reserve_exact(source.len())
        .map_err(|_| Error::InvalidOperation)?;
    data.extend_from_slice(source);
    *position = i32::try_from(bytes.len()).map_err(|_| Error::Argument)?;
    Ok(())
}

pub(crate) fn block_to_bytes(
    data: &[u8],
    bytes: &mut [u8],
    position: &mut i32,
) -> Result<(), Error> {
    let start = usize::try_from(*position).map_err(|_| Error::Argument)?;
    let end = start.checked_add(data.len()).ok_or(Error::Argument)?;
    let destination = bytes.get_mut(start..end).ok_or(Error::IndexOutOfRange)?;
    destination.copy_from_slice(data);
    *position = i32::try_from(end).map_err(|_| Error::Argument)?;
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn llsd_packet_envelope_round_trips() {
        let packet = packet_catalog::build_packet(PacketType::CloseCircuit).unwrap();
        let xml = to_xml_string(&packet).unwrap();
        let decoded = from_xml_string(xml).unwrap().unwrap();
        assert_eq!(decoded.type_, PacketType::CloseCircuit);
        assert_eq!(decoded.packet_to_osd().unwrap().count(), 0);
    }

    #[test]
    fn base_packet_wire_uses_public_header_state() {
        let mut packet = packet_catalog::build_packet(PacketType::CloseCircuit).unwrap();
        packet.header.sequence = 0x0102_0304;
        packet.header.reliable = false;
        let bytes = to_bytes(&packet).unwrap();
        assert_eq!(&bytes[1..5], &[1, 2, 3, 4]);
        assert_eq!(bytes[0] & crate::Helpers::MSG_RELIABLE, 0);
        assert_eq!(length(&packet), i32::try_from(bytes.len()).unwrap());
    }

    #[test]
    fn abstract_block_compatibility_storage_checks_bounds() {
        let mut data = Vec::new();
        let mut position = 1;
        block_from_bytes(&mut data, &[9, 8, 7], &mut position).unwrap();
        assert_eq!(data, [8, 7]);
        let mut output = [0_u8; 3];
        position = 1;
        block_to_bytes(&data, &mut output, &mut position).unwrap();
        assert_eq!(output, [0, 8, 7]);
        assert_eq!(position, 3);
    }
}
