#![allow(clippy::option_map_unit_fn)]
mod utils;

use std::collections::BTreeMap;
use std::os::unix::io::AsRawFd;
use smoltcp::phy::wait as phy_wait;
use smoltcp::wire::{EthernetAddress, Ipv4Address, IpCidr, Ipv4Cidr};
use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder, Routes};
use smoltcp::socket::{SocketSet, RawSocketBuffer, RawPacketMetadata};
use smoltcp::time::Instant;
use smoltcp::dhcp::Dhcpv4Client;

fn main() {
    #[cfg(feature = "log")]
    utils::setup_logging("");

    let (mut opts, mut free) = utils::create_options();
    utils::add_tap_options(&mut opts, &mut free);
    utils::add_middleware_options(&mut opts, &mut free);

    let mut matches = utils::parse_options(&opts, free);
    let device = utils::parse_tap_options(&mut matches);
    let fd = device.as_raw_fd();
    let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/false);

    let neighbor_cache = NeighborCache::new(BTreeMap::new());
    let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]);
    let ip_addrs = [IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 0)];
    let mut routes_storage = [None; 1];
    let routes = Routes::new(&mut routes_storage[..]);
    let mut iface = EthernetInterfaceBuilder::new(device)
            .ethernet_addr(ethernet_addr)
            .neighbor_cache(neighbor_cache)
            .ip_addrs(ip_addrs)
            .routes(routes)
            .finalize();

    let mut sockets = SocketSet::new(vec![]);
    let dhcp_rx_buffer = RawSocketBuffer::new(
        [RawPacketMetadata::EMPTY; 1],
        vec![0; 900]
    );
    let dhcp_tx_buffer = RawSocketBuffer::new(
        [RawPacketMetadata::EMPTY; 1],
        vec![0; 600]
    );
    let mut dhcp = Dhcpv4Client::new(&mut sockets, dhcp_rx_buffer, dhcp_tx_buffer, Instant::now());
    let mut prev_cidr = Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0);
    loop {
        let timestamp = Instant::now();
        iface.poll(&mut sockets, timestamp)
            .map(|_| ())
            .unwrap_or_else(|e| println!("Poll: {:?}", e));
        let config = dhcp.poll(&mut iface, &mut sockets, timestamp)
            .unwrap_or_else(|e| {
                println!("DHCP: {:?}", e);
                None
            });
        config.map(|config| {
            println!("DHCP config: {:?}", config);
            if let Some(cidr) = config.address {
                if cidr != prev_cidr {
                    iface.update_ip_addrs(|addrs| {
                        addrs.iter_mut().next()
                            .map(|addr| {
                                *addr = IpCidr::Ipv4(cidr);
                            });
                    });
                    prev_cidr = cidr;
                    println!("Assigned a new IPv4 address: {}", cidr);
                }
            }

            config.router.map(|router| iface.routes_mut()
                              .add_default_ipv4_route(router)
                              .unwrap()
            );
            iface.routes_mut()
                .update(|routes_map| {
                    routes_map.get(&IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 0))
                        .map(|default_route| {
                            println!("Default gateway: {}", default_route.via_router);
                        });
                });

            if config.dns_servers.iter().any(|s| s.is_some()) {
                println!("DNS servers:");
                for dns_server in config.dns_servers.iter().filter_map(|s| *s) {
                    println!("- {}", dns_server);
                }
            }
        });

        let mut timeout = dhcp.next_poll(timestamp);
        iface.poll_delay(&sockets, timestamp)
            .map(|sockets_timeout| timeout = sockets_timeout);
        phy_wait(fd, Some(timeout))
            .unwrap_or_else(|e| println!("Wait: {:?}", e));
    }
}