/**
 * Provide functions to manipulate raw memory from SSV library.
 */

// TODO : Est-ce qu'on pourrait/devrait définir un type custom pour représenter les tableaux de bytes ?

use std::convert::TryFrom;

#[derive(PartialEq, Debug)]
struct ElementSize {
    pub size: usize,
    pub pad: usize,
}

impl TryFrom<&[u8]> for ElementSize {
    type Error = &'static str;

    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
            /* Longueur:
        *   - si le bit de poids fort du premier octet est à 0, la longueur est codée sur un octet
        *   - si le bit de poids fort du premier octet est à 1, les 7 bits de poids faible codent le nombre d'octets utilisés pour coder la longueur
        */
        if bytes.len() == 0 {
            return Err("Empty bytes input");
        }

        let mut element_size = ElementSize { size: 0, pad: 1 };
        if bytes[0] & 0b1000_0000 == 0 {
            // Size coded on 1 byte
            element_size.size = bytes[0] as usize;
        } else {
            // Size coded on N bytes
            // N are the 7 lower bits of the first byte
            let size_bytes_len = (bytes[0] & 0b0111_1111) as usize;
            if size_bytes_len > bytes.len() - 1 {
                return Err("Invalid memory: not enough bytes to read the size");
            } else if size_bytes_len > 4 {
                return Err("Invalid memory: size is too big");
            }
            let size_bytes = &bytes[1..1 + size_bytes_len];

            // u32::from_be_bytes() requires a 4 bytes array
            let mut padded_bytes = [0u8; 4];
            padded_bytes[size_bytes_len..].copy_from_slice(&size_bytes);

            element_size.size = u32::from_be_bytes(padded_bytes) as usize;
            element_size.pad += size_bytes_len;
        }
        Ok(element_size)
    }
}

#[derive(Debug)]
pub struct Block<'a> {
    pub id: u16,
    pub size: usize,
    pub content: Vec<Field<'a>>,
}

impl<'a> From<&'a [u8]> for Block<'a> {
    fn from(bytes: &'a [u8]) -> Self {
        let mut offset = 0;
        let id = u16::from_be_bytes(bytes[..2].try_into().unwrap());
        offset += 2;
        let ElementSize { size: block_size, pad } = bytes[2..].try_into().unwrap();
        offset += pad;
        let raw_content = &bytes[offset..];
        let mut field_offset = 0;
        // While there is still content to read, parse Fields
        let mut content = Vec::new();
        while field_offset < block_size {
            let field: Field<'a> = raw_content[field_offset..].into();
            field_offset += field.size;
            content.push(field);
        }
        Block {
            id,
            size: offset + block_size,
            content,
        }
    }
}

#[derive(Debug)]
pub struct Field<'a> {
    pub size: usize,
    pub content: &'a [u8],
}

impl<'a> From<&'a [u8]> for Field<'a> {
    fn from(bytes: &'a [u8]) -> Self {
        let ElementSize { size, pad } = bytes.try_into().unwrap();
        let contenu = &bytes[pad..pad+size];
        Field {
            size: pad+size,
            content: contenu,
        }
    }
}

