use crate::{ComponentId, tmtc::PacketHandler};
use cobs::{decode_in_place, encode_including_sentinels, max_encoding_length};

/// This function encodes the given packet with COBS and also wraps the encoded packet with
/// the sentinel value 0. It can be used repeatedly on the same encoded buffer by expecting
/// and incrementing the mutable reference of the current packet index. This is also used
/// to retrieve the total encoded size.
///
/// This function will return [false] if the given encoding buffer is not large enough to hold
/// the encoded buffer and the two sentinel bytes and [true] if the encoding was successfull.
///
/// ## Example
///
/// ```
/// use cobs::decode_in_place_report;
/// use satrs::encoding::{encode_packet_with_cobs};
//
/// const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5];
/// const INVERTED_PACKET: [u8; 5] = [5, 4, 3, 2, 1];
///
/// let mut encoding_buf: [u8; 32] = [0; 32];
/// let mut current_idx = 0;
/// assert!(encode_packet_with_cobs(&SIMPLE_PACKET, &mut encoding_buf, &mut current_idx));
/// assert!(encode_packet_with_cobs(&INVERTED_PACKET, &mut encoding_buf, &mut current_idx));
/// assert_eq!(encoding_buf[0], 0);
/// let dec_report = decode_in_place_report(&mut encoding_buf[1..]).expect("decoding failed");
/// assert_eq!(encoding_buf[1 + dec_report.parsed_size()], 0);
/// assert_eq!(dec_report.frame_size(), 5);
/// assert_eq!(current_idx, 16);
/// ```
pub fn encode_packet_with_cobs(
    packet: &[u8],
    encoded_buf: &mut [u8],
    current_idx: &mut usize,
) -> bool {
    let max_encoding_len = max_encoding_length(packet.len());
    if *current_idx + max_encoding_len + 2 > encoded_buf.len() {
        return false;
    }
    *current_idx += encode_including_sentinels(packet, &mut encoded_buf[*current_idx..]);
    true
}

/// This function parses a given buffer for COBS encoded packets.
///
/// Please note that, it is recommended to use [cobs::CobsDecoderOwned] or [cobs::CobsDecoder]
/// instead.
///
/// The packet structure is
/// expected to be like this, assuming a sentinel value of 0 as the packet delimiter:
///
/// 0 | ... Encoded Packet Data ... | 0 | 0 | ... Encoded Packet Data ... | 0
///
/// This function is also able to deal with broken tail packets at the end. If broken tail
/// packets are detected, they are moved to the front of the buffer, and the write index for
/// future write operations will be written to the `next_write_idx` argument.
///
/// The parser will write all packets which were decoded successfully to the given `tc_receiver`.
pub fn parse_buffer_for_cobs_encoded_packets<SendError>(
    buf: &mut [u8],
    sender_id: ComponentId,
    packet_sender: &(impl PacketHandler<Error = SendError> + ?Sized),
    next_write_idx: &mut usize,
) -> Result<u32, SendError> {
    let mut start_index_packet = 0;
    let mut start_found = false;
    let mut last_byte = false;
    let mut packets_found = 0;
    for i in 0..buf.len() {
        if i == buf.len() - 1 {
            last_byte = true;
        }
        if buf[i] == 0 {
            if !start_found && !last_byte && buf[i + 1] == 0 {
                // Special case: Consecutive sentinel values or all zeroes.
                // Skip.
                continue;
            }
            if start_found {
                let decode_result = decode_in_place(&mut buf[start_index_packet..i]);
                if let Ok(packet_len) = decode_result {
                    packets_found += 1;
                    packet_sender.handle_packet(
                        sender_id,
                        &buf[start_index_packet..start_index_packet + packet_len],
                    )?;
                }
                start_found = false;
            } else {
                start_index_packet = i + 1;
                start_found = true;
            }
        }
    }
    // Move split frame at the end to the front of the buffer.
    if start_index_packet > 0 && start_found && packets_found > 0 {
        buf.copy_within(start_index_packet - 1.., 0);
        *next_write_idx = buf.len() - start_index_packet + 1;
    }
    Ok(packets_found)
}

#[cfg(test)]
pub(crate) mod tests {
    use cobs::encode;

    use crate::{
        ComponentId,
        encoding::tests::{INVERTED_PACKET, SIMPLE_PACKET, TcCacher, encode_simple_packet},
    };

    use super::parse_buffer_for_cobs_encoded_packets;

    const PARSER_ID: ComponentId = 0x05;

    #[test]
    fn test_parsing_simple_packet() {
        let test_sender = TcCacher::default();
        let mut encoded_buf: [u8; 16] = [0; 16];
        let mut current_idx = 0;
        encode_simple_packet(&mut encoded_buf, &mut current_idx);
        let mut next_read_idx = 0;
        let packets = parse_buffer_for_cobs_encoded_packets(
            &mut encoded_buf[0..current_idx],
            PARSER_ID,
            &test_sender,
            &mut next_read_idx,
        )
        .unwrap();
        assert_eq!(packets, 1);
        let queue = test_sender.tc_queue.borrow();
        assert_eq!(queue.len(), 1);
        let packet = &queue[0];
        assert_eq!(packet.packet, &SIMPLE_PACKET);
    }

