uuinfo 0.1.0

A tool to debug unique identifiers (UUID, ULID, Snowflake, etc).
use crate::schema::{Args, IDInfo, IdFormat};
use crate::utils::{bits64, milliseconds_to_seconds_and_iso8601};
use std::fmt::Write;

#[derive(Debug)]
struct SnowflakeAnnotation {
    version: Option<String>,
    datetime: Option<String>,
    timestamp: Option<String>,
    node1: Option<String>,
    node2: Option<String>,
    sequence: Option<u128>,
    color_map: Option<String>,
}

impl Default for SnowflakeAnnotation {
    fn default() -> SnowflakeAnnotation {
        Self {
            version: Some("Unknown (use -f to specify version)".to_string()),
            datetime: None,
            timestamp: None,
            node1: None,
            node2: None,
            sequence: None,
            color_map: Some("0000000000000000000000000000000000000000000000000000000000000000".to_string()),
        }
    }
}

fn annotate_twitter(args: &Args) -> SnowflakeAnnotation {
    let id_int: u64 = args.id.trim().parse().unwrap();
    let timestamp_raw = bits64(id_int, 1, 41);
    let worker_id = bits64(id_int, 42, 10);
    let sequence = bits64(id_int, 52, 12);
    let (timestamp, datetime) = milliseconds_to_seconds_and_iso8601(timestamp_raw, Some(1288834974657));
    SnowflakeAnnotation {
        version: Some("Twitter".to_string()),
        datetime: Some(datetime),
        timestamp: Some(timestamp),
        node1: Some(format!("{} (Worker ID)", worker_id)),
        node2: None,
        sequence: Some(sequence as u128),
        color_map: Some("0333333333333333333333333333333333333333334444444444666666666666".to_string()),
    }
}

fn annotate_discord(args: &Args) -> SnowflakeAnnotation {
    let id_int: u64 = args.id.trim().parse().unwrap();
    let timestamp_raw = bits64(id_int, 0, 42);
    let worker_id = bits64(id_int, 42, 5);
    let process_id = bits64(id_int, 47, 5);
    let sequence = bits64(id_int, 52, 12);
    let (timestamp, datetime) = milliseconds_to_seconds_and_iso8601(timestamp_raw, Some(1420070400000));
    SnowflakeAnnotation {
        version: Some("Discord".to_string()),
        datetime: Some(datetime),
        timestamp: Some(timestamp),
        node1: Some(format!("{} (Worker ID)", worker_id)),
        node2: Some(format!("{} (Process ID)", process_id)),
        sequence: Some(sequence as u128),
        color_map: Some("3333333333333333333333333333333333333333334444455555666666666666".to_string()),
    }
}

fn annotate_instagram(args: &Args) -> SnowflakeAnnotation {
    let id_int: u64 = args.id.trim().parse().unwrap();
    let timestamp_raw = bits64(id_int, 0, 41);
    let shard_id = bits64(id_int, 41, 13);
    let sequence = bits64(id_int, 54, 10);
    let (timestamp, datetime) = milliseconds_to_seconds_and_iso8601(timestamp_raw, Some(1314220021721));
    SnowflakeAnnotation {
        version: Some("Instagram".to_string()),
        datetime: Some(datetime),
        timestamp: Some(timestamp),
        node1: Some(format!("{} (Shard ID)", shard_id)),
        node2: None,
        sequence: Some(sequence as u128),
        color_map: Some("3333333333333333333333333333333333333333344444444444446666666666".to_string()),
    }
}

fn annotate_sony(args: &Args) -> SnowflakeAnnotation {
    let id_int: u64 = args.id.trim().parse().unwrap();
    let timestamp_raw = bits64(id_int, 1, 39);
    let sequence = bits64(id_int, 40, 8);
    let machine_id = bits64(id_int, 48, 16);
    let (timestamp, datetime) = milliseconds_to_seconds_and_iso8601(timestamp_raw * 10, Some(1409529600000));
    SnowflakeAnnotation {
        version: Some("Sony".to_string()),
        datetime: Some(datetime),
        timestamp: Some(timestamp),
        node1: Some(format!("{} (Machine ID)", machine_id)),
        node2: None,
        sequence: Some(sequence as u128),
        color_map: Some("0333333333333333333333333333333333333333666666664444444444444444".to_string()),
    }
}

fn annotate_spaceflake(args: &Args) -> SnowflakeAnnotation {
    let id_int: u64 = args.id.trim().parse().unwrap();
    let timestamp_raw = bits64(id_int, 1, 41);
    let node_id = bits64(id_int, 42, 5);
    let worker_id = bits64(id_int, 47, 5);
    let sequence = bits64(id_int, 52, 12);
    let (timestamp, datetime) = milliseconds_to_seconds_and_iso8601(timestamp_raw, Some(1420070400000));
    SnowflakeAnnotation {
        version: Some("Spaceflake".to_string()),
        datetime: Some(datetime),
        timestamp: Some(timestamp),
        node1: Some(format!("{} (Node ID)", node_id)),
        node2: Some(format!("{} (Worker ID)", worker_id)),
        sequence: Some(sequence as u128),
        color_map: Some("0333333333333333333333333333333333333333334444455555666666666666".to_string()),
    }
}

