
use embedded_hal::digital::v2::OutputPin;

use crate::config::{
    SystemConfig,
    FanOutput,
};

use crate::utils::ConfigStorage;

pub enum FanState {
    On,
    Off,
}

pub struct Fan<O: OutputPin> {
    state: FanState,
    pin: O,
}

impl<O: OutputPin> Fan<O> {

    pub fn new(mut pin: O) -> Fan<O> {

        // erroring here means the pin is wrongly configured, there is nothing else to do...
        pin.set_low().map_err(|_| "fan pin configuration").unwrap();
        Fan {
            state: FanState::Off,
            pin,
        }
    }

    pub fn on(&mut self) {
        self.pin.set_high().map_err(|_| "fan pin configuration").unwrap(); 
        self.state = FanState::On;
    }

    pub fn off(&mut self) {
        self.pin.set_low().map_err(|_| "fan pin configuration").unwrap();
        self.state = FanState::Off;
    }

    pub fn state(&self) -> &FanState { &self.state }
}

pub struct SystemState<O: OutputPin> {
    config: SystemConfig,
    config_storage: ConfigStorage,
    ext_temp: Option<f32>,
    p1_temp: Option<f32>,
    p2_temp: Option<f32>,
    fan: Fan<O>,
}

impl<O: OutputPin> SystemState<O> {

    pub fn new(config: SystemConfig, config_storage: ConfigStorage, fan_control_pin: O) 
        -> SystemState<O> {

        SystemState {
            config,
            config_storage,
            ext_temp: None,
            p1_temp: None,
            p2_temp: None,
            fan: Fan::new(fan_control_pin),
        }
    }

    pub fn config(&self) -> &SystemConfig { &self.config }
    pub fn ext_temp(&self) -> Option<f32> { self.ext_temp }
    pub fn p1_temp(&self) -> Option<f32> { self.p1_temp }
    pub fn p2_temp(&self) -> Option<f32> { self.p2_temp }

    pub fn update_config(&mut self, config: SystemConfig) { 

        // remove offsets
        let ext_temp = self.ext_temp.map(|temp| temp - self.config.ext_offset.offset);
        let p1_temp = self.p1_temp.map(|temp| temp - self.config.p1_offset.offset);
        let p2_temp = self.p2_temp.map(|temp| temp - self.config.p2_offset.offset);

        // replace config
        self.config = config; 
        self.config_storage.save_config(&self.config);

        // reset fan state before recomputing fan state
        self.fan.off();

        // recompute offsets and fan state
        self.update(ext_temp, p1_temp, p2_temp);
    }

    pub fn update(&mut self, ext_temp: Option<f32>, p1_temp: Option<f32>, p2_temp: Option<f32>) {
   
        // apply offsets
        self.ext_temp = ext_temp.map(|temp| temp + self.config.ext_offset.offset);
        self.p1_temp = p1_temp.map(|temp| temp + self.config.p1_offset.offset);
        self.p2_temp = p2_temp.map(|temp| temp + self.config.p2_offset.offset);

        // update cal values
        self.config.ext_offset.raw_value = ext_temp;        
        self.config.p1_offset.raw_value = p1_temp;
        self.config.p2_offset.raw_value = p2_temp;

        // select right probe and check if data is available
        let p = match match self.config.fan_output {
            FanOutput::P1 => self.p1_temp,
            FanOutput::P2 => self.p2_temp,
        } {
            Some(temp) => temp,
            None => {
                self.fan.off();
                return;
            },
        };
        let ext = match self.ext_temp {
            Some(temp) => temp,
            None => {
                self.fan.off();
                return;
            },
        };

        // compute fan state
        match self.fan.state() {
            FanState::On => {
                if (p - ext) < self.config.min_temp_diff {
                    self.fan.off();  
                }
            },
            FanState::Off => {
                if (p - ext) > self.config.max_temp_diff {
                    self.fan.on();
                }
        }}
    }

    pub fn stop(&mut self) {
        self.fan.off();
    }

    pub fn reset(&mut self) {
        self.ext_temp = None;
        self.p1_temp = None;
        self.p2_temp = None;
        self.fan.off();
    }
}