    #[test]
    fn test_parsing_consecutive_packets() {
        let test_sender = TcCacher::default();
        let mut encoded_buf: [u8; 16] = [0; 16];
        let mut current_idx = 0;
        encode_simple_packet(&mut encoded_buf, &mut current_idx);

        // Second packet
        encoded_buf[current_idx] = 0;
        current_idx += 1;
        current_idx += encode(&INVERTED_PACKET, &mut encoded_buf[current_idx..]);
        encoded_buf[current_idx] = 0;
        current_idx += 1;
        let mut next_read_idx = 0;
        let packets = parse_buffer_for_cobs_encoded_packets(
            &mut encoded_buf[0..current_idx],
            PARSER_ID,
            &test_sender,
            &mut next_read_idx,
        )
        .unwrap();
        assert_eq!(packets, 2);
        let queue = test_sender.tc_queue.borrow();
        assert_eq!(queue.len(), 2);
        let packet0 = &queue[0];
        assert_eq!(packet0.packet, &SIMPLE_PACKET);
        let packet1 = &queue[1];
        assert_eq!(packet1.packet, &INVERTED_PACKET);
    }

    #[test]
    fn test_split_tail_packet_only() {
        let test_sender = TcCacher::default();
        let mut encoded_buf: [u8; 16] = [0; 16];
        let mut current_idx = 0;
        encode_simple_packet(&mut encoded_buf, &mut current_idx);
        let mut next_read_idx = 0;
        let packets = parse_buffer_for_cobs_encoded_packets(
            // Cut off the sentinel byte at the end.
            &mut encoded_buf[0..current_idx - 1],
            PARSER_ID,
            &test_sender,
            &mut next_read_idx,
        )
        .unwrap();
        assert_eq!(packets, 0);
        let queue = test_sender.tc_queue.borrow();
        assert_eq!(queue.len(), 0);
        assert_eq!(next_read_idx, 0);
    }

    fn generic_test_split_packet(cut_off: usize) {
        let test_sender = TcCacher::default();
        let mut encoded_buf: [u8; 16] = [0; 16];
        assert!(cut_off < INVERTED_PACKET.len() + 1);
        let mut current_idx = 0;
        encode_simple_packet(&mut encoded_buf, &mut current_idx);
        // Second packet
        encoded_buf[current_idx] = 0;
        let packet_start = current_idx;
        current_idx += 1;
        let encoded_len = encode(&INVERTED_PACKET, &mut encoded_buf[current_idx..]);
        assert_eq!(encoded_len, 6);
        current_idx += encoded_len;
        // We cut off the sentinel byte, so we expecte the write index to be the length of the
        // packet minus the sentinel byte plus the first sentinel byte.
        let next_expected_write_idx = 1 + encoded_len - cut_off + 1;
        encoded_buf[current_idx] = 0;
        current_idx += 1;
        let mut next_write_idx = 0;
        let expected_at_start = encoded_buf[packet_start..current_idx - cut_off].to_vec();
        let packets = parse_buffer_for_cobs_encoded_packets(
            // Cut off the sentinel byte at the end.
            &mut encoded_buf[0..current_idx - cut_off],
            PARSER_ID,
            &test_sender,
            &mut next_write_idx,
        )
        .unwrap();
        assert_eq!(packets, 1);
        let queue = test_sender.tc_queue.borrow();
        assert_eq!(queue.len(), 1);
        assert_eq!(&queue[0].packet, &SIMPLE_PACKET);
        assert_eq!(next_write_idx, next_expected_write_idx);
        assert_eq!(encoded_buf[..next_expected_write_idx], expected_at_start);
    }

    #[test]
    fn test_one_packet_and_split_tail_packet_0() {
        generic_test_split_packet(1);
    }

    #[test]
    fn test_one_packet_and_split_tail_packet_1() {
        generic_test_split_packet(2);
    }

    #[test]
    fn test_one_packet_and_split_tail_packet_2() {
        generic_test_split_packet(3);
    }

    #[test]
    fn test_zero_at_end() {
        let test_sender = TcCacher::default();
        let mut encoded_buf: [u8; 16] = [0; 16];
        let mut next_write_idx = 0;
        let mut current_idx = 0;
        encoded_buf[current_idx] = 5;
        current_idx += 1;
        encode_simple_packet(&mut encoded_buf, &mut current_idx);
        encoded_buf[current_idx] = 0;
        current_idx += 1;
        let packets = parse_buffer_for_cobs_encoded_packets(
            // Cut off the sentinel byte at the end.
            &mut encoded_buf[0..current_idx],
            PARSER_ID,
            &test_sender,
            &mut next_write_idx,
        )
        .unwrap();
        assert_eq!(packets, 1);
        let queue = test_sender.tc_queue.borrow_mut();
        assert_eq!(queue.len(), 1);
        assert_eq!(&queue[0].packet, &SIMPLE_PACKET);
        assert_eq!(next_write_idx, 1);
        assert_eq!(encoded_buf[0], 0);
    }

    #[test]
    fn test_all_zeroes() {
        let test_sender = TcCacher::default();
        let mut all_zeroes: [u8; 5] = [0; 5];
        let mut next_write_idx = 0;
        let packets = parse_buffer_for_cobs_encoded_packets(
            // Cut off the sentinel byte at the end.
            &mut all_zeroes,
            PARSER_ID,
            &test_sender,
            &mut next_write_idx,
        )
        .unwrap();
        assert_eq!(packets, 0);
        let queue = test_sender.tc_queue.borrow();
        assert!(queue.is_empty());
        assert_eq!(next_write_idx, 0);
    }
}
