#![no_std]
#![no_main]

#[macro_use]
extern crate log;

#[macro_use]
extern crate lazy_static;

use smoltcp as net;
use stm32h7xx_hal::ethernet;
use stm32h7xx_hal::{gpio::Speed, prelude::*, spi, pac};
use embedded_hal::{
    blocking::spi::Transfer,
    digital::v2::OutputPin,
};

use core::sync::atomic::{AtomicU32, Ordering};
use core::fmt::Write;
use core::str;

// use heapless::{consts, String};

use cortex_m;
use cortex_m::iprintln;
use cortex_m_rt::{
    entry,
    exception,
};
// use cortex_m_semihosting::hprintln;

// use panic_halt as _;
use panic_itm as _;

use rtic::cyccnt::{Instant, U32Ext};

use log::info;
use log::debug;
use log::trace;
use log::warn;

use nb::block;

use minimq::{
    embedded_nal::{IpAddr, Ipv4Addr, TcpStack, SocketAddr, Mode},
    MqttClient, QoS,
};

use firmware::nal_tcp_client::{NetworkStack, NetStorage, NetworkInterface};
use firmware::{Urukul};
use firmware::cpld::{CPLD};

#[link_section = ".sram3.eth"]
static mut DES_RING: ethernet::DesRing = ethernet::DesRing::new();

// Logging setup
#[path = "util/logger.rs"]
mod logger;
#[path = "util/clock.rs"]
mod clock;
// End of logging setup

// static TIME: AtomicU32 = AtomicU32::new(0);

