//! Using shift register 74hc595 and multiplexer cd4051b with ESP32
//!

#![no_std]
#![no_main]

use core::result::Result;

use hal::{
    clock::ClockControl, gpio::IO, peripherals::Peripherals, prelude::*, timer::TimerGroup, Delay,
    Rtc,
};
use esp_backtrace as _;
use esp_println::println;
use drive_74hc595::ShiftRegister;
use hal::ehal::digital::v2::OutputPin;

//
// Refer https://wokwi.com/projects/343522915673702994 
// for cd4051b.chip.c and cd4051b.chip.json
//

#[entry]
fn main() -> ! {
    let peripherals = Peripherals::take();
    let mut system = peripherals.DPORT.split();
    let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

    // Disable the RTC and TIMG watchdog timers
    let mut rtc = Rtc::new(peripherals.RTC_CNTL);
    let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
    let mut wdt0 = timer_group0.wdt;
    let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks);
    let mut wdt1 = timer_group1.wdt;
    rtc.rwdt.disable();
    wdt0.disable();
    wdt1.disable();
    println!("Hello world!");

    // Setup the GPIOs.
    let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
    let mut clock_pin = io.pins.gpio4.into_push_pull_output();
    let mut latch_pin = io.pins.gpio2.into_push_pull_output();
    let mut data_pin = io.pins.gpio15.into_push_pull_output();

    let mut relay_control = EightBitRelayControl::new(data_pin, clock_pin, latch_pin);

    // Initialize the Delay peripheral, and use it to toggle the LED state in a
    // loop.
    let mut delay = Delay::new(&clocks);

    relay_control.begin();

    loop {
        for pin in 0..=7 {
            relay_control.set_high(pin);
            delay.delay_ms(50u32);
        }
        for pin in 0..=7 {
            relay_control.set_low(pin);
            delay.delay_ms(50u32);    
        }
    }
}


struct EightBitRelayControl<DP, CP, LP>
where 
    DP: OutputPin,
    CP: OutputPin,
    LP: OutputPin 
{
    drive: ShiftRegister<DummyPin, DP, DummyPin, CP, LP>,
    curr_val: u8,
}

impl<DP, CP, LP> EightBitRelayControl<DP, CP, LP>
where 
    DP: OutputPin,
    CP: OutputPin,
    LP: OutputPin
{
    pub fn new(data_pin: DP, clock_pin: CP, latch_pin: LP) -> Self {
        let drive = ShiftRegister::new(DummyPin, data_pin, DummyPin, clock_pin, latch_pin);
        Self {
            drive,
            curr_val: 0,
        }
    }

    pub fn begin(&mut self) {
        self.drive.begin();
        self.drive.output_clear();
    }

    pub fn all_high(&mut self) {
        self.drive.disable_output();
        self.drive.load(255);
        self.curr_val = 255;
        self.drive.enable_output();
    }

    pub fn all_low(&mut self) {
        self.drive.disable_output();
        self.drive.load(0);
        self.curr_val = 0;
        self.drive.enable_output();
    }

    pub fn set_high(&mut self, addr: u8) {
        assert!(addr < 8);
        let addr = u8::pow(2, addr as u32);
        self.drive.disable_output();
        self.curr_val |= addr;
        self.drive.load(self.curr_val);
        self.drive.enable_output();
    }

    pub fn set_low(&mut self, addr: u8) {
        assert!(addr < 8);
        let addr = u8::pow(2, addr as u32);
        self.drive.disable_output();
        self.curr_val -= self.curr_val & addr;
        self.drive.load(self.curr_val);
        self.drive.enable_output();
    }
}

struct DummyPin;

impl OutputPin for DummyPin {
    type Error = ();

    fn set_low(&mut self) -> Result<(), Self::Error> {
        Ok(())
    }

    fn set_high(&mut self) -> Result<(), Self::Error> {
        Ok(())
    }
}
74HC595
CD4051BBreakout