#![no_std]
#![no_main]

use dht_hal_drv::{
    dht_split_init,
    dht_split_read,
    DhtError,
    DhtType,
    DhtValue
    };

use embedded_graphics::{
    mono_font::{
        ascii::{FONT_6X10, FONT_9X18_BOLD},
        MonoTextStyleBuilder,
    },
    pixelcolor::BinaryColor,
    prelude::*,
    text::{Alignment, Text},
    primitives::{Circle, PrimitiveStyleBuilder},
};

use esp32_hal::{
    clock::ClockControl,
    pac::Peripherals,
    i2c::I2C,
    gpio::IO,
    prelude::*,
    timer::TimerGroup,
    Rtc,
    Delay
    };

// use Dht;
use esp_backtrace as _;
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306};
use xtensa_lx_rt::entry;

pub mod write_to {
    use core::cmp::min;
    use core::fmt;

    pub struct WriteTo<'a> {
        buffer: &'a mut [u8],
        // on write error (i.e. not enough space in buffer) this grows beyond
        // `buffer.len()`.
        used: usize,
    }

    impl<'a> WriteTo<'a> {
        pub fn new(buffer: &'a mut [u8]) -> Self {
            WriteTo { buffer, used: 0 }
        }

        pub fn as_str(self) -> Option<&'a str> {
            if self.used <= self.buffer.len() {
                // only successful concats of str - must be a valid str.
                use core::str::from_utf8_unchecked;
                Some(unsafe { from_utf8_unchecked(&self.buffer[..self.used]) })
            } else {
                None
            }
        }
    }

    impl<'a> fmt::Write for WriteTo<'a> {
        fn write_str(&mut self, s: &str) -> fmt::Result {
            if self.used > self.buffer.len() {
                return Err(fmt::Error);
            }
            let remaining_buf = &mut self.buffer[self.used..];
            let raw_s = s.as_bytes();
            let write_num = min(raw_s.len(), remaining_buf.len());
            remaining_buf[..write_num].copy_from_slice(&raw_s[..write_num]);
            self.used += raw_s.len();
            if write_num < raw_s.len() {
                Err(fmt::Error)
            } else {
                Ok(())
            }
        }
    }

    pub fn show<'a>(buffer: &'a mut [u8], args: fmt::Arguments) -> Result<&'a str, fmt::Error> {
        let mut w = WriteTo::new(buffer);
        fmt::write(&mut w, args)?;
        w.as_str().ok_or(fmt::Error)
    }
}

#[entry]
fn main() -> ! {
    let peripherals = Peripherals::take().unwrap();
    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();

    // Set GPIO15 as an output, and set its state high initially.
    let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
    let mut led = io.pins.gpio15.into_push_pull_output();

    // Create a new peripheral object with the described wiring
    // and standard I2C clock speed
    let i2c = I2C::new(
        peripherals.I2C0,
        io.pins.gpio32,
        io.pins.gpio33,
        100u32.kHz(),
        &mut system.peripheral_clock_control,
        &clocks,
    )
    .unwrap();

    led.set_high().unwrap();

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

    // Initialize display
    let interface = I2CDisplayInterface::new(i2c);
    let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
        .into_buffered_graphics_mode();
    display.init().unwrap();

    // Specify different text styles
    let text_style = MonoTextStyleBuilder::new()
        .font(&FONT_9X18_BOLD)
        .text_color(BinaryColor::On)
        .build();
/*
    let fill = PrimitiveStyle::with_fill(BinaryColor::On);

    Rectangle::new(Point::new(0, 0), Size::new(16, 16))
        .into_styled(fill)
        .draw(&mut display)
        .unwrap();
*/
    // let style = PrimitiveStyleBuilder::new()
    //     .stroke_width(1)
    //     .stroke_color(BinaryColor::On)
    //     .build();

    // Circle::new(Point::new(10, 20), 30)
    // .into_styled(style)
    // .draw(&mut display).unwrap();

    // Write buffer to display
    // display.flush().unwrap();

    // let sensor = Dht::new();

    let mut pin_out = io.pins.gpio2.into_push_pull_output();

    let mut delay_us = |d| delay.delay_us(d);

    // dht_split_init(&mut pin_out, &mut delay_us);

    let mut pin_in = pin_out.into_pull_down_input();

    let mut readings;

    let mut temp: f32;

    loop {
        display.clear();

        pin_out = pin_in.into_push_pull_output();

        dht_split_init(&mut pin_out, &mut delay_us);

        pin_in = pin_out.into_pull_down_input();

        readings = dht_split_read(DhtType::DHT22, &mut pin_in, &mut delay_us);

        match readings {
            Ok(res) => {
                temp = res.temperature();
            }
            _ => {
                temp = 0.0;
            }
        };

        let mut buf = [0u8; 64];
        let temps: &str = write_to::show(&mut buf, format_args!("{}", temp) ).unwrap();

        Text::with_alignment(
            temps,
            display.bounding_box().center(),
            text_style,
            Alignment::Center,
        ).draw(&mut display).unwrap();

        display.flush().unwrap();

        led.toggle().unwrap();
        // delay.delay_ms(250u32);
    }
}