#![no_std]
#![no_main]

use core::cell::{RefCell, RefMut};

use core::convert::Infallible;
use core::mem;
use core::ops::Deref;
use core::sync::atomic::AtomicUsize;
use core::sync::atomic::Ordering::Relaxed;
extern crate panic_semihosting;
//use ringbuffer::ConstGenericRingBuffer;

#[macro_use]
mod gui;
mod keyboard;
mod keyboard_layouts;
mod pin_defs;
#[cfg(feature = "ps2")]
mod ps2;
mod switch;

use cortex_m::asm::delay;
use cortex_m::interrupt::Mutex;
use embedded_graphics::geometry::{Point, Size};
use embedded_hal::digital::v2::{InputPin, OutputPin};
use frunk::{HCons, HNil};
use heapless::String;
use keyboard::*;
use pin_defs::*;

use cortex_m_rt::entry;
use sh1106::displayrotation::DisplayRotation;
use sh1106::displaysize::DisplaySize;
use sh1106::mode::GraphicsMode;
use sh1106::Builder;

use stm32f1xx_hal::gpio::{ErasedPin, Input, Output, PullDown, PushPull};
use stm32f1xx_hal::i2c::{BlockingI2c, Mode};
use stm32f1xx_hal::pac::{NVIC, TIM3};

use stm32f1xx_hal::timer::{Counter, Event};

use stm32f1xx_hal::usb::{Peripheral, UsbBus, UsbBusType};
use stm32f1xx_hal::{device::interrupt, prelude::*, stm32};
use switch::Switch;
use usb_device::bus::UsbBusAllocator;
use usb_device::device::{UsbDevice, UsbDeviceBuilder, UsbVidPid};
use usbd_human_interface_device::device::keyboard::{NKROBootKeyboard, NKROBootKeyboardConfig};
use usbd_human_interface_device::device::DeviceClass;
use usbd_human_interface_device::page::Keyboard;
use usbd_human_interface_device::usb_class::{UsbHidClass, UsbHidClassBuilder};

use crate::gui::draw_gui;

//static mut G_TIMER2: Option<Timer<Timer2>> = None;
//static mut G_TIMER1: Option<Timer<TIMER1>> = None;
static mut G_TIMER3: Option<Counter<TIM3, 10000>> = None;
//LEDPWM
/*
static mut LED_PWM: Option<LedPwm> = None;
type KB = Keyboard<
    keyboard::KeyMatrix<Pxx<Output<PushPull>>, Pxx<Input<PullDown>>, 7_usize, 7_usize>,
    1_usize,
    PB0<Output<OpenDrain>>,
    PB1<Output<OpenDrain>>,
>;
static mut KEYBOARD: Option<KB> = None;*/
type MatrixRight = KeyMatrix<ErasedPin<Output<PushPull>>, ErasedPin<Input<PullDown>>, 7, 7>;
static KEY_MATRIX: Mutex<RefCell<Option<MatrixRight>>> = Mutex::new(RefCell::new(None));
static mut KEYS: [Switch<5>; 96] = [Switch::new(); 96];

static USB_DEV: Mutex<RefCell<Option<UsbDevice<UsbBusType>>>> = Mutex::new(RefCell::new(None));
type KbdDevice = UsbHidClass<
    'static,
    stm32f1xx_hal::usb::UsbBus<Peripheral>,
    HCons<NKROBootKeyboard<'static, UsbBusType>, HNil>,
>;
static USB_KBD: Mutex<RefCell<Option<KbdDevice>>> = Mutex::new(RefCell::new(None));

//Time overflow after ~119,3h
static mut TIME: AtomicUsize = AtomicUsize::new(0);
fn get_millis() -> usize {
    unsafe { TIME.load(Relaxed) }
}

