Documentation
use crate::{
  command::EmapiCommand,
  constants::{COMMAND_HEADER_LENGTH, FRAME_HEAD, FRAME_OVERHEAD_LENGTH, FRAME_TAIL, RESERVED},
  crc32,
  endian::{read_uint16, read_uint32, uint16, uint32},
  error::{EmapiError, Result},
};

pub struct FrameCodec;

impl FrameCodec {
  pub fn encode(command: &EmapiCommand) -> Result<Vec<u8>> {
    let data_length = COMMAND_HEADER_LENGTH + command.payload.len();
    if data_length > usize::from(u16::MAX) {
      return Err(EmapiError::Range {
        name: "dataLength",
        min: 0,
        max: u64::from(u16::MAX),
        value: data_length as u64,
      });
    }

    let mut data = Vec::with_capacity(data_length);
    data.push(command.command_type);
    data.push(command.parent);
    data.push(command.child);
    data.extend_from_slice(&uint16(command.payload.len() as u16));
    data.extend_from_slice(&command.payload);

    let mut frame = Vec::with_capacity(data_length + FRAME_OVERHEAD_LENGTH);
    frame.push(FRAME_HEAD);
    frame.push(RESERVED);
    frame.extend_from_slice(&uint16(data.len() as u16));
    frame.extend_from_slice(&data);
    frame.extend_from_slice(&uint32(crc32::compute(&data)));
    frame.push(FRAME_TAIL);
    Ok(frame)
  }

  pub fn decode(frame: &[u8]) -> Result<EmapiCommand> {
    if frame.len() < FRAME_OVERHEAD_LENGTH {
      return Err(EmapiError::FrameTooShort);
    }
    if frame[0] != FRAME_HEAD {
      return Err(EmapiError::MissingFrameHead);
    }
    if frame[frame.len() - 1] != FRAME_TAIL {
      return Err(EmapiError::MissingFrameTail);
    }

    let data_length = usize::from(read_uint16(frame, 2)?);
    let expected_frame_length = data_length + FRAME_OVERHEAD_LENGTH;
    if frame.len() != expected_frame_length {
      return Err(EmapiError::FrameLengthMismatch {
        expected: expected_frame_length,
        actual: frame.len(),
      });
    }
    if data_length < COMMAND_HEADER_LENGTH {
      return Err(EmapiError::CommandDataTooShort);
    }

    let data_start = 4;
    let data_end = data_start + data_length;
    let data = &frame[data_start..data_end];
    let expected_crc = read_uint32(frame, data_end)?;
    let actual_crc = crc32::compute(data);
    if expected_crc != actual_crc {
      return Err(EmapiError::BadFrameCrc {
        expected: expected_crc,
        actual: actual_crc,
      });
    }

    let payload_length = usize::from(read_uint16(data, 3)?);
    let expected_data_length = COMMAND_HEADER_LENGTH + payload_length;
    if data.len() != expected_data_length {
      return Err(EmapiError::PayloadLengthMismatch {
        expected: payload_length,
        actual: data.len() - COMMAND_HEADER_LENGTH,
      });
    }

    Ok(EmapiCommand::new(
      data[0],
      data[1],
      data[2],
      data[COMMAND_HEADER_LENGTH..].to_vec(),
    ))
  }
}