#![no_std]
#![no_main]

extern crate panic_halt;

use core::{
    cell::RefCell,
    sync::atomic::{AtomicBool, Ordering},
};

use cortex_m::interrupt::Mutex;
use cortex_m_rt::entry;

use embedded_hal::digital::{
    v1_compat::OldOutputPin,
};
 
use stm32f1xx_hal::{
    gpio::*,
    adc::{Adc, SampleTime},
    pac,
    pac::{interrupt, Interrupt, EXTI},
    prelude::*,
    timer::{Timer, CountDownTimer, Event},
    qei::{QeiOptions, SlaveMode},
    rtc::{Rtc, RtcClkLsi},
};

mod lcd_gui;
use lcd_gui::{LCDGui, GUI_TICK_SEC};

mod utils;
use utils::{
    TemperatureProbe,
    ConfigStorage,
};

mod config;
use config::SystemConfig;

mod state;
use state::SystemState;

//--------------------------------------------------------------------------------------------------
/* system config */

const AWAKE_TIMEOUT_SEC: f32 = 20.0;
const HEARTBEAT_SEC: f32 = 1.0;

const TEMPS_TICK_SEC: u32 = 30;

//--------------------------------------------------------------------------------------------------
/* interrupt variables */

// tick/heartbeat timer
static PCB_LED: Mutex<RefCell<Option<gpioc::PC13<Output<PushPull>>>>> 
    = Mutex::new(RefCell::new(None)); 
static TICK_TIMER: Mutex<RefCell<Option<CountDownTimer<pac::TIM2>>>>
    = Mutex::new(RefCell::new(None));

// button interrupt
static BUTTON_PIN: Mutex<RefCell<Option<gpiob::PB8<Input<Floating>>>>> 
    = Mutex::new(RefCell::new(None));
static BUTTON_FLAG: AtomicBool = AtomicBool::new(false);

// temps interrupt
static RTC: Mutex<RefCell<Option<Rtc::<RtcClkLsi>>>> = Mutex::new(RefCell::new(None));
static G_EXTI: Mutex<RefCell<Option<EXTI>>> = Mutex::new(RefCell::new(None));
static TEMP_FLAG: AtomicBool = AtomicBool::new(false);

//--------------------------------------------------------------------------------------------------
/* power management */
enum PowerState {
    Awake,
    Sleeping,
}

