extern crate alloc;

use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use core::cell::RefCell;
use core::convert::Infallible;
use embedded_hal::digital::v2::OutputPin;

use ross_logger::{log_debug, log_warning, Logger};
use ross_protocol::convert_packet::ConvertPacket;
use ross_protocol::event::relay::{RelayValue, RelaySetValueEvent, RelayDoubleExclusiveValue};
use ross_config::peripheral::{Peripheral, RelayPeripheral};
use ross_config::config::Config;

use crate::helper::type_helper::CanProtocol;
use crate::module::*;

pub struct RelayModuleConfig<'a> {
    pub protocol: Rc<RefCell<CanProtocol<'a>>>,
    pub logger: &'a RefCell<Logger>,
    pub config: Rc<RefCell<Config>>,
}

pub struct RelayModule<'a> {
    peripherals: BTreeMap<u32, RelayPeripheral>,
    logger: &'a RefCell<Logger>,
    relays: BTreeMap<
        u8,
        Box<dyn OutputPin<Error = Infallible>>,
    >,
}

impl<'a> RelayModule<'a> {
    pub fn add_relay(
        module: Rc<RefCell<Self>>,
        index: u8,
        mut output: Box<dyn OutputPin<Error = Infallible>>,
    ) {
        let logger = module.borrow_mut().logger;
        let relays = &mut module.borrow_mut().relays;

        output.set_low().unwrap();

        log_debug!(
            logger,
            "Relay ({}) has been added to the relay module.",
            index
        );

        relays.insert(index, output);
    }

    fn set_value(module: Rc<RefCell<Self>>, index: u8, value: &RelayValue) {
        let borrowed_module: &mut Self = &mut module.borrow_mut();
        let logger = borrowed_module.logger;
        let relays = &mut borrowed_module.relays;
        let peripherals = &borrowed_module.peripherals;

        if let Some(peripheral) = peripherals
            .get(&(index as u32))
            .map(|peripheral| *peripheral)
        {
            match (peripheral, value) {
                (RelayPeripheral::Single(index), RelayValue::Single(value)) => {
                    if *value {
                        if let Some(relay) = relays.get_mut(&index) {
                            relay.set_high().unwrap();
                        } else {
                            log_warning!(logger, "Received index ({:?}) does not match peripherals.", index);
                        };
                    } else {
                        if let Some(relay) = relays.get_mut(&index) {
                            relay.set_low().unwrap();
                        } else {
                            log_warning!(logger, "Received index ({:?}) does not match peripherals.", index);
                        };
                    }
                }
                (RelayPeripheral::DoubleExclusive(index1, index2), RelayValue::DoubleExclusive(value)) => {
                    match value {
                        RelayDoubleExclusiveValue::FirstChannelOn => {
                            if let Some(relay) = relays.get_mut(&index2) {
                                relay.set_low().unwrap();
                            } else {
                                log_warning!(logger, "Received index ({:?}) does not match peripherals.", index2);
                            };

                            if let Some(relay) = relays.get_mut(&index1) {
                                relay.set_high().unwrap();
                            } else {
                                log_warning!(logger, "Received index ({:?}) does not match peripherals.", index1);
                            };
                        },
                        RelayDoubleExclusiveValue::SecondChannelOn => {
                            if let Some(relay) = relays.get_mut(&index1) {
                                relay.set_low().unwrap();
                            } else {
                                log_warning!(logger, "Received index ({:?}) does not match peripherals.", index1);
                            };
                            
                            if let Some(relay) = relays.get_mut(&index2) {
                                relay.set_high().unwrap();
                            } else {
                                log_warning!(logger, "Received index ({:?}) does not match peripherals.", index2);
                            };
                        }
                        RelayDoubleExclusiveValue::NoChannelOn => {
                            if let Some(relay) = relays.get_mut(&index1) {
                                relay.set_low().unwrap();
                            } else {
                                log_warning!(logger, "Received index ({:?}) does not match peripherals.", index1);
                            };
                            
                            if let Some(relay) = relays.get_mut(&index2) {
                                relay.set_low().unwrap();
                            } else {
                                log_warning!(logger, "Received index ({:?}) does not match peripherals.", index2);
                            };
                        }
                    }
                }
                (_, _) => {
                    log_warning!(
                        logger,
                        "Received index ({:?}) does not match peripherals.",
                        index
                    );
                }
            }
        } else {
            log_warning!(
                logger,
                "Received index ({:?}) does not match peripherals.",
                index
            );
        }
    }
}

impl<'a> Module<'a> for RelayModule<'a> {
    fn new<'b>(config: ModuleConfig<'a, 'b>) -> Rc<RefCell<Self>> {
        let config = match config {
            ModuleConfig::RelayModule(config) => config,
            _ => {
                panic!("Wrong config provided for relay module.");
            }
        };

        let protocol = config.protocol;
        let logger = config.logger;

        let peripherals = config
            .config
            .borrow_mut()
            .peripherals
            .drain_filter(|_, peripheral| matches!(peripheral, Peripheral::Relay(_)))
            .filter_map(|peripheral| {
                if let (index, Peripheral::Relay(peripheral)) = peripheral {
                    Some((index, peripheral))
                } else {
                    None
                }
            })
            .collect();

        let module = Rc::new(RefCell::new(Self {
            peripherals,
            relays: BTreeMap::new(),
            logger,
        }));

        let module_clone = Rc::clone(&module);
        protocol
            .borrow_mut()
            .add_packet_handler(
                Box::new(move |packet, _protocol| {
                    if let Ok(event) = RelaySetValueEvent::try_from_packet(&packet) {
                        log_debug!(
                            logger,
                            "Received `relay_set_value_event` ({:?}).",
                            event
                        );

                        Self::set_value(
                            Rc::clone(&module_clone),
                            event.index,
                            &event.value,
                        );
                    }
                }),
                false,
            )
            .unwrap();

        log_debug!(logger, "Relay module initialized.");

        module
    }

    fn tick(_module: Rc<RefCell<Self>>, _current_time: &mut u32) {}
}
