use core::cell::RefCell;
use core::future::Future;
use core::task::Context;
use core::task::Poll;
use embassy::time::{Instant, Timer};
use embassy::util::ThreadModeMutex;
use embassy::util::{Forever, WakerRegistration};
use futures::pin_mut;
use smoltcp::iface::{InterfaceBuilder, Neighbor, NeighborCache, Route, Routes};
use smoltcp::phy::Device as _;
use smoltcp::phy::Medium;
use smoltcp::socket::SocketSetItem;
use smoltcp::time::Instant as SmolInstant;
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv4Cidr};

use crate::config::Configurator;
use crate::config::Event;
use crate::device::{Device, DeviceAdapter, LinkState};
use crate::fmt::*;
use crate::{Interface, SocketSet};

const ADDRESSES_LEN: usize = 1;
const NEIGHBOR_CACHE_LEN: usize = 8;
const SOCKETS_LEN: usize = 2;
const LOCAL_PORT_MIN: u16 = 1025;
const LOCAL_PORT_MAX: u16 = 65535;

struct StackResources {
    addresses: [IpCidr; ADDRESSES_LEN],
    neighbor_cache: [Option<(IpAddress, Neighbor)>; NEIGHBOR_CACHE_LEN],
    sockets: [Option<SocketSetItem<'static>>; SOCKETS_LEN],
    routes: [Option<(IpCidr, Route)>; 1],
}

static STACK_RESOURCES: Forever<StackResources> = Forever::new();
static STACK: ThreadModeMutex<RefCell<Option<Stack>>> = ThreadModeMutex::new(RefCell::new(None));

pub(crate) struct Stack {
    iface: Interface,
    pub sockets: SocketSet,
    link_up: bool,
    next_local_port: u16,
    configurator: &'static mut dyn Configurator,
    waker: WakerRegistration,
}

impl Stack {
    pub(crate) fn with<R>(f: impl FnOnce(&mut Stack) -> R) -> R {
        let mut stack = STACK.borrow().borrow_mut();
        let stack = stack.as_mut().unwrap();
        f(stack)
    }

    pub fn get_local_port(&mut self) -> u16 {
        let res = self.next_local_port;
        self.next_local_port = if res >= LOCAL_PORT_MAX {
            LOCAL_PORT_MIN
        } else {
            res + 1
        };
        res
    }

    pub(crate) fn wake(&mut self) {
        self.waker.wake()
    }

    fn poll_configurator(&mut self, timestamp: SmolInstant) {
        let medium = self.iface.device().capabilities().medium;

        match self
            .configurator
            .poll(&mut self.iface, &mut self.sockets, timestamp)
        {
            Event::NoChange => {}
            Event::Configured(config) => {
                debug!("Acquired IP configuration:");

                debug!("   IP address:      {}", config.address);
                set_ipv4_addr(&mut self.iface, config.address);

                if medium == Medium::Ethernet {
                    if let Some(gateway) = config.gateway {
                        debug!("   Default gateway: {}", gateway);
                        self.iface
                            .routes_mut()
                            .add_default_ipv4_route(gateway)
                            .unwrap();
                    } else {
                        debug!("   Default gateway: None");
                        self.iface.routes_mut().remove_default_ipv4_route();
                    }
                }
                for (i, s) in config.dns_servers.iter().enumerate() {
                    debug!("   DNS server {}:    {}", i, s);
                }
            }
            Event::Deconfigured => {
                debug!("Lost IP configuration");
                set_ipv4_addr(&mut self.iface, Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0));
                if medium == Medium::Ethernet {
                    self.iface.routes_mut().remove_default_ipv4_route();
                }
            }
        }
    }

    fn poll(&mut self, cx: &mut Context<'_>) {
        self.iface.device_mut().device.register_waker(cx.waker());
        self.waker.register(cx.waker());

        let timestamp = instant_to_smoltcp(Instant::now());
        if let Err(_) = self.iface.poll(&mut self.sockets, timestamp) {
            // If poll() returns error, it may not be done yet, so poll again later.
            cx.waker().wake_by_ref();
            return;
        }

        // Update link up
        let old_link_up = self.link_up;
        self.link_up = self.iface.device_mut().device.link_state() == LinkState::Up;

        // Print when changed
        if old_link_up != self.link_up {
            if self.link_up {
                info!("Link up!");
            } else {
                info!("Link down!");
            }
        }

        if old_link_up || self.link_up {
            self.poll_configurator(timestamp)
        }

        if let Some(poll_at) = self.iface.poll_at(&mut self.sockets, timestamp) {
            let t = Timer::at(instant_from_smoltcp(poll_at));
            pin_mut!(t);
            if t.poll(cx).is_ready() {
                cx.waker().wake_by_ref();
            }
        }
    }
}

fn set_ipv4_addr(iface: &mut Interface, cidr: Ipv4Cidr) {
    iface.update_ip_addrs(|addrs| {
        let dest = addrs.iter_mut().next().unwrap();
        *dest = IpCidr::Ipv4(cidr);
    });
}

/// Initialize embassy_net.
/// This function must be called from thread mode.
pub fn init(device: &'static mut dyn Device, configurator: &'static mut dyn Configurator) {
    const NONE_SOCKET: Option<SocketSetItem<'static>> = None;
    let res = STACK_RESOURCES.put(StackResources {
        addresses: [IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 32)],
        neighbor_cache: [None; NEIGHBOR_CACHE_LEN],
        sockets: [NONE_SOCKET; SOCKETS_LEN],
        routes: [None; 1],
    });

    let medium = device.capabilities().medium;
    let ethernet_addr = if medium == Medium::Ethernet {
        device.ethernet_address()
    } else {
        [0, 0, 0, 0, 0, 0]
    };

    let mut b = InterfaceBuilder::new(DeviceAdapter::new(device));
    b = b.ip_addrs(&mut res.addresses[..]);

    if medium == Medium::Ethernet {
        b = b.ethernet_addr(EthernetAddress(ethernet_addr));
        b = b.neighbor_cache(NeighborCache::new(&mut res.neighbor_cache[..]));
        b = b.routes(Routes::new(&mut res.routes[..]));
    }

    let iface = b.finalize();

    let sockets = SocketSet::new(&mut res.sockets[..]);

    let local_port = loop {
        let mut res = [0u8; 2];
        rand(&mut res);
        let port = u16::from_le_bytes(res);
        if port >= LOCAL_PORT_MIN && port <= LOCAL_PORT_MAX {
            break port;
        }
    };

    let stack = Stack {
        iface,
        sockets,
        link_up: false,
        configurator,
        next_local_port: local_port,
        waker: WakerRegistration::new(),
    };

    *STACK.borrow().borrow_mut() = Some(stack);
}

pub fn is_init() -> bool {
    STACK.borrow().borrow().is_some()
}

pub async fn run() {
    futures::future::poll_fn(|cx| {
        Stack::with(|stack| stack.poll(cx));
        Poll::<()>::Pending
    })
    .await
}

fn instant_to_smoltcp(instant: Instant) -> SmolInstant {
    SmolInstant::from_millis(instant.as_millis() as i64)
}

fn instant_from_smoltcp(instant: SmolInstant) -> Instant {
    Instant::from_millis(instant.total_millis() as u64)
}

extern "Rust" {
    fn _embassy_rand(buf: &mut [u8]);
}

fn rand(buf: &mut [u8]) {
    unsafe { _embassy_rand(buf) }
}