use crate::num::arithmetic::traits::UnsignedAbs;
use crate::num::basic::traits::Zero;
use crate::num::conversion::traits::{Digits, ToStringBase, WrappingFrom};
use crate::vecs::vec_pad_left;
use alloc::string::String;
use alloc::string::ToString;
use core::fmt::{Debug, Display, Formatter, Result, Write};
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct BaseFmtWrapper<T> {
pub(crate) x: T,
pub(crate) base: u8,
}
impl<T> BaseFmtWrapper<T> {
pub fn new(x: T, base: u8) -> Self {
assert!((2..=36).contains(&base), "base out of range");
BaseFmtWrapper { x, base }
}
#[allow(clippy::missing_const_for_fn)]
pub fn unwrap(self) -> T {
self.x
}
}
pub const fn digit_to_display_byte_lower(b: u8) -> Option<u8> {
match b {
0..=9 => Some(b + b'0'),
10..=35 => Some(b + b'a' - 10),
_ => None,
}
}
pub const fn digit_to_display_byte_upper(b: u8) -> Option<u8> {
match b {
0..=9 => Some(b + b'0'),
10..=35 => Some(b + b'A' - 10),
_ => None,
}
}
fn fmt_unsigned<T: Copy + Digits<u8> + Eq + Zero>(
w: &BaseFmtWrapper<T>,
f: &mut Formatter,
) -> Result {
let mut digits = w.x.to_digits_desc(&u8::wrapping_from(w.base));
if f.alternate() {
for digit in &mut digits {
*digit = digit_to_display_byte_upper(*digit).unwrap();
}
} else {
for digit in &mut digits {
*digit = digit_to_display_byte_lower(*digit).unwrap();
}
}
if w.x == T::ZERO {
digits.push(b'0');
}
f.pad_integral(true, "", core::str::from_utf8(&digits).unwrap())
}
fn to_string_base_unsigned<T: Copy + Digits<u8> + Eq + Zero>(x: &T, base: u8) -> String {
assert!((2..=36).contains(&base), "base out of range");
if *x == T::ZERO {
"0".to_string()
} else {
let mut digits = x.to_digits_desc(&base);
for digit in &mut digits {
*digit = digit_to_display_byte_lower(*digit).unwrap();
}
String::from_utf8(digits).unwrap()
}
}
fn to_string_base_upper_unsigned<T: Copy + Digits<u8> + Eq + Zero>(x: &T, base: u8) -> String {
assert!((2..=36).contains(&base), "base out of range");
if *x == T::ZERO {
"0".to_string()
} else {
let mut digits = x.to_digits_desc(&base);
for digit in &mut digits {
*digit = digit_to_display_byte_upper(*digit).unwrap();
}
String::from_utf8(digits).unwrap()
}
}
macro_rules! impl_to_string_base_unsigned {
($t:ident) => {
impl Display for BaseFmtWrapper<$t> {
#[inline]
fn fmt(&self, f: &mut Formatter) -> Result {
fmt_unsigned(self, f)
}
}
impl Debug for BaseFmtWrapper<$t> {
#[inline]
fn fmt(&self, f: &mut Formatter) -> Result {
Display::fmt(self, f)
}
}
impl ToStringBase for $t {
#[inline]
fn to_string_base(&self, base: u8) -> String {
to_string_base_unsigned(self, base)
}
#[inline]
fn to_string_base_upper(&self, base: u8) -> String {
to_string_base_upper_unsigned(self, base)
}
}
};
}
apply_to_unsigneds!(impl_to_string_base_unsigned);
fn fmt_signed<T: Copy + Ord + UnsignedAbs + Zero>(
w: &BaseFmtWrapper<T>,
f: &mut Formatter,
) -> Result
where
BaseFmtWrapper<<T as UnsignedAbs>::Output>: Display,
{
if w.x < T::ZERO {
f.write_char('-')?;
if let Some(width) = f.width() {
return if f.alternate() {
write!(
f,
"{:#0width$}",
&BaseFmtWrapper::new(w.x.unsigned_abs(), w.base),
width = width.saturating_sub(1)
)
} else {
write!(
f,
"{:0width$}",
&BaseFmtWrapper::new(w.x.unsigned_abs(), w.base),
width = width.saturating_sub(1)
)
};
}
}
Display::fmt(&BaseFmtWrapper::new(w.x.unsigned_abs(), w.base), f)
}
fn to_string_base_signed<U: Digits<u8>, S: Copy + Eq + Ord + UnsignedAbs<Output = U> + Zero>(
x: &S,
base: u8,
) -> String {
assert!((2..=36).contains(&base), "base out of range");
if *x == S::ZERO {
"0".to_string()
} else {
let mut digits = x.unsigned_abs().to_digits_desc(&u8::wrapping_from(base));
for digit in &mut digits {
*digit = digit_to_display_byte_lower(*digit).unwrap();
}
if *x < S::ZERO {
vec_pad_left(&mut digits, 1, b'-');
}
String::from_utf8(digits).unwrap()
}
}
fn to_string_base_upper_signed<
U: Digits<u8>,
S: Copy + Eq + Ord + UnsignedAbs<Output = U> + Zero,
>(
x: &S,
base: u8,
) -> String {
assert!((2..=36).contains(&base), "base out of range");
if *x == S::ZERO {
"0".to_string()
} else {
let mut digits = x.unsigned_abs().to_digits_desc(&base);
for digit in &mut digits {
*digit = digit_to_display_byte_upper(*digit).unwrap();
}
if *x < S::ZERO {
vec_pad_left(&mut digits, 1, b'-');
}
String::from_utf8(digits).unwrap()
}
}
macro_rules! impl_to_string_base_signed {
($u:ident, $s:ident) => {
impl Display for BaseFmtWrapper<$s> {
#[inline]
fn fmt(&self, f: &mut Formatter) -> Result {
fmt_signed(self, f)
}
}
impl Debug for BaseFmtWrapper<$s> {
#[inline]
fn fmt(&self, f: &mut Formatter) -> Result {
Display::fmt(self, f)
}
}
impl ToStringBase for $s {
#[inline]
fn to_string_base(&self, base: u8) -> String {
to_string_base_signed::<$u, $s>(self, base)
}
#[inline]
fn to_string_base_upper(&self, base: u8) -> String {
to_string_base_upper_signed::<$u, $s>(self, base)
}
}
};
}
apply_to_unsigned_signed_pairs!(impl_to_string_base_signed);