pub fn decode_ssv_memory(bytes: &[u8], size: usize) -> Vec<Block> {
    let mut blocks: Vec<Block> = Vec::new();
    let mut offset = 0;
    while offset < size {
        let block: Block = bytes[offset..].into();
        offset += block.size;
        blocks.push(block);
    }
    blocks
}

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

    #[test]
    fn short_size() {
        let bytes: &[u8] = &[0b_0000_0001_u8];
        let element_size: ElementSize = bytes.try_into().unwrap();
        assert_eq!(element_size.size, 1);
        assert_eq!(element_size.pad, 1);

        let bytes: &[u8] = &[0b_0100_0000_u8];
        let element_size: ElementSize = bytes.try_into().unwrap();
        assert_eq!(element_size.size, 64);
        assert_eq!(element_size.pad, 1);
    }

    #[test]
    fn long_size() {
        let bytes: &[u8] = &[0b_1000_0010_u8, 0b_0000_0001_u8, 0b_0100_0000_u8];
        let element_size: ElementSize = bytes.try_into().unwrap();
        assert_eq!(element_size.size, 320);
        assert_eq!(element_size.pad, 3);
    }

    #[test]
    fn null_size() {
        let bytes: &[u8] = &[];
        let result: Result<ElementSize, &str> = bytes.try_into();
        assert_eq!(
            result,
            Err("Empty bytes input"),
        );
    }

    #[test]
    fn invalid_memory() {
        let bytes: &[u8] = &[0b_1000_0001_u8];
        let result: Result<ElementSize, &str> = bytes.try_into();
        assert_eq!(
            result,
            Err("Invalid memory: not enough bytes to read the size"),
        );

        let bytes: &[u8] = &[0b_1000_0010_u8, 1];
        let result: Result<ElementSize, &str> = bytes.try_into();
        assert_eq!(
            result,
            Err("Invalid memory: not enough bytes to read the size"),
        );

        let bytes: &[u8] = &[0b_1000_0101_u8, 1, 1, 1, 1, 1];
        let result: Result<ElementSize, &str> = bytes.try_into();
        assert_eq!(
            result,
            Err("Invalid memory: size is too big"),
        );
    }
}

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

    #[test]
    fn short_size() {
        let bytes: &[u8] = &[51,
            1, 48, 1, 56, 11, 57, 57, 55, 48, 48,
            53, 50, 52, 49, 57, 52, 1, 52, 2, 50,
            50, 17, 80, 72, 65, 82, 77, 65, 67, 73,
            69, 78, 48, 48, 53, 50, 52, 49, 57, 9,
            70, 82, 65, 78, 67, 79, 73, 83, 69, 1,
            84,
        ];
        let element: Field = bytes.try_into().unwrap();
        assert_eq!(element.size, 52);
        assert_eq!(element.content[..5], [1, 48, 1, 56, 11]);
    }

    #[test]
    fn long_size() {
        let mut bytes_vec = vec![0b_1000_0010_u8,
            0b_0000_0001_u8, 0b_0000_0000_u8, // size = 256
        ];
        // Add 256 bytes to the content
        bytes_vec.append(&mut vec![1; 256]);
        let bytes: &[u8] = &bytes_vec;
        let element: Field = bytes.try_into().unwrap();
        assert_eq!(element.size, 259);
        assert_eq!(element.content.len(), 256);
    }
}

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

    #[test]
    fn test_francoise_pharmacien0052419_partial_block_1() {
        let bytes: &[u8] = &[
            1, 48,
            1, 56,
            11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52,
        ];
        
        let field1: Field = bytes.into();
        assert_eq!(field1.size, 2);
        assert_eq!(field1.content, &[48]);

        let field2: Field = bytes[field1.size..].into();
        assert_eq!(field2.size, 2);
        assert_eq!(field2.content, &[56]);

        let field3: Field = bytes[field1.size + field2.size..].into();
        assert_eq!(field3.size, 12);
        assert_eq!(field3.content, &[57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52]);
    }

    #[test]
    fn test_francoise_pharmacien0052419() {
        let bytes: &[u8] = &[
        0, 1, 51, // 3
            1, 48, // 2
            1, 56, // 2
            11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, // 12
            1, 52, // 2
            2, 50, 50, // 3
            17, 80, 72, 65, 82, 77, 65, 67, 73, 69, 78, 48, 48, 53, 50, 52, 49, 57, // 18
            9, 70, 82, 65, 78, 67, 79, 73, 83, 69, // 10
            1, 84, // 2
        // total: 54
            
        0, 2, 83,
            1, 1,
            1, 48,
            1, 49,
            2, 56, 54,
            1, 49,
            9, 48, 66, 48, 50, 50, 49, 57, 53, 56,
            1, 56,
            24, 80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, 50, 50, 49, 57, 53,
            8, 48, 48, 50, 48, 50, 52, 49, 57,
            1, 56,
            0,
            1, 48,
            1, 49,
            2, 53, 48,
            2, 49, 48,
            2, 48, 48,
            1, 48,
            1, 48,
            1, 48,
            1, 49,
            1, 49,
        ];
        
        let first_block: Block = bytes.into();
        assert_eq!(first_block.id, 1);
        assert_eq!(first_block.size, 54);
        assert_eq!(first_block.content.len(), 8);

        let second_block: Block = bytes[first_block.size..].into();
        assert_eq!(second_block.id, 2);
        assert_eq!(second_block.size, 86);
        assert_eq!(second_block.content.len(), 21);
    }
}

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

    #[test]
    fn test_francoise_pharmacien0052419() {
        let bytes: &[u8] = &[
        0, 1, 51, // 3
            1, 48, // 2
            1, 56, // 2
            11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, // 12
            1, 52, // 2
            2, 50, 50, // 3
            17, 80, 72, 65, 82, 77, 65, 67, 73, 69, 78, 48, 48, 53, 50, 52, 49, 57, // 18
            9, 70, 82, 65, 78, 67, 79, 73, 83, 69, // 10
            1, 84, // 2
        // total: 54
            
        0, 2, 83,
            1, 1,
            1, 48,
            1, 49,
            2, 56, 54,
            1, 49,
            9, 48, 66, 48, 50, 50, 49, 57, 53, 56,
            1, 56,
            24, 80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, 50, 50, 49, 57, 53,
            8, 48, 48, 50, 48, 50, 52, 49, 57,
            1, 56,
            0,
            1, 48,
            1, 49,
            2, 53, 48,
            2, 49, 48,
            2, 48, 48,
            1, 48,
            1, 48,
            1, 48,
            1, 49,
            1, 49,
        ];
        let blocks = decode_ssv_memory(&bytes, bytes.len());
        assert_eq!(blocks.len(), 2);
    }
}