use base64::{Engine as _, engine::general_purpose::URL_SAFE, engine::general_purpose::URL_SAFE_NO_PAD};
use short_uuid::{CustomTranslator, ShortUuidCustom};
use std::fmt::Write;
use uuid::{Uuid, Variant};
use uuid25::Uuid25;
use crate::schema::{Args, IDInfo};
use crate::utils::milliseconds_to_seconds_and_iso8601;
pub const SHORT_UUID_ALPHABET: &str = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
pub const COLOR_MAP_UUID_GENERIC: &str = "22222222222222222222222222222222222222222222222211112222222222220022222222222222222222222222222222222222222222222222222222222222";
pub const COLOR_MAP_UUID_NO_RFC: &str = "22222222222222222222222222222222222222222222222222222222222222220022222222222222222222222222222222222222222222222222222222222222";
pub const COLOR_MAP_UUID_16: &str = "33333333333333333333333333333333333333333333333311113333333333330066666666666666444444444444444444444444444444444444444444444444";
pub const COLOR_MAP_UUID_7: &str = "33333333333333333333333333333333333333333333333311112222222222220022222222222222222222222222222222222222222222222222222222222222";
pub fn parse_uuid(args: &Args) -> Option<IDInfo> {
let uuid = Uuid::try_parse(&args.id).ok()?;
let id_type: String;
let mut version: Option<String> = None;
let mut entropy: u16 = 0;
let mut color_map: Option<String> = Some(COLOR_MAP_UUID_GENERIC.to_string());
let mut datetime: Option<String> = None;
let mut timestamp: Option<String> = None;
if uuid.is_nil() {
id_type = "Nil UUID (all zeros)".to_string();
} else if uuid.is_max() {
id_type = "Max UUID (all ones)".to_string();
} else {
match uuid.get_variant() {
Variant::NCS => {
id_type = "NCS UUID".to_string();
color_map = Some(COLOR_MAP_UUID_NO_RFC.to_string());
}
Variant::Microsoft => {
id_type = "Microsoft GUID".to_string();
color_map = Some(COLOR_MAP_UUID_NO_RFC.to_string());
}
Variant::RFC4122 => {
id_type = match uuid.get_version_num() {
1..6 => "UUID (RFC-4122)".to_string(),
6..9 => "UUID (RFC-9562)".to_string(),
_ => "UUID".to_string(),
};
version = match uuid.get_version_num() {
1 => Some("1 (timestamp and node)".to_string()),
2 => Some("2 (DCE security)".to_string()),
3 => Some("3 (MD5 hash)".to_string()),
4 => Some("4 (random)".to_string()),
5 => Some("5 (SHA-1 hash)".to_string()),
6 => Some("6 (sortable timestamp and node)".to_string()),
7 => Some("7 (sortable timestamp and random)".to_string()),
8 => Some("8 (custom)".to_string()),
ver => Some(format!("{ver} (out of spec)")),
};
entropy = match uuid.get_version_num() {
1 | 6 => 0,
7 => 74,
_ => 122,
};
color_map = match uuid.get_version_num() {
1 | 6 => Some(COLOR_MAP_UUID_16.to_string()),
7 => Some(COLOR_MAP_UUID_7.to_string()),
_ => Some(COLOR_MAP_UUID_GENERIC.to_string()),
};
}
_ => {
id_type = "Unknown UUID-like".to_string();
color_map = Some(COLOR_MAP_UUID_NO_RFC.to_string());
}
}
}
if uuid.get_variant() == Variant::RFC4122
&& let Some(ts) = uuid.get_timestamp()
{
let secs: i64 = ts.to_unix().0.try_into().unwrap();
let nanos: u32 = ts.to_unix().1;
let ms = (secs * 1000) as u64 + (nanos / 1_000_000) as u64;
let formatted_time = milliseconds_to_seconds_and_iso8601(ms, None);
timestamp = Some(formatted_time.0);
datetime = Some(formatted_time.1);
}
let node1: Option<String> = match uuid.get_node_id() {
Some(value) => {
let mut node1_buff: String = "".to_string();
for (i, c) in value.into_iter().enumerate() {
node1_buff.push_str(&hex::encode(vec![c]));
if i < 5 {
node1_buff.push(':');
}
}
Some(node1_buff)
}
None => None,
};
let sequence: Option<u128> = match uuid.get_version_num() {
1 | 6 => {
let sequence_bytes = &uuid.as_bytes()[8..10];
let mut first_byte = sequence_bytes[0];
first_byte <<= 2;
first_byte >>= 2;
Some(u16::from_be_bytes([first_byte, sequence_bytes[1]]).into())
}
_ => None,
};
Some(IDInfo {
id_type,
version,
standard: uuid.to_string(),
integer: Some(uuid.as_u128()),
parsed: Some("from hex".to_string()),
size: 128,
entropy,
datetime,
timestamp,
sequence,
node1,
hex: Some(hex::encode(uuid.as_bytes())),
bits: Some(uuid.as_bytes().iter().fold(String::new(), |mut output, c| {
let _ = write!(output, "{c:08b}");
output
})),
color_map,
..Default::default()
})
}
pub fn parse_short_uuid(args: &Args) -> Option<IDInfo> {
let translator = CustomTranslator::new(SHORT_UUID_ALPHABET).unwrap();
let suuid = ShortUuidCustom::parse_str(&args.id, &translator).ok()?;
let uuid_str = suuid.clone().to_uuid(&translator).ok()?.to_string();
let mut new_args: Args = args.clone();
new_args.id = uuid_str.clone();
let mut id_info = parse_uuid(&new_args)?;
id_info.id_type = format!("ShortUUID of {}", id_info.id_type);
id_info.standard = args.id.to_string();
id_info.uuid_wrap = Some(uuid_str);
id_info.parsed = Some("from base57".to_string());
Some(id_info)
}
pub fn parse_base64_uuid(args: &Args) -> Option<IDInfo> {
let mut padded = true;
let uuid = match URL_SAFE.decode(&args.id) {
Ok(value) => Uuid::from_slice_le(value.as_slice()).ok()?,
Err(_) => match URL_SAFE_NO_PAD.decode(&args.id) {
Ok(value) => {
padded = false;
Uuid::from_slice_le(value.as_slice()).ok()?
}
Err(_) => return None,
},
};
let mut new_args: Args = args.clone();
new_args.id = uuid.to_string();
let mut id_info = parse_uuid(&new_args)?;
if padded {
id_info.id_type = format!("Padded Base64 of {}", id_info.id_type);
} else {
id_info.id_type = format!("Unpadded Base64 of {}", id_info.id_type);
}
id_info.standard = args.id.clone();
id_info.uuid_wrap = Some(uuid.to_string());
id_info.parsed = Some("from base64".to_string());
Some(id_info)
}
pub fn parse_uuid25(args: &Args) -> Option<IDInfo> {
if args.id.chars().count() != 25 {
return None;
}
let uuid_str = Uuid25::parse(&args.id).ok()?.to_hyphenated().to_string();
let mut new_args: Args = args.clone();
new_args.id = uuid_str.clone();
let mut id_info = parse_uuid(&new_args)?;
id_info.id_type = format!("Uuid25 of {}", id_info.id_type);
id_info.standard = args.id.to_string();
id_info.uuid_wrap = Some(uuid_str);
id_info.parsed = Some("from base36".to_string());
Some(id_info)
}
pub fn parse_uuid_integer(args: &Args) -> Option<IDInfo> {
let id_int: u128 = args.id.trim().parse::<u128>().ok()?;
let uuid_str = Uuid::from_u128(id_int).to_string();
let mut new_args: Args = args.clone();
new_args.id = uuid_str.clone();
let mut id_info = parse_uuid(&new_args)?;
id_info.id_type = format!("Integer of {}", id_info.id_type);
id_info.standard = uuid_str.clone();
id_info.parsed = Some("as integer".to_string());
Some(id_info)
}