#[entry]
fn main() -> ! {
    
    // logger::semihosting_init();
    let clock = clock::Clock::new();

    let mut cp = cortex_m::Peripherals::take().unwrap();
    let dp = pac::Peripherals::take().unwrap();

    cp.DWT.enable_cycle_counter();

    // Enable SRAM3 for the descriptor ring.
    dp.RCC.ahb2enr.modify(|_, w| w.sram3en().set_bit());
    // Reset RCC clock
    dp.RCC.rsr.write(|w| w.rmvf().set_bit());

    let pwr = dp.PWR.constrain();
    let vos = pwr.freeze();

    let rcc = dp.RCC.constrain();
    let ccdr = rcc
                .use_hse(8.mhz())
                .sysclk(400.mhz())
                .hclk(200.mhz())
                // .per_ck(100.mhz())
                .pll1_q_ck(48.mhz())    // for SPI
                .pll1_r_ck(400.mhz())   // for TRACECK
                // .pll2_p_ck(100.mhz())
                // .pll2_q_ck(100.mhz())
                .freeze(vos, &dp.SYSCFG);
    
    unsafe {
        logger::enable_itm(&dp.DBGMCU, &mut cp.DCB, &mut cp.ITM);
    }

    let mut delay = cp.SYST.delay(ccdr.clocks);

    let gpioa = dp.GPIOA.split(ccdr.peripheral.GPIOA);
    let gpiob = dp.GPIOB.split(ccdr.peripheral.GPIOB);
    let gpioc = dp.GPIOC.split(ccdr.peripheral.GPIOC);
    let gpiod = dp.GPIOD.split(ccdr.peripheral.GPIOD);
    let gpioe = dp.GPIOE.split(ccdr.peripheral.GPIOE);
    let gpiof = dp.GPIOF.split(ccdr.peripheral.GPIOF);
    let gpiog = dp.GPIOG.split(ccdr.peripheral.GPIOG);

    let mut yellow_led = gpioe.pe1.into_push_pull_output();
    yellow_led.set_low().unwrap();
    let mut red_led = gpiob.pb14.into_push_pull_output();
    red_led.set_high().unwrap();
    let mut green_led = gpiob.pb0.into_push_pull_output();
    green_led.set_low().unwrap();

    // gpiob.pb3.into_alternate_af0().set_speed(Speed::VeryHigh);

    logger::init();

    // Configure ethernet IO
    {
        let _rmii_refclk = gpioa.pa1.into_alternate_af11().set_speed(Speed::VeryHigh);
        let _rmii_mdio = gpioa.pa2.into_alternate_af11().set_speed(Speed::VeryHigh);
        let _rmii_mdc = gpioc.pc1.into_alternate_af11().set_speed(Speed::VeryHigh);
        let _rmii_crs_dv = gpioa.pa7.into_alternate_af11().set_speed(Speed::VeryHigh);
        let _rmii_rxd0 = gpioc.pc4.into_alternate_af11().set_speed(Speed::VeryHigh);
        let _rmii_rxd1 = gpioc.pc5.into_alternate_af11().set_speed(Speed::VeryHigh);
        let _rmii_tx_en = gpiog.pg11.into_alternate_af11().set_speed(Speed::VeryHigh);
        let _rmii_txd0 = gpiog.pg13.into_alternate_af11().set_speed(Speed::VeryHigh);
        let _rmii_txd1 = gpiob.pb13.into_alternate_af11().set_speed(Speed::VeryHigh);
    }

    // Configure ethernet
    let mac_addr = net::wire::EthernetAddress([0xAC, 0x6F, 0x7A, 0xDE, 0xD6, 0xC8]);
    let (eth_dma, mut eth_mac) = unsafe {
        ethernet::new_unchecked(
            dp.ETHERNET_MAC,
            dp.ETHERNET_MTL,
            dp.ETHERNET_DMA,
            &mut DES_RING,
            mac_addr.clone(),
        )
    };

    unsafe { ethernet::enable_interrupt() }
    
    let mut ip_addrs = [net::wire::IpCidr::new(net::wire::IpAddress::v4(192, 168, 1, 200), 24)];

    let mut neighbor_cache_entries = [None; 8];
    let mut neighbor_cache = net::iface::NeighborCache::new(&mut neighbor_cache_entries[..]);
    neighbor_cache.fill(
        net::wire::IpAddress::v4(192, 168, 1, 125),
        net::wire::EthernetAddress([0x2C, 0xF0, 0x5D, 0x26, 0xB8, 0x2F]),
        clock.elapsed(),
    );

    let mut net_interface = net::iface::EthernetInterfaceBuilder::new(eth_dma)
        .ethernet_addr(mac_addr)
        .neighbor_cache(neighbor_cache)
        .ip_addrs(&mut ip_addrs[..])
        .finalize();

    /*
     *	Using SPI1, AF5
     *  SCLK -> PA5
     *	MOSI -> PB5
     *	MISO -> PA6
     *	CS -> 0: PB12, 1: PA15, 2: PC7
     *  I/O_Update -> PB15
     */
    let sclk = gpioa.pa5.into_alternate_af5();
    let mosi = gpiob.pb5.into_alternate_af5();
    let miso = gpioa.pa6.into_alternate_af5();

    let (cs0, cs1, cs2) = (
        gpiob.pb12.into_push_pull_output(),
        gpioa.pa15.into_push_pull_output(),
        gpioc.pc7.into_push_pull_output(),
    );

    let io_update = gpiob.pb15.into_push_pull_output();     

    let spi = dp.SPI1.spi(
        (sclk, miso, mosi),
        spi::MODE_0,
        3.mhz(),
        ccdr.peripheral.SPI1,
        &ccdr.clocks,
    );

    let cpld = CPLD::new(spi, (cs0, cs1, cs2), io_update);
    let parts = cpld.split();

    let urukul = Urukul::new(
        parts.spi1, parts.spi2, parts.spi3, parts.spi4, parts.spi5, parts.spi6, parts.spi7,
        [25_000_000, 25_000_000, 25_000_000, 25_000_000]
    );

    cp.SCB.invalidate_icache();
    cp.SCB.enable_icache();

    // let mut time: u32 = 0;
    // let mut next_ms = Instant::now();

    // next_ms += 400_000.cycles();

    let mut socket_set_entries: [_; 8] = Default::default();
    let mut sockets = net::socket::SocketSet::new(&mut socket_set_entries[..]);

    let mut rx_storage = [0; 4096];
    let mut tx_storage = [0; 4096];

    let tcp_socket = {
        let tx_buffer = net::socket::TcpSocketBuffer::new(&mut tx_storage[..]);
        let rx_buffer = net::socket::TcpSocketBuffer::new(&mut rx_storage[..]);

        net::socket::TcpSocket::new(tx_buffer, rx_buffer)
    };
    let handle = sockets.add(tcp_socket);

    // delay.delay_ms(2000_u16);
    green_led.set_high().unwrap();

    {
        let mut socket = sockets.get::<net::socket::TcpSocket>(handle);
        socket.connect((net::wire::IpAddress::v4(192, 168, 1, 125), 1883), 49500).unwrap();
        socket.set_timeout(Some(net::time::Duration::from_millis(2000)));
        debug!("connect!");
    }

    yellow_led.set_low().unwrap();
    red_led.set_high().unwrap();

    let mut green = true;
    let mut connected = false;

    debug!("Poll link status: {}", eth_mac.phy_poll_link());
    while !eth_mac.phy_poll_link() {}
    debug!("Poll link status: {}", eth_mac.phy_poll_link());

    loop {
        // let timestamp = net::time::Instant::from_millis(TIME.load(Ordering::Relaxed) as i64);
        while !eth_mac.phy_poll_link() {}
        match net_interface.poll(&mut sockets, clock.elapsed()) {
            Ok(_) => {},
            Err(e) => {
                debug!("poll error: {}", e);
            }
        }

        {
            let mut socket = sockets.get::<net::socket::TcpSocket>(handle);

            info!("Socket state: {} at time {}", socket.state(), clock.elapsed());

            if socket.may_recv() {
                yellow_led.set_high().unwrap();
                red_led.set_low().unwrap();
                let data = socket.recv(|data| {
                    (data.len(), data)
                }).unwrap();
                if socket.can_send() {
                    socket.send_slice("response".as_bytes()).unwrap();
                }
            }
            if socket.may_send() {
                yellow_led.set_high().unwrap();
                red_led.set_low().unwrap();
                debug!("close");
                socket.close();
            }
        }

        match net_interface.poll_delay(&sockets, clock.elapsed()) {
            Some(net::time::Duration {millis :0}) => {
                clock.advance(net::time::Duration::from_millis(1));
                continue;
            }
            Some(time_delay) => {
                green_led.set_low().unwrap();
                delay.delay_ms(time_delay.total_millis() as u32);
                green_led.set_high().unwrap();
                clock.advance(time_delay)
            },
            None => {
                // delay.delay_ms(1_u32);
                clock.advance(net::time::Duration::from_millis(1))
            },
        }
    }
}