//--------------------------------------------------------------------------------------------------
/* error management */
enum ErrorState {
    NoError,
    NewError (&'static str),
    HandlingError,
}

//--------------------------------------------------------------------------------------------------
/* interrupt service routines */
#[interrupt]
fn TIM2() {
   
    static mut TICK_DIVIDER: u32 = 0;

    cortex_m::interrupt::free(|cs| {
        let _ = TICK_TIMER.borrow(cs).borrow_mut().as_mut().unwrap().clear_update_interrupt_flag();
    });

    *TICK_DIVIDER += 1;
    if *TICK_DIVIDER >= (HEARTBEAT_SEC / GUI_TICK_SEC) as u32 {
        *TICK_DIVIDER = 0;
        cortex_m::interrupt::free(|cs| {
            let _ = PCB_LED.borrow(cs).borrow_mut().as_mut().unwrap().toggle();
        });
    }
}

#[interrupt]
fn EXTI9_5() {
   
    cortex_m::interrupt::free(|cs| {
        let _ = BUTTON_PIN.borrow(cs).borrow_mut().as_mut().unwrap().clear_interrupt_pending_bit();
    });

    BUTTON_FLAG.store(true, Ordering::Release);
}

#[interrupt]
fn RTCALARM() {
    
    cortex_m::interrupt::free(|cs| {
        let _ = G_EXTI.borrow(cs).borrow_mut().as_mut().unwrap().pr.write(|w| w.pr17().set_bit());
    });

    cortex_m::interrupt::free(|cs| {
        let mut rtc = RTC.borrow(cs).borrow_mut();
        let time = rtc.as_mut().unwrap().current_time();
        rtc.as_mut().unwrap().set_alarm(time + TEMPS_TICK_SEC); //also clears the flag
    });

    TEMP_FLAG.store(true, Ordering::Release);
}
 
//--------------------------------------------------------------------------------------------------
/* main function */
#[entry]
fn main() -> ! {

    /* setup */

    // basic setup
    let mut cp = cortex_m::Peripherals::take().unwrap();
    let mut dp = pac::Peripherals::take().unwrap();
 
    // clocks config
    let rcc = dp.RCC.constrain();
    let mut flash = dp.FLASH.constrain();
    let clocks = rcc.cfgr
        .use_hse(8.mhz())
        .freeze(&mut flash.acr);

    // GPIOs
    let mut gpioa = dp.GPIOA.split();
    let mut gpiob = dp.GPIOB.split();
    let mut gpioc = dp.GPIOC.split();
 
    // setup LED
    let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
    let _ = led.set_high();

    cortex_m::interrupt::free(|cs| {
        PCB_LED.borrow(cs).borrow_mut().replace(led)
    });
    
    // Configure the timer 2 as tick timer
    let mut timer = Timer::tim2(dp.TIM2, &clocks)
        .start_count_down(((1.0/GUI_TICK_SEC) as u32).hz());
    timer.listen(Event::Update);

    cortex_m::interrupt::free(|cs| {
        TICK_TIMER.borrow(cs).borrow_mut().replace(timer)
    });

    // set EXTI 17 (see note in 18.4.2, in short : needed for rtc ISR to trigger)
    dp.EXTI.ftsr.write(|w| w.tr17().set_bit());
    dp.EXTI.imr.write(|w| w.mr17().set_bit());

    // setup RTC
    let mut backup_domain = rcc.bkp.constrain(dp.BKP, &mut dp.PWR);
    let mut rtc = Rtc::<RtcClkLsi>::rtc(dp.RTC, &mut backup_domain);
    rtc.set_alarm(rtc.current_time() + TEMPS_TICK_SEC);
    rtc.listen_alarm();

    cortex_m::interrupt::free(|cs| {
        RTC.borrow(cs).borrow_mut().replace(rtc)
    });

    let mut afio = dp.AFIO.constrain();

    // initialize system state
    let mut config_storage = ConfigStorage::new(backup_domain, dp.CRC.new());
    let mut error_state = ErrorState::NoError;
    let config = match config_storage.load_config() {
        Ok(conf) => conf,
        Err(_) => {
            error_state = ErrorState::NewError("config invalide");
            let conf = SystemConfig::new();
            config_storage.save_config(&conf);
            conf
    }};
    let mut state = SystemState::new(config, config_storage, 
                                     gpiob.pb12.into_push_pull_output(&mut gpiob.crh));

    // Setup display
    let mut gui = {
        use stm32f1xx_hal::{gpio::*, delay::Delay};
        
        use hd44780_driver::HD44780;

        // Disable JTAG to free pins
        let (d3_pin, d5_pin, d7_pin) = afio.mapr.disable_jtag(gpioa.pa15, gpiob.pb3, gpiob.pb4);

        // Setup pins
        let rs_pin = gpioa.pa10.into_push_pull_output(&mut gpioa.crh);
        let en_pin = gpioa.pa11.into_push_pull_output(&mut gpioa.crh);
        let d0_pin = gpioa.pa8.into_push_pull_output(&mut gpioa.crh);
        let d1_pin = gpioa.pa12.into_push_pull_output(&mut gpioa.crh);
        let d2_pin = gpiob.pb15.into_push_pull_output(&mut gpiob.crh);
        let d3_pin = d3_pin.into_push_pull_output(&mut gpioa.crh);
        let d4_pin = gpiob.pb14.into_push_pull_output(&mut gpiob.crh);
        let d5_pin = d5_pin.into_push_pull_output(&mut gpiob.crl);
        let d6_pin = gpiob.pb13.into_push_pull_output(&mut gpiob.crh);
        let d7_pin = d7_pin.into_push_pull_output(&mut gpiob.crl);

        // Force write mode
        let mut rw_pin = gpioa.pa9.into_push_pull_output(&mut gpioa.crh);
        rw_pin.set_low();

        let lcd = HD44780::new_8bit(
            OldOutputPin::new(rs_pin), 
            OldOutputPin::new(en_pin), 
            OldOutputPin::new(d0_pin), 
            OldOutputPin::new(d1_pin), 
            OldOutputPin::new(d2_pin), 
            OldOutputPin::new(d3_pin), 
            OldOutputPin::new(d4_pin), 
            OldOutputPin::new(d5_pin), 
            OldOutputPin::new(d6_pin),
            OldOutputPin::new(d7_pin), 
            Delay::new(cp.SYST, clocks)
        );

        // setup button (interrupt)
        let mut but_pin = gpiob.pb8.into_floating_input(&mut gpiob.crh);
        but_pin.make_interrupt_source(&mut afio);
        but_pin.trigger_on_edge(&dp.EXTI, Edge::Rising);
        but_pin.enable_interrupt(&dp.EXTI);

        cortex_m::interrupt::free(|cs| {
            BUTTON_PIN.borrow(cs).borrow_mut().replace(but_pin);
        });

        // Set backlight
        let backlight_pin = gpiob.pb9.into_push_pull_output(&mut gpiob.crh);

        // setup encoder
        let enc = Timer::tim4(dp.TIM4, &clocks)
            .qei((gpiob.pb6, gpiob.pb7), &mut afio.mapr, 
                 QeiOptions { 
                     slave_mode:SlaveMode::EncoderMode1, 
                     auto_reload_value: 1022,
                 });

        LCDGui::new(lcd, enc, &BUTTON_FLAG, Some(backlight_pin), state.config())
    };
    
    let exti = dp.EXTI;
    cortex_m::interrupt::free(|cs| {
        G_EXTI.borrow(cs).borrow_mut().replace(exti);
    });

    // Enable interrupts
    unsafe {
        cortex_m::peripheral::NVIC::unmask(Interrupt::TIM2);
        cortex_m::peripheral::NVIC::unmask(Interrupt::EXTI9_5);
        cortex_m::peripheral::NVIC::unmask(Interrupt::RTCALARM);
    }

    // allow the debugging to continue in sleep and stop modes
    #[cfg(debug_assertions)]
    dp.DBGMCU.cr.write(|w| w.dbg_sleep().set_bit());
    #[cfg(debug_assertions)]
    dp.DBGMCU.cr.write(|w| w.dbg_stop().set_bit());

    // configure stop mode (not working yet, probably due to fake chip)
    //dp.PWR.cr.write(|w| w.pdds().stop_mode());
    //dp.PWR.cr.write(|w| w.lpds().set_bit());

    // setup adc
    let mut adc = Adc::adc1(dp.ADC1, clocks);
    adc.set_sample_time(SampleTime::T_71);
    let adc = RefCell::new(adc);
    let ext1 = gpioa.pa4.into_analog(&mut gpioa.crl);
    let ext2 = gpioa.pa5.into_analog(&mut gpioa.crl);
    let p2_1 = gpioa.pa2.into_analog(&mut gpioa.crl);
    let p2_2 = gpioa.pa3.into_analog(&mut gpioa.crl);
    let p1_1 = gpioa.pa0.into_analog(&mut gpioa.crl);
    let p1_2 = gpioa.pa1.into_analog(&mut gpioa.crl);

    let mut ext_probe = TemperatureProbe::new(&adc, ext1, ext2).unwrap();
    let mut p1_probe = TemperatureProbe::new(&adc, p1_1, p1_2).unwrap();
    let mut p2_probe = TemperatureProbe::new(&adc, p2_1, p2_2).unwrap();
    
    let mut power_state = PowerState::Awake;
    let mut timeout = 0;
    
    /* run */
    let _ = TEMP_FLAG.swap(true, Ordering::Release);

    // fix some weird bug caused by the debouncing capacitor of the button
    for _ in 0..10 {
        cortex_m::asm::wfi();
    }
    BUTTON_FLAG.store(false, Ordering::Release);

    loop {

        match error_state {
            ErrorState::NoError => {
                if TEMP_FLAG.swap(false, Ordering::AcqRel) {

                    // compute temps
                    let ext_temp = ext_probe.read().ok();
                    let p1_temp = p1_probe.read().ok();
                    let p2_temp = p2_probe.read().ok();

                    state.update(ext_temp, p1_temp, p2_temp);
                    gui.update_temps(&mut state);

                } else {

                    match gui.update(&mut state) { 
                        true => timeout = 0,
                        false => timeout += 1,
                    }

                    // manage power state
                    match power_state {
                        PowerState::Awake => { // normal gui update
                            if timeout as f32 * GUI_TICK_SEC >= AWAKE_TIMEOUT_SEC {
                                // go to sleep
                                power_state = PowerState::Sleeping;
                                gui.sleep();
                                cp.SCB.set_sleepdeep();
                            }
                        },
                        PowerState::Sleeping => { // button was pressed during sleep
                            // wake up
                            power_state = PowerState::Awake;
                            cp.SCB.clear_sleepdeep();
                }}}
            },
            ErrorState::NewError (err) => {
                gui.display_error(err);
                state.stop(); 
                error_state = ErrorState::HandlingError;
            },
            ErrorState::HandlingError => {
                if gui.update(&mut state) {
                    error_state = ErrorState::NoError;
                    state.reset();
                    ext_probe.reset().unwrap();
                    p1_probe.reset().unwrap();
                    p2_probe.reset().unwrap();
                    timeout = 0;
                }
        }}
                
        // put device in sleep mode until next interrupt (button or timer)
        cortex_m::asm::wfi();
    }
}

