use std::{sync::{Arc, Mutex}, thread};

use digest::{FixedOutput, Input};
use rand::rngs::OsRng;
use ripemd160::Ripemd160;
use secp256k1::{key::PublicKey, Secp256k1};
use sha2::Sha256;

fn main() {
    let args = std::env::args();
    if args.len() <= 1 {
        println!("[ERROR] Need at least one argument, e.g. 1Hatter 1UTF8 ...");
        return;
    }
    let continue_on_found = std::env::var("CONTINUE_ON_FOUND")
        .map(|c| c.to_lowercase())
        .map(|c| c == "1" || c == "true" || c == "yes" || c == "on")
        .unwrap_or_else(|_| false);

    let prefixes: Vec<String> = args.skip(1).collect();
    println!("Prefixes: {:?}", prefixes);
    if continue_on_found {
        println!("CONTINUE_ON_FOUND is ON");
    } else {
        println!("CONTINUE_ON_FOUND is OFF");
    }
    let loop_count = std::env::var("LOOP_COUNT")
        .map(|c| c.parse::<u64>())
        .unwrap_or_else(|_| Ok(10_000_000_u64))
        .unwrap_or_else(|_| 10_000_000_u64);
    println!("LOOP_COUNT={}", loop_count);


    let num_of_vcpus = num_cpus::get();
    let num_of_phycpus = num_cpus::get_physical();
    println!("You have {} vCPUs, from {} phyCPUs", num_of_vcpus, num_of_phycpus);
    let concurrent_count = std::env::var("CONCURRENT_COUNT")
        .map(|c| c.parse::<u64>())
        .unwrap_or_else(|_| Ok(num_of_phycpus as u64))
        .unwrap_or_else(|_| 1_u64);
    println!("CONCURRENT_COUNT={}", concurrent_count);

    let stop_flag = Arc::new(Mutex::new(false));
    let mut handles = vec![];
    for ind in 0..concurrent_count {
        println!("- Running task {} of {}", ind, concurrent_count);
        let the_stop_flag = Arc::clone(&stop_flag);
        let prefixes = prefixes.clone();
        let child = thread::spawn(move || {
            run_one_task(prefixes, continue_on_found, loop_count, ind, the_stop_flag);
        });
        handles.push(child);
    }
    while let Some(h) = handles.pop() {
        h.join().unwrap();
    }
    println!("Finished!");
}

fn run_one_task(prefixes: Vec<String>, continue_on_found: bool, loop_count: u64, ind: u64, the_stop_flag: Arc<Mutex<bool>>) {
    let secp = Secp256k1::new();
    let mut rng = OsRng::new().expect("OsRng");

    for i in 0..loop_count {
        let (secret_key, public_key) = secp.generate_keypair(&mut rng);

        let s = make_btc_address(&public_key);

        if i % 1_000 == 0 {
            if *the_stop_flag.lock().unwrap() {
                return;
            }
            if i > 0 && i % 100_000 == 0 {
                println!("> {} - {}", ind, i);
            }
        }
        if prefixes.iter().any(|p| s.starts_with(p)) {
            println!(">> {}\n{}", s, secret_key);

            if !continue_on_found {
                *the_stop_flag.lock().unwrap() = true;
                break;
            }
        }
    }
}

fn make_btc_address(public_key: &PublicKey) -> String {
    let public_key_bytes = public_key.serialize_uncompressed().to_vec();
    let riphemd160_sha256_pub_key = calc_ripemd160(&calc_sha256(&public_key_bytes));

    let mut btc_addr = Vec::<u8>::with_capacity(25);
    btc_addr.push(0x00 as u8);
    btc_addr.extend_from_slice(&riphemd160_sha256_pub_key);

    let checksum = &calc_sha256(&calc_sha256(&btc_addr))[0..4];
    btc_addr.extend_from_slice(checksum);

    bs58::encode(&btc_addr).into_string()
}

#[inline]
fn calc_sha256(i: &[u8]) -> Vec<u8> {
    calc_hash(Sha256::default(), i)
}

#[inline]
fn calc_ripemd160(i: &[u8]) -> Vec<u8> {
    calc_hash(Ripemd160::default(), i)
}

#[inline]
fn calc_hash<T>(mut hasher: T, i: &[u8]) -> Vec<u8> where T: Input + FixedOutput {
    hasher.input(&i);
    hasher.fixed_result().to_vec()
}