#[allow(
    non_snake_case,
    clippy::unwrap_used,
    clippy::similar_names,
    clippy::too_many_lines
)]
#[entry]
fn main() -> ! {
    let take = stm32::Peripherals::take().unwrap();
    let dp = take;

    let mut flash = dp.FLASH.constrain();
    let rcc = dp.RCC.constrain();
    let clocks = rcc
        .cfgr
        .use_hse(8.MHz())
        .sysclk(48.MHz())
        .pclk1(24.MHz())
        .freeze(&mut flash.acr);
    assert!(clocks.usbclk_valid());

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

    let mut gpioa = dp.GPIOA.split();
    let mut gpiob = dp.GPIOB.split();
    let mut led = gpiob.pb8.into_push_pull_output(&mut gpiob.crh);

    {
        let mut tm3: Counter<TIM3, 10000> = dp.TIM3.counter(&clocks);
        tm3.start(1.millis());
        tm3.listen(Event::Update);
        unsafe { G_TIMER3 = Some(tm3) };
        unsafe {
            NVIC::unmask(interrupt::TIM3);
        }
    }

    //usb
    let usb_bus = RefCell::new({
        #[cfg(debug_assertions)]
        {
            //forced usb reset
            let mut usb_dp = gpioa.pa12.into_push_pull_output(&mut gpioa.crh);
            usb_dp.set_low();
            delay(clocks.sysclk().raw() / 100);
            UsbBus::new(Peripheral {
                usb: dp.USB,
                pin_dm: gpioa.pa11,
                pin_dp: usb_dp.into_floating_input(&mut gpioa.crh),
            })
        }
        #[cfg(not(debug_assertions))]
        {
            UsbBus::new(Peripheral {
                usb: dp.USB,
                pin_dm: gpioa.pa11,
                pin_dp: gpioa.pa12,
            })
        }
    });
    {
        //extending lifetime of usb_bus to static
        //this is sound since main never returns and therefore usb_bus stays on the stack
        let usb_bus: &'static RefMut<UsbBusAllocator<UsbBusType>> =
            unsafe { mem::transmute(&usb_bus.borrow_mut()) };
        let kbd = UsbHidClassBuilder::new()
            .add_device(NKROBootKeyboardConfig::default())
            .build(usb_bus);
        let usb_dev = UsbDeviceBuilder::new(usb_bus, UsbVidPid(0x16c0, 0x27db))
            .manufacturer("Zimward Inc.")
            .product("Ideal Keyboard")
            .serial_number("PROTOTYPE")
            .build();
        cortex_m::interrupt::free(|cs| {
            USB_DEV.borrow(cs).replace(Some(usb_dev));
        });
        cortex_m::interrupt::free(|cs| {
            USB_KBD.borrow(cs).replace(Some(kbd));
        });
    }

    // unsafe {
    // LED_PWM = Some(LedPwm::new(gpiob.pb8, 220));
    // }
    // unsafe {
    let right_kb = KeyMatrix::new(
        pp_output!(
            gpioa, gpioa.pa0, gpioa.pa1, gpioa.pa2, gpioa.pa3, gpioa.pa4, gpioa.pa5, gpioa.pa6
        ),
        pd_input!(
            gpiob, gpiob.pb9, gpiob.pb10, gpiob.pb11, gpiob.pb12, gpiob.pb13, gpiob.pb14,
            gpiob.pb15
        ),
        0,
    );
    cortex_m::interrupt::free(|cs| {
        KEY_MATRIX.borrow(cs).replace(Some(right_kb));
    });
    //     KEYBOARD = Some(Keyboard::new(
    //         [right_kb],
    //         gpiob.pb0.into_open_drain_output(),
    //         gpiob.pb1.into_open_drain_output(),
    //     ));
    // }

    /*I2C0 interface*/
    let scl = gpiob.pb6.into_alternate_open_drain(&mut gpiob.crl);
    let sda = gpiob.pb7.into_alternate_open_drain(&mut gpiob.crl);
    let i2c = BlockingI2c::i2c1(
        dp.I2C1,
        (scl, sda),
        &mut afio.mapr,
        Mode::Standard {
            frequency: 100.kHz(),
        },
        clocks,
        998,
        1,
        998,
        998,
    );
    /*Display*/
    let mut disp: GraphicsMode<_> = Builder::new()
        .with_size(DisplaySize::Display128x64)
        .with_rotation(DisplayRotation::Rotate180)
        .connect_i2c(i2c)
        .into();

    disp.init().unwrap();
    disp.flush().unwrap();
    let tab1 = StaticGuiElement!(0, 0, 9, "Macro");
    let tab2 = StaticGuiElement!(tab1.size.width + 5, 0, 9, "DVORAK");
    let tab3 = StaticGuiElement!(
        u32::try_from(tab2.pos.x).unwrap() + tab2.size.width + 5,
        0,
        9,
        "Men√º"
    );
    let static_gui_elem = [tab1, tab2, tab3];
    let mut last = get_millis();
    loop {
        if last + 1_000 <= get_millis() {
            last = get_millis();
            draw_gui(&mut disp, &static_gui_elem);
            led.toggle();
        }
        cortex_m::interrupt::free(|cs| {
            let matrix = KEY_MATRIX.borrow(cs).replace(None);
            if let Some(mut matrix) = matrix {
                unsafe {
                    matrix.scan(&mut KEYS);
                }
                KEY_MATRIX.borrow(cs).replace(Some(matrix));
            }
            let rep = unsafe {
                if KEYS[0].is_down() {
                    [Keyboard::A]
                } else {
                    [Keyboard::NoEventIndicated]
                }
            };
            let usb_kbd = USB_KBD.borrow(cs).replace(None);
            if let Some(mut usb_kbd) = usb_kbd {
                usb_kbd.device().write_report(rep);
                USB_KBD.borrow(cs).replace(Some(usb_kbd));
            }
        });
    }
}

#[allow(clippy::unwrap_used)]
#[interrupt]
fn TIM3() {
    cortex_m::interrupt::free(|cs| {
        unsafe {
            TIME.fetch_add(1, Relaxed);
            //KEYBOARD.as_mut().unwrap().scan();
        }
        let usb_dev = USB_DEV.borrow(cs).replace(None);
        let usb_kbd = USB_KBD.borrow(cs).replace(None);
        if let (Some(mut usb_dev), Some(mut usb_kbd)) = (usb_dev, usb_kbd) {
            if usb_dev.poll(&mut [&mut usb_kbd]) {
                let _ = usb_kbd.device().read_report();
            }
            usb_kbd.device().tick();
            USB_DEV.borrow(cs).replace(Some(usb_dev));
            USB_KBD.borrow(cs).replace(Some(usb_kbd));
        }
        if let Some(ref mut timer) = unsafe { &mut G_TIMER3 } {
            timer.clear_interrupt(Event::Update);
        }
    });
}
