//! # Configuration protocol
//!
//! Configuration and runtime management is done over a separate TCP port, to
//! simplify management. It follows a simple binary protocol, described here.
//!
//! The protocol is packet-based, 2 bytes start sequence, 1 byte command, 4 byte
//! data, 1 byte CRC. CRC includes the start sequence, and is MAXIM-DOW. Chosen
//! because the crc python package had it as well.
//!
//! The 4 bytes of data are little-endian.
//! 
//! For the settings that can be set, see [Settings].
//!
//! ## Sending a command
//!
//! The command is simply the index of the setting to read or write, with the
//! top bit set to indicate a write. Set the value to 0 during a read.
//!
//! ## Receiving a response
//!
//! The command is the index of the setting given, with the top bit set if it's
//! an error. The value is either the value saved in the setting, or an error
//! code. Error codes are defined in [ErrorCodes]

/// Start sequence to indicate the beginning of a packet.
const START_SEQUENCE: [u8; 2] = [0xDE, 0xAD];

/// Possible return values from a command
#[derive(Clone, Copy, Debug)]
pub enum ErrorCodes {
    /// Command accepted
    Success = 0,
    /// Value was not valid for the provided setting
    ValueOutOfRange = 1,
    /// Setting index provided does not exist
    InvalidSetting = 2,
}

/// Setting indices
#[derive(Clone, Copy, Debug)]
pub enum Settings {
    /// Trigger threshold (in ADC counts, peak to peak is 1024)
    TriggerThreshold = 0,
    /// Number of samples that diff must be higher than threshold to trigger
    TriggerPeriod = 1,
    /// Value to decay difference every DecayPeriod cycles
    DecayValue = 2,
    /// The number of cycles between every decay
    DecayPeriod = 3,
    /// Gain setting of preprocessor (value in dB)
    Gain = 4,
    /// Center frequency to set preprocessor filter to (value in Hz)
    CenterFreq = 5,
    /// Sampling enabled, 1 to enable, 0 to disable
    SamplingEnabled = 6,
}

#[derive(Clone, Copy, Debug)]
pub enum Error {
    /// Start sequence incorrect, no packet
    BadStartSequence,
    /// CRC was bad
    BadCrc,
    /// State machine error
    StateMachineError,
    /// No packet found in data
    NoPacket,
}

impl TryFrom<u8> for Settings {
    type Error = ();

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(Self::TriggerThreshold),
            1 => Ok(Self::TriggerPeriod),
            2 => Ok(Self::DecayValue),
            3 => Ok(Self::DecayPeriod),
            4 => Ok(Self::Gain),
            5 => Ok(Self::CenterFreq),
            6 => Ok(Self::SamplingEnabled),

            _ => Err(()),
        }
    }
}

pub struct CommandPacket {
    pub is_write: bool,
    pub setting: u8,
    pub value: u32,
}

pub struct ResponsePacket {
    pub is_error: bool,
    pub setting: u8,
    pub value: u32,
}

/// Serialize a valid response
pub fn serialize_response_value(setting: Settings, value: u32) -> [u8; 8] {
    serialize_response(setting as u8, value)
}

/// Serialize an error response
pub fn serialize_response_error(setting: u8, error: ErrorCodes) -> [u8; 8] {
    serialize_response(0x80 | setting, error as u32)
}

fn serialize_response(command: u8, value: u32) -> [u8; 8] {
    let mut crc = crc_any::CRCu8::crc8maxim();
    let mut buf = [0u8; 8];

    // Construct packet data
    &buf[0..2].copy_from_slice(&START_SEQUENCE);
    buf[2] = command;
    for i in 0..4 {
        buf[3 + i] = (value >> (8 * i)) as u8;
    }

    // Run CRC and append value
    crc.digest(&buf[0..7]);
    buf[7] = crc.get_crc();

    buf
}

pub struct PacketParser {
    packet_index: u8,
    crc: crc_any::CRCu8,

    is_write: bool,
    setting: u8,
    value: u32,
}

impl PacketParser {
    pub fn new() -> Self {
        Self {
            packet_index: 0,
            crc: crc_any::CRCu8::crc8maxim(),

            is_write: false,
            setting: 0,
            value: 0,
        }
    }

    /// Try and parse a packet from a received buffer. Returns Some(packet) if a
    /// packet was found, and the number of bytes processed from the buffer. Bytes
    /// processed may be smaller than the buffer length if a packet was found.
    pub fn try_parse(&mut self, buf: &[u8]) -> (Result<CommandPacket, Error>, usize) {
        let mut bytes_processed = 0;
        for byte in buf {
            bytes_processed += 1;
            match self.process_byte(*byte) {
                Ok(packet) => return (Ok(packet), bytes_processed),

                // We don't care to pass errors to the user
                // TODO log
                _ => (),
            }
        }

        (Err(Error::NoPacket), bytes_processed)
    }

    /// Process incoming byte using a simple state machine.
    pub fn process_byte(&mut self, byte: u8) -> Result<CommandPacket, Error> {
        match self.packet_index {
            // Check the start sequence
            0 => {
                if byte != START_SEQUENCE[0] {
                    self.reset();
                    return Err(Error::BadStartSequence);
                }
            }
            1 => {
                if byte != START_SEQUENCE[1] {
                    self.reset();
                    return Err(Error::BadStartSequence);
                }
            }

            // Save data
            2 => {
                self.is_write = byte & 0x80 != 0;
                self.setting = byte & 0x7F;
            }
            3..=6 => {
                self.value |= (byte << (8 * (self.packet_index - 3))) as u32;
            }

            // Check CRC
            7 => {
                if self.crc.get_crc() == byte {
                    return Ok(CommandPacket {
                        is_write: self.is_write,
                        setting: self.setting,
                        value: self.value,
                    });
                } else {
                    self.reset();
                    return Err(Error::BadCrc);
                }
            }

            // Should be unreachable, just reset
            _ => {
                self.reset();
                return Err(Error::StateMachineError);
            }
        }

        // Increment index
        self.packet_index += 1;
        // Process CRC (sucks I can't just give it one byte)
        self.crc.digest(&[byte]);

        Err(Error::NoPacket)
    }

    /// Reset values to start parsing a packet again
    fn reset(&mut self) {
        self.packet_index = 0;
        self.crc = crc_any::CRCu8::crc8maxim();
        self.value = 0;
    }
}
