#![no_std]
#![no_main]

use esp32_hal::{
    adc::{AdcConfig, Attenuation, ADC, ADC2},
    clock::ClockControl,
    peripherals::Peripherals,
    gpio::*,
    prelude::*,
    spi,
    timer::TimerGroup,
    Rtc,
    IO,
    Delay,
};

/* Set this number according to count of displays 
in chain (check "chain" parameter of max7219-matrix in diagram.json) */
const DISPLAY_COUNT : usize = 7;

use embedded_hal;

use max7219::connectors::PinConnector;
use max7219::MAX7219;
use max7219::DecodeMode;

use esp_max7219_nostd::{prepare_display, show_moving_text_in_loop, draw_point, clear_with_state};
use esp_max7219_nostd::mappings::SingleDisplayData;

use esp_backtrace as _;
use esp_println::println;

#[derive(Copy, Clone, PartialEq)]
pub enum Event {
    Pressed,
    Released,
    Nothing,
}
pub struct Button<T> {
    button: T,
    pressed: bool,
}
impl<T: ::embedded_hal::digital::v2::InputPin<Error = core::convert::Infallible>> Button<T> {
    pub fn new(button: T) -> Self {
        Button {
            button,
            pressed: true,
        }
    }
    pub fn check(&mut self){
        self.pressed = !self.button.is_low().unwrap();
    }

    pub fn poll(&mut self, delay :&mut Delay) -> Event {
        let pressed_now = !self.button.is_low().unwrap();
        if !self.pressed  &&  pressed_now
        {
            delay.delay_ms(30 as u32);
            self.check();
            if !self.button.is_low().unwrap() {
                Event::Pressed
            }
            else {
                Event::Nothing
            }
        }
        else if self.pressed && !pressed_now{
            delay.delay_ms(30 as u32);
            self.check();
            if self.button.is_low().unwrap()
            {
                Event::Released
            }
            else {
                Event::Nothing
            }
        }
        else{
            Event::Nothing
        }
        
    }
}

extern crate alloc;
use alloc::vec::Vec;
use core::mem::MaybeUninit;
#[global_allocator]
static ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty();

fn init_heap() {
    const HEAP_SIZE: usize = 32 * 1024;
    static mut HEAP: MaybeUninit<[u8; HEAP_SIZE]> = MaybeUninit::uninit();

    unsafe {
        ALLOCATOR.init(HEAP.as_mut_ptr() as *mut u8, HEAP_SIZE);
    }
}

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

    let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);

    let mut delay = Delay::new(&clocks);

    /* Initialise analog peripherals for adc */
    let analog = peripherals.SENS.split();

    /* Setting up environment for joystick */
    let mut adc2_config = AdcConfig::new();
    
    /* Setting up joystick's axles and select button */
    let mut joystick_select = Button::new(io.pins.gpio21.into_pull_up_input()); // MENU button on esp32-s3-usb-otg (low level when pressed)
    let mut y_axis = adc2_config.enable_pin(io.pins.gpio25.into_analog(), Attenuation::Attenuation11dB);
    let mut x_axis = adc2_config.enable_pin(io.pins.gpio2.into_analog(), Attenuation::Attenuation11dB);

    let mut adc2 = ADC::<ADC2>::adc(analog.adc2, adc2_config).unwrap();

    let mut y_axis_actual :u16 = 0;
    let mut x_axis_actual :u16 = 0;

    /* Set pins for display initialisation */
    let din = io.pins.gpio23.into_push_pull_output();
    let cs = io.pins.gpio15.into_push_pull_output();
    let clk = io.pins.gpio18.into_push_pull_output();

    let mut display = MAX7219::from_pins(DISPLAY_COUNT, din, cs, clk).unwrap();


    /* This vector will contain actual configurations of each display (which points are lit) */
    let mut display_actual_state : Vec<[u8;8]> = Vec::new();

    let mut tmp = [0b00000000 as u8;8];

    for i in 0..DISPLAY_COUNT
    {
        display_actual_state.push(tmp);
        tmp = [0b00000000 as u8;8];
    }

    prepare_display(&mut display, DISPLAY_COUNT, 0x5);
    draw_point(&mut display, 0, &mut display_actual_state[0], 1, 1); // draw starting point

    let mut x : usize = 1;
    let mut y : usize = 1;
    let mut dp_index : usize = 0;  // index of actual display

    loop {
        /* Little delay to make movement smoother */
        delay.delay_ms(40 as u32); 

        /* Read analog data from x and y joystick pins */
        y_axis_actual = nb::block!(adc2.read(&mut y_axis)).unwrap();
        x_axis_actual = nb::block!(adc2.read(&mut x_axis)).unwrap();

        /* SELECT button will clear whole display and set you to a start point */
        if let Event::Pressed = joystick_select.poll(&mut delay) 
        {   
            println!("Reset! Now pointer is at ({}({});{})(display No.{})", x, x + 8*dp_index, y, dp_index);
            for i in 0..DISPLAY_COUNT {clear_with_state(&mut display, i, &mut display_actual_state[i]);}
            x = 1;
            y = 1; 
            dp_index = 0; 
            draw_point(&mut display, dp_index, &mut display_actual_state[dp_index], x, y);
        }
        if x_axis_actual < 2048 // right
        {
            if x < 8 && (dp_index < (DISPLAY_COUNT)) {x += 1;}
            else if x == 8 && dp_index != (DISPLAY_COUNT - 1)
            {
                x = 1;
                dp_index += 1;
            }
            draw_point(&mut display, dp_index, &mut display_actual_state[dp_index], x, y);
            println!("Moved right!\nNow pointer is at ({}({});{})(display No.{})\n\n", x, x + 8*dp_index, y, dp_index);
        }
        if x_axis_actual > 2048 // left
        {
            if (x == 1 && dp_index == 0) {continue;}
            else if (x == 0 && dp_index != 0)
            {
                x = 8;
                dp_index -= 1;
            }
            else {x -= 1;}
            
            draw_point(&mut display, dp_index, &mut display_actual_state[dp_index], x, y);
            println!("Moved left!\nNow pointer is at ({}({});{})(display No.{})\n\n", x, x + 8*dp_index, y, dp_index);
        }
        if y_axis_actual < 2048 // down
        {
            if y < 8 
            {
                y += 1;
                draw_point(&mut display, dp_index, &mut display_actual_state[dp_index], x, y);
            }
            println!("Moved down!\nNow pointer is at ({}({});{})(display No.{})\n\n", x, x + 8*dp_index, y, dp_index);
        }
         if y_axis_actual > 2048 // up
        {
            if y > 1 
            {
                y -= 1;
                draw_point(&mut display, dp_index, &mut display_actual_state[dp_index], x, y);
            }
            println!("Moved up!\nNow pointer is at ({}({});{})(display No.{})\n\n", x, x + 8*dp_index, y, dp_index);
        }
    }
}