fn annotate_linkedin(args: &Args) -> SnowflakeAnnotation {
    let id_int: u64 = args.id.trim().parse().unwrap();
    let timestamp_raw = bits64(id_int, 1, 41);
    let worker_id = bits64(id_int, 42, 10);
    let sequence = bits64(id_int, 52, 12);
    let (timestamp, datetime) = milliseconds_to_seconds_and_iso8601(timestamp_raw, None);
    SnowflakeAnnotation {
        version: Some("LinkedIn".to_string()),
        datetime: Some(datetime),
        timestamp: Some(timestamp),
        node1: Some(format!("{} (Worker ID)", worker_id)),
        node2: None,
        sequence: Some(sequence as u128),
        color_map: Some("0333333333333333333333333333333333333333334444444444666666666666".to_string()),
    }
}

fn annotate_mastodon(args: &Args) -> SnowflakeAnnotation {
    let id_int: u64 = args.id.trim().parse().unwrap();
    let timestamp_raw = bits64(id_int, 0, 48);
    let sequence = bits64(id_int, 48, 16);
    let (timestamp, datetime) = milliseconds_to_seconds_and_iso8601(timestamp_raw, None);
    SnowflakeAnnotation {
        version: Some("Mastodon".to_string()),
        datetime: Some(datetime),
        timestamp: Some(timestamp),
        node1: None,
        node2: None,
        sequence: Some(sequence as u128),
        color_map: Some("3333333333333333333333333333333333333333333333336666666666666666".to_string()),
    }
}

fn annotate_snowflake_variant(args: &Args) -> SnowflakeAnnotation {
    match args.force {
        Some(value) => {
            let annotation: SnowflakeAnnotation = match value {
                IdFormat::SfTwitter => annotate_twitter(args),
                IdFormat::SfMastodon => annotate_mastodon(args),
                IdFormat::SfDiscord => annotate_discord(args),
                IdFormat::SfInstagram => annotate_instagram(args),
                IdFormat::SfSony => annotate_sony(args),
                IdFormat::SfSpaceflake => annotate_spaceflake(args),
                IdFormat::SfLinkedin => annotate_linkedin(args),
                _ => SnowflakeAnnotation::default(),
            };
            annotation
        }
        None => SnowflakeAnnotation::default(),
    }
}

pub fn parse_snowflake(args: &Args) -> Option<IDInfo> {
    let id_int: u64 = match args.id.trim().parse::<u64>() {
        Ok(value) => value,
        Err(_) => return None,
    };

    let annotation = annotate_snowflake_variant(args);

    Some(IDInfo {
        id_type: "Snowflake".to_string(),
        version: annotation.version,
        standard: id_int.to_string(),
        integer: Some(id_int as u128),
        short_uuid: None,
        base64: None,
        uuid_wrap: None,
        size: 64,
        entropy: 0,
        datetime: annotation.datetime,
        timestamp: annotation.timestamp,
        sequence: annotation.sequence,
        node1: annotation.node1,
        node2: annotation.node2,
        hex: Some(hex::encode(id_int.to_be_bytes())),
        bits: Some(id_int.to_be_bytes().iter().fold(String::new(), |mut output, c| {
            let _ = write!(output, "{c:08b}");
            output
        })),
        color_map: annotation.color_map,
    })
}

pub fn compare_snowflake(args: &Args) {
    if args.id.trim().parse::<u64>().is_ok() {
        println!("Date/times of the Snowflake ID if parsed as:");
        let mut snowflake_times = vec![
            format!("- {} Twitter", annotate_twitter(args).datetime.unwrap_or("".to_string())),
            format!("- {} Discord", annotate_discord(args).datetime.unwrap_or("".to_string())),
            format!("- {} Instagram", annotate_instagram(args).datetime.unwrap_or("".to_string())),
            format!("- {} Sony", annotate_sony(args).datetime.unwrap_or("".to_string())),
            format!("- {} Spaceflake", annotate_spaceflake(args).datetime.unwrap_or("".to_string())),
            format!("- {} LinkedIn", annotate_linkedin(args).datetime.unwrap_or("".to_string())),
            format!("- {} Mastodon", annotate_mastodon(args).datetime.unwrap_or("".to_string())),
        ];
        snowflake_times.sort();
        for time in &snowflake_times {
            println!("{time}");
        }
    } else {
        println!("Not a valid Snowflake ID.");
    }
    std::process::exit(0);
}