//! Library which implements serialization and deserialization for the packet format used by the
//! base station and all sensor/display nodes.
#![no_std]

#[cfg(test)]
extern crate std;

use core::convert::TryInto;

use crc16::{State, KERMIT};
use xxtea_nostd::{decrypt, encrypt};

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Packet {
    GetSalt,
    Salt(u64),
    Report(Report),
    GetValues(GetValues),
    Values(Values),
}

impl Packet {
    pub fn decrypt_and_decode(key: &[u8], data: &mut [u8]) -> Result<Packet, Error> {
        decrypt_cbc(key, data);
        let checksummed = &data[8..];
        let remainder = State::<KERMIT>::calculate(&checksummed);
        if remainder != 0 {
            return Err(Error::CRC);
        }
        Self::decode(&checksummed[..22])
    }

    pub fn encode_and_encrypt(&self, key: &[u8], salt: u64, data: &mut [u8]) -> bool {
        data[0..8].copy_from_slice(&salt.to_le_bytes());
        if !self.encode(&mut data[8..30]) {
            return false;
        }
        let checksum = State::<KERMIT>::calculate(&data[8..30]);
        data[30..].copy_from_slice(&checksum.to_le_bytes());
        encrypt_cbc(key, data);
        true
    }

    fn decode(data: &[u8]) -> Result<Packet, Error> {
        let type_ = data[0] & 0x1f;
        // count can be at most 7, so we do not need any checks when indexing 8-element arrays
        // below.
        let count = data[0] >> 5;
        match type_ {
            0 => Ok(Self::GetSalt),
            1 => Ok(Self::Salt({
                // The lowest 8 bit of the salt are the device ID and are filled in by the caller.
                u64::from_le_bytes(data[1..9].try_into().unwrap()) << 8
            })),
            2 => Ok(Self::Report(Report::decode(count, &data[1..])?)),
            3 => Ok(Self::GetValues(GetValues::decode(count, &data[1..])?)),
            4 => Ok(Self::Values(Values::decode(count, &data[1..])?)),
            _ => Err(Error::InvalidPacketType),
        }
    }

