//! This is a CLI application that uses the Client module of this crate's library.
//!
//! This binary is only built if the `v4_client` feature is enabled.
//! The `v4_client` feature is enabled by default.

use clap::{Parser, Subcommand};
use std::{
    net::{Ipv4Addr, SocketAddrV4},
    time::Instant,
};
use toe_beans::v4::{Client, ClientConfig, Message, RESOLVED_SERVER_PORT, init_logger};
use tracing::info;

/// A DHCPv4 client
#[derive(Parser, Debug)]
#[command(version)]
struct Arguments {
    /// There is a command for each type of interaction a client would want to make with a server
    #[command(subcommand)]
    command: Commands,
    /// These are global arguments that apply to all commands and not just a single one.
    #[command(flatten)]
    client_config: ClientConfig,
}

// #[allow(clippy::large_enum_variant)]
/// All subcommands
#[derive(Subcommand, Debug)]
enum Commands {
    /// Lease an IP address from the server
    Dora {},
    /// Send a release message to the server
    Release {},
    /// Send an inform message to the server
    Inform {},
    /// Send a decline message to the server
    Decline {},
}

fn main() {
    let now = Instant::now();

    init_logger(true);

    let args = Arguments::parse();
    let config = args.client_config;
    let mut client = Client::new(config);

    let server_address = SocketAddrV4::new(Ipv4Addr::LOCALHOST, RESOLVED_SERVER_PORT);
    match &args.command {
        Commands::Dora {} => {
            let message = client.discover();
            info!("Sending discover message");
            client
                .socket
                .broadcast(&message, RESOLVED_SERVER_PORT)
                .unwrap();

            info!("Waiting for offer message");
            let (offer, _src) = client.socket.receive().unwrap();
            info!("Received offer message");

            let message = client.request(offer);
            info!("Sending request message");
            client
                .socket
                .broadcast(&message, RESOLVED_SERVER_PORT)
                .unwrap();

            info!("Waiting for ack message");
            let (ack, _src) = client.socket.receive::<Message>().unwrap();
            info!("Received ack message");

            info!("Your ip address: {:?}", ack.yiaddr);
        }
        Commands::Release {} => {
            let message = client.release();
            info!("Sending release message");
            client.socket.unicast(&message, server_address).unwrap();
        }
        Commands::Inform {} => {
            let message = client.inform(Vec::new());
            info!("Sending inform message");
            client.socket.unicast(&message, server_address).unwrap();
        }
        Commands::Decline {} => todo!("Send a decline request"),
    }

    info!("Completed in {} microseconds", now.elapsed().as_micros());
}
