//! Search for country data or prefix for a ham radio callsign
//!
//! licensed under CC BY-SA 4.0,
//! To view a copy of this license, visit <http://creativecommons.org/licenses/by-sa/4.0/>
//!
//! Author: Andreas, <df1asc@darc.de>

use clap::Parser;
use hamcty::CountryDatabase;
use regex::Regex;
use std::{io, io::Write, process::exit, sync::LazyLock};

static RE_CALL: LazyLock<Regex> = LazyLock::new(|| {
    Regex::new(r"([a-zA-Z0-9]{1,3}?/)?([a-zA-Z0-9]{1,3}?[0-9][a-zA-Z0-9]{0,3}?[a-zA-Z])(/[aAmMpPrRtT]{1,2}?)?").unwrap()
});

/// Search for country data or prefix for a ham radio callsign
#[derive(Parser, Debug)]
#[command(version, about)]
struct Args {
    /// the CTY database to read (e.g. cty.csv)
    #[arg(default_value = "test_data/cty.csv")]
    file: String,

    /// result is the prefix instead of country data
    #[arg(short, long, default_value_t = false)]
    prefix: bool,

    /// disable callsign validation
    #[arg(long, default_value_t = false)]
    nocheck: bool,

    /// search only for one callsign, print result and exit
    #[arg(short, long)]
    call: Option<String>,
}

fn main() {
    let args = Args::parse();

    let Ok(cdb) = CountryDatabase::load(&args.file) else {
        eprintln!("could not read CTY database at '{}'", &args.file);
        exit(1);
    };

    // process one call and exit
    if let Some(call) = args.call {
        match search_call(&cdb, &call, args.nocheck, args.prefix) {
            Ok(res) => println!("{res}"),
            Err(err) => eprintln!("Error: {err}"),
        }

        return;
    }

    // Enter REPL
    let mut nocheck = args.nocheck;
    let mut prefix = args.prefix;

    println!(
        "To search for country data or prefix type in a callsign and hit <enter>\n\
         Set the result type with 'country' or 'prefix'\n\
         'nocheck' disables callsign validation\n\
         Type 'exit' or 'quit' to leave the shell\n"
    );

    loop {
        print!("callsign> ");
        let _ = io::stdout().flush();
        let mut input = String::new();
        io::stdin()
            .read_line(&mut input)
            .expect("Failed to read line");
        let input = input.trim();

        if ["exit", "quit"].contains(&input) {
            return;
        } else if input == "nocheck" {
            nocheck = true;
            println!(" => callsign validation disabled\n");
            continue;
        } else if input == "country" {
            prefix = false;
            println!(" => set result to 'country'\n");
            continue;
        } else if input == "prefix" {
            prefix = true;
            println!(" => set result to 'prefix'\n");
            continue;
        }

        match search_call(&cdb, input, nocheck, prefix) {
            Ok(res) => println!(" => {res}\n"),
            Err(err) => println!(" => {err}\n"),
        }
    }
}

fn search_call(
    cdb: &CountryDatabase,
    call: impl AsRef<str>,
    nocheck: bool,
    prefix: bool,
) -> Result<String, String> {
    if call.as_ref().chars().all(|c| c.is_ascii_alphanumeric()) {
        if !nocheck && Regex::find(&RE_CALL, &call.as_ref()).is_none() {
            return Err(format!(
                "'{}' does not seem to be a callsign",
                call.as_ref()
            ));
        }

        if !prefix {
            if let Ok(cty) = cdb.country(&call) {
                Ok(format!("{cty:#?}"))
            } else {
                Err(format!(
                    "could not find country data for '{}'",
                    call.as_ref()
                ))
            }
        } else {
            if let Ok(pfx) = cdb.prefix(&call) {
                Ok(format!("{pfx}"))
            } else {
                Err(format!("could not find prefix for '{}'", call.as_ref()))
            }
        }
    } else {
        Err(format!(
            "'{}' contains unallowed characters (only alphabetic and numeric are allowed)",
            call.as_ref()
        ))
    }
}
