use crate::commanded_state::CommandedState;
use crate::hardware::mcp23017::Mcp23017;
use crate::hardware::pin::PinDevice;
use crate::scheduler::{CyclicTask, TaskHandle};
use crate::state_vector::{SectionIdentifier, SectionWriter, StateVector};
use embedded_hal::digital::PinState;
use log::trace;
use std::array;
use std::fmt::{Debug, Formatter};
use std::sync::mpsc::Receiver;
use std::time::Instant;

#[derive(Clone, Debug)]
pub enum Mcp23017Message {
    SetPin {
        pin: u8,
        value: PinState,
        valid_until: Instant,
        priority: u8,
    },
}

impl<D: Debug> PinDevice for TaskHandle<Mcp23017Message, D> {
    fn set_pin(&self, pin: u8, value: PinState, valid_until: Instant, priority: u8) {
        trace!(
            "TaskHandle<Mcp23017Message, D>::set_pin(self: {self:?}, pin: {pin}, value: {value:?}, valid_until: {valid_until:?}, priority: {priority})"
        );
        // This can only fail if the other end is disconnected - which we intentionally want to
        // ignore
        let _ = self.sender.send(Mcp23017Message::SetPin {
            pin,
            value,
            valid_until,
            priority,
        });
    }
}

#[derive(Debug, Default)]
pub struct Mcp23017State {
    pub pins: [bool; 16],
}

#[derive(Debug, Clone)]
pub struct Mcp23017Data {
    id: SectionIdentifier,
}

impl Mcp23017Data {
    pub fn get_id(&self) -> SectionIdentifier {
        self.id
    }
}

pub struct Mcp23017Task<'a, M: Mcp23017> {
    mcp23017: M,
    pins: AllPins,
    state: SectionWriter<'a, Mcp23017State>,
}

impl<M: Mcp23017 + Debug> Debug for Mcp23017Task<'_, M> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "Mcp23017Task {{ mcp23017: {:?} }}", self.mcp23017)
    }
}

struct AllPins {
    pins: [PinData; 16],
}

impl AllPins {
    fn new(default_states: [PinState; 16]) -> Self {
        trace!("AllPins::new()");
        Self {
            pins: array::from_fn(|i| PinData::new(default_states[i])),
        }
    }
}

type PinData = CommandedState<PinState>;

impl<'a, M: Mcp23017 + Debug> Mcp23017Task<'a, M> {
    pub fn new(mcp23017: M, state_vector: &'a StateVector, _is_a: bool) -> Self {
        trace!("Mcp23017Task::new(mcp23017: {mcp23017:?})");
        Self {
            mcp23017,
            pins: AllPins::new(array::repeat(PinState::Low)),
            state: state_vector.create_section(Mcp23017State::default()),
        }
    }
}

impl<M: Mcp23017 + Debug> CyclicTask for Mcp23017Task<'_, M> {
    type Message = Mcp23017Message;
    type Data = Mcp23017Data;

    fn get_data(&self) -> Self::Data {
        trace!("Mcp23017Task::get_data(self: {self:?})");
        Self::Data {
            id: self.state.get_identifier(),
        }
    }

    fn step(&mut self, receiver: &Receiver<Self::Message>, step_time: Instant) {
        trace!("Mcp23017Task::step(self: {self:?}, receiver, step_time: {step_time:?})");
        let mut changed = false;

        for pin in 0u8..16u8 {
            self.pins.pins[pin as usize].evaluate(step_time);
        }

        while let Ok(recv) = receiver.try_recv() {
            match recv {
                Mcp23017Message::SetPin {
                    pin,
                    value,
                    valid_until,
                    priority,
                } => {
                    if (0u8..16u8).contains(&pin) {
                        self.pins.pins[pin as usize].insert(value, valid_until, priority);
                    }
                }
            }
        }

        for pin in 0u8..16u8 {
            let current_pin = &mut self.pins.pins[pin as usize];
            let state = **current_pin;
            if current_pin.consume_changed() {
                // This shouldn't be able to fail
                // TODO: handle error case
                let _ = self.mcp23017.set_pin(pin, state);
                changed = true;
            }
        }
        if changed {
            let _ = self.mcp23017.flush();
            self.state.update(|s| {
                s.pins = array::from_fn(|i| *self.pins.pins[i] == PinState::High);
            });
        }
    }
}
