#![no_main]
#![no_std]

use arbitrary_int::u11;
use core::time::Duration;
use embassy_stm32::gpio::Output;
use spacepackets::{
    ccsds_packet_len_for_user_data_len_with_checksum, CcsdsPacketCreationError,
    CcsdsPacketCreatorWithReservedData, CcsdsPacketIdAndPsc, SpacePacketHeader,
};

pub const APID: u11 = u11::new(0x02);

#[derive(defmt::Format, serde::Serialize, serde::Deserialize, PartialEq, Eq, Clone, Copy)]
pub enum Direction {
    North,
    NorthEast,
    East,
    SouthEast,
    South,
    SouthWest,
    West,
    NorthWest,
}

impl Direction {
    pub fn switch_to_next(&mut self) -> (Self, Self) {
        let curr = *self;
        *self = match self {
            Direction::North => Direction::NorthEast,
            Direction::NorthEast => Direction::East,
            Direction::East => Direction::SouthEast,
            Direction::SouthEast => Direction::South,
            Direction::South => Direction::SouthWest,
            Direction::SouthWest => Direction::West,
            Direction::West => Direction::NorthWest,
            Direction::NorthWest => Direction::North,
        };
        (curr, *self)
    }
}

#[derive(Copy, Clone, Debug, defmt::Format, serde::Serialize, serde::Deserialize)]
pub enum Request {
    Ping,
    ChangeBlinkFrequency(Duration),
}

#[derive(Debug, defmt::Format, serde::Serialize, serde::Deserialize)]
pub struct TmHeader {
    pub tc_packet_id: Option<CcsdsPacketIdAndPsc>,
    pub uptime_millis: u32,
}

#[derive(Debug, defmt::Format, serde::Serialize, serde::Deserialize)]
pub enum Response {
    CommandDone,
}

pub fn tm_size(tm_header: &TmHeader, response: &Response) -> usize {
    ccsds_packet_len_for_user_data_len_with_checksum(
        postcard::experimental::serialized_size(tm_header).unwrap()
            + postcard::experimental::serialized_size(response).unwrap(),
    )
    .unwrap()
}

pub fn create_tm_packet(
    buf: &mut [u8],
    sp_header: SpacePacketHeader,
    tm_header: TmHeader,
    response: Response,
) -> Result<usize, CcsdsPacketCreationError> {
    let packet_data_size = postcard::experimental::serialized_size(&tm_header).unwrap()
        + postcard::experimental::serialized_size(&response).unwrap();
    let mut creator =
        CcsdsPacketCreatorWithReservedData::new_tm_with_checksum(sp_header, packet_data_size, buf)?;

    let current_index = postcard::to_slice(&tm_header, creator.packet_data_mut())
        .unwrap()
        .len();
    postcard::to_slice(&response, &mut creator.packet_data_mut()[current_index..]).unwrap();
    Ok(creator.finish())
}

pub struct Leds {
    pub north: Output<'static>,
    pub north_east: Output<'static>,
    pub east: Output<'static>,
    pub south_east: Output<'static>,
    pub south: Output<'static>,
    pub south_west: Output<'static>,
    pub west: Output<'static>,
    pub north_west: Output<'static>,
}

impl Leds {
    pub fn blink_next(&mut self, current_dir: &mut Direction) {
        let (prev, curr) = current_dir.switch_to_next();
        self.set_dir_low(prev);
        self.set_dir_high(curr);
    }

    pub fn set_dir(&mut self, dir: Direction, level: embassy_stm32::gpio::Level) {
        match dir {
            Direction::North => self.north.set_level(level),
            Direction::NorthEast => self.north_east.set_level(level),
            Direction::East => self.east.set_level(level),
            Direction::SouthEast => self.south_east.set_level(level),
            Direction::South => self.south.set_level(level),
            Direction::SouthWest => self.south_west.set_level(level),
            Direction::West => self.west.set_level(level),
            Direction::NorthWest => self.north_west.set_level(level),
        }
    }

    pub fn set_dir_low(&mut self, dir: Direction) {
        self.set_dir(dir, embassy_stm32::gpio::Level::Low);
    }

    pub fn set_dir_high(&mut self, dir: Direction) {
        self.set_dir(dir, embassy_stm32::gpio::Level::High);
    }
}

pub struct LedPinSet {
    pub pin_n: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE8>,
    pub pin_ne: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE9>,
    pub pin_e: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE10>,
    pub pin_se: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE11>,
    pub pin_s: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE12>,
    pub pin_sw: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE13>,
    pub pin_w: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE14>,
    pub pin_nw: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE15>,
}

impl Leds {
    pub fn new(pin_set: LedPinSet) -> Self {
        let led_n = Output::new(
            pin_set.pin_n,
            embassy_stm32::gpio::Level::Low,
            embassy_stm32::gpio::Speed::Medium,
        );
        let led_ne = Output::new(
            pin_set.pin_ne,
            embassy_stm32::gpio::Level::Low,
            embassy_stm32::gpio::Speed::Medium,
        );
        let led_e = Output::new(
            pin_set.pin_e,
            embassy_stm32::gpio::Level::Low,
            embassy_stm32::gpio::Speed::Medium,
        );
        let led_se = Output::new(
            pin_set.pin_se,
            embassy_stm32::gpio::Level::Low,
            embassy_stm32::gpio::Speed::Medium,
        );
        let led_s = Output::new(
            pin_set.pin_s,
            embassy_stm32::gpio::Level::Low,
            embassy_stm32::gpio::Speed::Medium,
        );
        let led_sw = Output::new(
            pin_set.pin_sw,
            embassy_stm32::gpio::Level::Low,
            embassy_stm32::gpio::Speed::Medium,
        );
        let led_w = Output::new(
            pin_set.pin_w,
            embassy_stm32::gpio::Level::Low,
            embassy_stm32::gpio::Speed::Medium,
        );
        let led_nw = Output::new(
            pin_set.pin_nw,
            embassy_stm32::gpio::Level::Low,
            embassy_stm32::gpio::Speed::Medium,
        );
        Self {
            north: led_n,
            north_east: led_ne,
            east: led_e,
            south_east: led_se,
            south: led_s,
            south_west: led_sw,
            west: led_w,
            north_west: led_nw,
        }
    }
}
