use clap::{ArgGroup, Command, Id, arg, command, crate_authors};
use lib_porcelain::Dollcode;
#[cfg(feature = "bigint")]
use num::{BigUint, Num, traits::ToBytes};
use std::str::FromStr;

fn main() {
    let matches = command!()
        .author(crate_authors!("\n"))
        .subcommand_required(true)
        .subcommand(
            Command::new("encode")
                .arg(arg!([input] "Input to encode to a dollcode").required(true))
                .arg(arg!(hex: -x --hex "Encode from hex"))
                .arg(arg!(string: -s --string "Encode from a utf8 string")),
        )
        .subcommand(
            Command::new("decode")
                .arg(arg!([dollcode] "Dollcode to decode").required(true))
                .arg(arg!(decimial: -d --decimial "Decode to decimial"))
                .arg(arg!(hex: -x --hex "Decode to hexadecimal"))
                .arg(arg!(string: -s --string "Decode to utf8 string"))
                .group(ArgGroup::new("format").args(["decimial", "hex", "string"])),
        )
        .get_matches();

    if let Some(ematches) = matches.subcommand_matches("encode") {
        let input = ematches.get_one::<String>("input").unwrap();

        if ematches.get_flag("string") {
            let std_int = |s: &str| {
                let mut buf = [0u8; 8];
                let len = 8.min(s.len());
                buf[..len].copy_from_slice(&s.as_bytes()[..len]);
                u64::from_be_bytes(buf)
            };

            println!(
                "{}",
                (match cfg!(feature = "bigint") {
                    true => BigUint::from_bytes_be(input.clone().as_bytes()).to_dollcode(),
                    false => std_int(input).to_dollcode(),
                })
            );
            return;
        }

        if let Some(s) = input.strip_prefix("0x") {
            println!(
                "{}",
                (match cfg!(feature = "bigint") {
                    true => BigUint::from_str_radix(s, 16)
                        .expect("invalid hex")
                        .to_dollcode(),
                    false => u64::from_str_radix(s, 16)
                        .expect("invalid hex")
                        .to_dollcode(),
                })
            );
            return;
        }

        if ematches.get_flag("hex") {
            println!(
                "{}",
                (match cfg!(feature = "bigint") {
                    true => BigUint::from_str_radix(input, 16)
                        .expect("invalid hex")
                        .to_dollcode(),
                    false => u64::from_str_radix(input, 16)
                        .expect("invalid hex")
                        .to_dollcode(),
                })
            );
            return;
        }

        println!(
            "{}",
            (match cfg!(feature = "bigint") {
                true => BigUint::from_str(input)
                    .expect("input must be declared as hex or be a decimial number")
                    .to_dollcode(),
                false => input
                    .parse::<u64>()
                    .expect("input must be declared as hex or be a decimial number")
                    .to_dollcode(),
            })
        );
    }

    if let Some(dmatches) = matches.subcommand_matches("decode") {
        let dollcode = dmatches.get_one::<String>("dollcode").unwrap();

        let err = "dollcodes may only contain '▖', '▘', or '▌'";

        #[cfg(feature = "bigint")]
        let res = BigUint::from_dollcode(dollcode).expect(err);
        #[cfg(not(feature = "bigint"))]
        let res = u64::from_dollcode(dollcode).expect(err);

        let res_bytes = res.clone().to_be_bytes();
        let str_res = str::from_utf8(&res_bytes).expect("failed to encode res as utf8");

        match dmatches.get_one("format").map(Id::as_ref) {
            Some("decimial") => println!("{}", res),
            Some("hex") => println!("{:X}", res),
            Some("string") => println!("{}", str_res),
            None => println!(
                "decimial: {}, hexadecimal: {:X}, string: {}",
                res, res, str_res
            ),
            Some(_) => unreachable!(),
        }
    }
}