    fn encode(&self, data: &mut [u8]) -> bool {
        match self {
            Self::GetSalt => {
                data[0] = 0;
                true
            }
            Self::Salt(salt) => {
                data[0] = 1;
                data[1..8].copy_from_slice(&(salt >> 8 as u64).to_le_bytes()[0..7]);
                true
            }
            Self::Report(report) => {
                data[0] = (report.count << 5) | 2;
                report.encode(&mut data[1..])
            }
            Self::GetValues(get_values) => {
                data[0] = (get_values.count << 5) | 2;
                get_values.encode(&mut data[1..])
            }
            Self::Values(values) => {
                data[0] = (values.count << 5) | 2;
                values.encode(&mut data[1..])
            }
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Report {
    pub count: u8,
    pub values: [Value; 8],
}

impl Report {
    fn decode(count: u8, mut data: &[u8]) -> Result<Report, Error> {
        let mut report = Self {
            count,
            values: [Value::Invalid; 8],
        };
        for i in 0..count {
            report.values[i as usize] = Value::decode(&mut data)?;
        }
        Ok(report)
    }

    fn encode(&self, mut data: &mut [u8]) -> bool {
        for i in 0..self.count {
            let value_len = self.values[i as usize].encoded_length();
            if data.len() < value_len {
                return false;
            }
            if !self.values[i as usize].encode(data) {
                return false;
            }
            data = &mut data[value_len..];
        }
        true
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GetValues {
    pub count: u8,
    pub location: Location,
    pub types_: [ValueType; 8],
}

impl GetValues {
    fn decode(_count: u8, _data: &[u8]) -> Result<GetValues, Error> {
        // TODO
        Err(Error::InvalidPacketType)
    }

    fn encode(&self, mut _data: &[u8]) -> bool {
        // TODO
        false
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Values {
    pub count: u8,
    pub location: Location,
    pub values: [Value; 8],
}

impl Values {
    fn decode(_count: u8, _data: &[u8]) -> Result<Values, Error> {
        // TODO
        Err(Error::InvalidPacketType)
    }

    fn encode(&self, mut _data: &[u8]) -> bool {
        // TODO
        false
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Location {
    Livingroom,
    Bathroom,
    Bedroom,
    Kitchen,
    Balcony,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Value {
    Invalid,
    Time(u64),
    Temperature(i16),
    Pressure(u32),
    Humidity(u16),
}

impl Value {
    fn decode(data: &mut &[u8]) -> Result<Value, Error> {
        let type_ = data[0];
        let length = match type_ {
            0 => 9,
            1 => 3,
            2 => 5,
            3 => 3,
            _ => return Err(Error::InvalidValueType),
        };
        if data.len() < length {
            return Err(Error::TooShort);
        }
        let result = match type_ {
            0 => Self::Time(u64::from_le_bytes(data[1..9].try_into().unwrap())),
            1 => Self::Temperature(i16::from_le_bytes(data[1..3].try_into().unwrap())),
            2 => Self::Pressure(u32::from_le_bytes(data[1..5].try_into().unwrap())),
            3 => Self::Humidity(u16::from_le_bytes(data[1..3].try_into().unwrap())),
            _ => return Err(Error::InvalidValueType),
        };
        *data = &data[length..];
        Ok(result)
    }

    fn encode(&self, data: &mut [u8]) -> bool {
        match self {
            Self::Invalid => {}
            Self::Time(time) => {
                data[0] = 0;
                data[1..9].copy_from_slice(&time.to_le_bytes());
            }
            Self::Temperature(temperature) => {
                data[0] = 1;
                data[1..3].copy_from_slice(&temperature.to_le_bytes());
            }
            Self::Pressure(pressure) => {
                data[0] = 2;
                data[1..5].copy_from_slice(&pressure.to_le_bytes());
            }
            Self::Humidity(humidity) => {
                data[0] = 3;
                data[1..3].copy_from_slice(&humidity.to_le_bytes());
            }
        };
        true
    }

    fn encoded_length(&self) -> usize {
        match self {
            Self::Invalid => 0,
            Self::Time(_) => 9,
            Self::Temperature(_) => 3,
            Self::Pressure(_) => 5,
            Self::Humidity(_) => 3,
        }
    }

    pub fn split(&self) -> Option<(ValueType, i64)> {
        match self {
            Self::Invalid => None,
            Self::Time(t) => Some((ValueType::Time, *t as i64)),
            Self::Temperature(t) => Some((ValueType::Temperature, *t as i64)),
            Self::Pressure(p) => Some((ValueType::Pressure, *p as i64)),
            Self::Humidity(h) => Some((ValueType::Humidity, *h as i64)),
        }
    }

    pub fn combine(type_: ValueType, value: i64) -> Value {
        match type_ {
            ValueType::Time => Self::Time(value as u64),
            ValueType::Temperature => Self::Temperature(value as i16),
            ValueType::Pressure => Self::Pressure(value as u32),
            ValueType::Humidity => Self::Humidity(value as u16),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ValueType {
    Time,
    Temperature,
    Pressure,
    Humidity,
}

fn encrypt_cbc(key: &[u8], data: &mut [u8]) {
    for i in 0..data.len() / 8 - 1 {
        let (prev, block) = data[i * 8..i * 8 + 16].split_at_mut(8);
        for j in 0..8 {
            block[j] ^= prev[j];
        }
        encrypt(&key, block);
    }
}

fn decrypt_cbc(key: &[u8], data: &mut [u8]) {
    let mut blocks = data.rchunks_mut(8).peekable();
    loop {
        let block = blocks.next().unwrap();
        let prev_block = match blocks.peek() {
            Some(b) => b,
            None => break,
        };
        decrypt(&key, block);

        for i in 0..8 {
            block[i] ^= prev_block[i];
        }
    }
}

#[derive(Debug, Copy, Clone)]
pub enum Error {
    CRC,
    InvalidPacketType,
    InvalidValueType,
    InvalidLocation,
    TooShort,
}

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

    #[test]
    fn test_identity() {
        let packets = [Packet::GetSalt, Packet::Salt(0x12345678abcdef12)];

        let key = [
            0x12, 0x34, 0x56, 0x78, 0xab, 0xcd, 0xef, 0x12, 0x12, 0x34, 0x56, 0x78, 0xab, 0xcd,
            0xef, 0x12,
        ];

        for packet in packets.iter() {
            let mut encoded = [0u8; 32];
            packet.encode_and_encrypt(&key, 0x1234123412341234, &mut encoded);
            assert_eq!(encoded[0], 0x34);
            assert_eq!(encoded[7], 0x12);
            let decoded = Packet::decrypt_and_decode(&key, &mut encoded).unwrap();
            // TODO: Compare encoded and decoded.
        }
    }
}
