// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) DUSK NETWORK. All rights reserved.

use std::error::Error;
use std::io::{self, BufRead};

use clap::{Arg, ArgAction, Command};
use kadcast::config::Config;
use kadcast::{MessageInfo, NetworkListen, Peer};
use rustc_tools_util::{VersionInfo, get_version_info};

#[tokio::main]
pub async fn main() -> Result<(), Box<dyn Error>> {
    let crate_info = get_version_info!();
    // intentionally leak the version string because clap needs a static str
    let version: &'static str =
        Box::leak(show_version(crate_info).into_boxed_str());
    let matches = Command::new(env!("CARGO_PKG_NAME"))
        .version(version)
        .author("Dusk Network B.V. All Rights Reserved.")
        .about("Kadcast Network impl.")
        .arg(
            Arg::new("listen_address")
                .short('l')
                .long("listen")
                .help("Internal address you want to use to listen incoming connections. Eg: 127.0.0.1:696")
                .num_args(1)
                .required(false),
        )
        .arg(
            Arg::new("public_address")
                .short('p')
                .long("address")
                .help("Public address you want to be identified with. Eg: 193.xxx.xxx.198:696")
                .num_args(1)
                .required(true),
        )
        .arg(
            Arg::new("bootstrap")
                .long("bootstrap")
                .short('b')
                .action(ArgAction::Append)
                .help("List of bootstrapping server instances")
                .num_args(1)
                .required(true),
        )
        .arg(
            Arg::new("log-level")
                .long("log-level")
                .value_name("LOG")
                .value_parser(["error", "warn", "info", "debug", "trace"])
                .default_value("info")
                .help("Output log level"),
        )
        .get_matches();

    // Match tracing desired level.
    let log = match matches
        .get_one::<String>("log-level")
        .expect("Failed parsing log-level arg")
        .as_str()
    {
        "error" => tracing::Level::ERROR,
        "warn" => tracing::Level::WARN,
        "info" => tracing::Level::INFO,
        "debug" => tracing::Level::DEBUG,
        "trace" => tracing::Level::TRACE,
        _ => unreachable!(),
    };

    // Generate a subscriber with the desired log level.
    let subscriber = tracing_subscriber::fmt::Subscriber::builder()
        .with_max_level(log)
        .finish();
    // Set the subscriber as global.
    // so this subscriber will be used as the default in all threads for the
    // remainder of the duration of the program, similar to how `loggers`
    // work in the `log` crate.
    tracing::subscriber::set_global_default(subscriber)
        .expect("Failed on subscribe tracing");

    let mut conf = Config::default();
    conf.public_address = matches
        .get_one::<String>("public_address")
        .expect("public_address to have a value")
        .to_string();
    conf.listen_address = matches
        .get_one::<String>("listen_address")
        .map(|a| a.to_string());
    conf.bootstrapping_nodes = matches
        .get_many::<String>("bootstrap")
        .unwrap_or_default()
        .map(|s| s.to_string())
        .collect();

    let peer = Peer::new(conf, DummyListener {})?;
    loop {
        let stdin = io::stdin();
        for message in stdin.lock().lines().flatten() {
            match &message[..] {
                "report" => {
                    peer.report().await;
                }
                v => peer.broadcast(v.as_bytes(), None).await,
            }
        }
    }
}
pub struct DummyListener {}
impl NetworkListen for DummyListener {
    fn on_message(&self, message: Vec<u8>, md: MessageInfo) {
        println!(
            "Received {} from {} (height: {})",
            String::from_utf8(message.to_vec())
                .unwrap_or_else(|_| "No UTF8 message received".to_string()),
            md.src(),
            md.height(),
        );
    }
}

fn show_version(info: VersionInfo) -> String {
    let version = format!("{}.{}.{}", info.major, info.minor, info.patch);
    let build = format!(
        "{} {}",
        info.commit_hash.unwrap_or_default(),
        info.commit_date.unwrap_or_default()
    );

    if build.len() > 1 {
        format!("{} ({})", version, build)
    } else {
        version
    }
}
