use crate::xdr::SCSYMBOL_LIMIT;
use crate::{
    declare_tag_based_small_and_object_wrappers, val::ValConvert, Compare, ConversionError, Env,
    Tag, TryFromVal, Val,
};
use core::{cmp::Ordering, fmt::Debug, hash::Hash, str};
declare_tag_based_small_and_object_wrappers!(Symbol, SymbolSmall, SymbolObject);
#[derive(Debug)]
pub enum SymbolError {
    TooLong(usize),
    BadChar(u8),
    MalformedSmall,
}
impl core::fmt::Display for SymbolError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            SymbolError::TooLong(len) => f.write_fmt(format_args!(
                "symbol too long: length {len}, max {MAX_SMALL_CHARS}"
            )),
            SymbolError::BadChar(char) => f.write_fmt(format_args!(
                "symbol bad char: encountered {char}, supported range [a-zA-Z0-9_]"
            )),
            SymbolError::MalformedSmall => f.write_str("malformed small symbol"),
        }
    }
}
impl From<SymbolError> for ConversionError {
    fn from(_: SymbolError) -> Self {
        ConversionError
    }
}
extern crate static_assertions as sa;
use super::val::BODY_BITS;
pub(crate) const MAX_SMALL_CHARS: usize = 9;
const CODE_BITS: usize = 6;
const CODE_MASK: u64 = (1u64 << CODE_BITS) - 1;
const SMALL_MASK: u64 = (1u64 << (MAX_SMALL_CHARS * CODE_BITS)) - 1;
sa::const_assert!(CODE_MASK == 0x3f);
sa::const_assert!(CODE_BITS * MAX_SMALL_CHARS + 2 == BODY_BITS);
sa::const_assert!(SMALL_MASK == 0x003f_ffff_ffff_ffff);
impl<E: Env> TryFromVal<E, &[u8]> for Symbol {
    type Error = crate::Error;
    fn try_from_val(env: &E, v: &&[u8]) -> Result<Self, Self::Error> {
        if let Ok(s) = SymbolSmall::try_from_bytes(v) {
            Ok(s.into())
        } else {
            env.symbol_new_from_slice(v)
                .map(Into::into)
                .map_err(Into::into)
        }
    }
}
impl<E: Env> TryFromVal<E, &str> for Symbol {
    type Error = crate::Error;
    fn try_from_val(env: &E, v: &&str) -> Result<Self, Self::Error> {
        Symbol::try_from_val(env, &v.as_bytes())
    }
}
impl<E: Env> Compare<Symbol> for E {
    type Error = E::Error;
    fn compare(&self, a: &Symbol, b: &Symbol) -> Result<Ordering, Self::Error> {
        let taga = a.0.get_tag();
        let tagb = b.0.get_tag();
        match taga.cmp(&tagb) {
            Ordering::Equal => {
                if taga == Tag::SymbolSmall {
                    let ssa = unsafe { SymbolSmall::unchecked_from_val(a.0) };
                    let ssb = unsafe { SymbolSmall::unchecked_from_val(b.0) };
                    Ok(ssa.cmp(&ssb))
                } else {
                    let soa = unsafe { SymbolObject::unchecked_from_val(a.0) };
                    let sob = unsafe { SymbolObject::unchecked_from_val(b.0) };
                    self.compare(&soa, &sob)
                }
            }
            other => Ok(other),
        }
    }
}
impl SymbolSmall {
    #[doc(hidden)]
    pub const fn try_from_body(body: u64) -> Result<Self, SymbolError> {
        if body & SMALL_MASK != body {
            Err(SymbolError::MalformedSmall)
        } else {
            Ok(unsafe { SymbolSmall::from_body(body) })
        }
    }
}
impl Symbol {
    pub const fn try_from_small_bytes(b: &[u8]) -> Result<Self, SymbolError> {
        match SymbolSmall::try_from_bytes(b) {
            Ok(sym) => Ok(Symbol(sym.0)),
            Err(e) => Err(e),
        }
    }
    pub const fn try_from_small_str(s: &str) -> Result<Self, SymbolError> {
        Self::try_from_small_bytes(s.as_bytes())
    }
    #[cfg(feature = "testutils")]
    pub const fn from_small_str(s: &str) -> Self {
        Symbol(SymbolSmall::from_str(s).0)
    }
}
impl Ord for SymbolSmall {
    fn cmp(&self, other: &Self) -> Ordering {
        Iterator::cmp(self.into_iter(), *other)
    }
}
impl TryFrom<&[u8]> for SymbolSmall {
    type Error = SymbolError;
    fn try_from(b: &[u8]) -> Result<SymbolSmall, SymbolError> {
        Self::try_from_bytes(b)
    }
}
#[cfg(feature = "std")]
use crate::xdr::StringM;
#[cfg(feature = "std")]
impl<const N: u32> TryFrom<StringM<N>> for SymbolSmall {
    type Error = SymbolError;
    fn try_from(v: StringM<N>) -> Result<Self, Self::Error> {
        v.as_slice().try_into()
    }
}
#[cfg(feature = "std")]
impl<const N: u32> TryFrom<&StringM<N>> for SymbolSmall {
    type Error = SymbolError;
    fn try_from(v: &StringM<N>) -> Result<Self, Self::Error> {
        v.as_slice().try_into()
    }
}
impl SymbolSmall {
    #[doc(hidden)]
    pub const fn validate_byte(b: u8) -> Result<(), SymbolError> {
        match Self::encode_byte(b) {
            Ok(_) => Ok(()),
            Err(e) => Err(e),
        }
    }
    const fn encode_byte(b: u8) -> Result<u8, SymbolError> {
        let v = match b {
            b'_' => 1,
            b'0'..=b'9' => 2 + (b - b'0'),
            b'A'..=b'Z' => 12 + (b - b'A'),
            b'a'..=b'z' => 38 + (b - b'a'),
            _ => return Err(SymbolError::BadChar(b)),
        };
        Ok(v)
    }
    pub const fn try_from_bytes(bytes: &[u8]) -> Result<SymbolSmall, SymbolError> {
        if bytes.len() > MAX_SMALL_CHARS {
            return Err(SymbolError::TooLong(bytes.len()));
        }
        let mut n = 0;
        let mut accum: u64 = 0;
        while n < bytes.len() {
            let v = match Self::encode_byte(bytes[n]) {
                Ok(v) => v,
                Err(e) => return Err(e),
            };
            accum <<= CODE_BITS;
            accum |= v as u64;
            n += 1;
        }
        Ok(unsafe { Self::from_body(accum) })
    }
    pub const fn try_from_str(s: &str) -> Result<SymbolSmall, SymbolError> {
        Self::try_from_bytes(s.as_bytes())
    }
    #[doc(hidden)]
    pub const unsafe fn get_body(&self) -> u64 {
        self.0.get_body()
    }
    #[cfg(feature = "testutils")]
    pub const fn from_str(s: &str) -> SymbolSmall {
        match Self::try_from_str(s) {
            Ok(sym) => sym,
            Err(SymbolError::TooLong(_)) => panic!("symbol too long"),
            Err(SymbolError::BadChar(_)) => panic!("symbol bad char"),
            Err(SymbolError::MalformedSmall) => panic!("malformed small symbol"),
        }
    }
    pub fn to_str(&self) -> SymbolStr {
        sa::const_assert!(SCSYMBOL_LIMIT as usize >= MAX_SMALL_CHARS);
        let mut chars = [b'\x00'; SCSYMBOL_LIMIT as usize];
        for (src, dst) in self.into_iter().zip(chars.iter_mut()) {
            *dst = src as u8
        }
        SymbolStr(chars)
    }
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SymbolStr([u8; SCSYMBOL_LIMIT as usize]);
impl SymbolStr {
    pub fn is_empty(&self) -> bool {
        self.0[0] == 0
    }
    pub fn len(&self) -> usize {
        let s: &[u8] = &self.0;
        for (i, x) in s.iter().enumerate() {
            if *x == 0 {
                return i;
            }
        }
        s.len()
    }
}
impl Debug for SymbolStr {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        let s: &str = self.as_ref();
        f.debug_tuple("SymbolStr").field(&s).finish()
    }
}
impl AsRef<[u8]> for SymbolStr {
    fn as_ref(&self) -> &[u8] {
        let s: &[u8] = &self.0;
        &s[..self.len()]
    }
}
impl AsRef<str> for SymbolStr {
    fn as_ref(&self) -> &str {
        let s: &[u8] = self.as_ref();
        unsafe { str::from_utf8_unchecked(s) }
    }
}
impl From<&SymbolSmall> for SymbolStr {
    fn from(s: &SymbolSmall) -> Self {
        s.to_str()
    }
}
impl From<SymbolSmall> for SymbolStr {
    fn from(s: SymbolSmall) -> Self {
        (&s).into()
    }
}
impl<E: Env> TryFromVal<E, Symbol> for SymbolStr {
    type Error = crate::Error;
    fn try_from_val(env: &E, v: &Symbol) -> Result<Self, Self::Error> {
        if let Ok(ss) = SymbolSmall::try_from(*v) {
            Ok(ss.into())
        } else {
            let obj: SymbolObject = unsafe { SymbolObject::unchecked_from_val(v.0) };
            let mut arr = [0u8; SCSYMBOL_LIMIT as usize];
            let len: u32 = env.symbol_len(obj).map_err(Into::into)?.into();
            if let Some(slice) = arr.get_mut(..len as usize) {
                env.symbol_copy_to_slice(obj, Val::U32_ZERO, slice)
                    .map_err(Into::into)?;
                Ok(SymbolStr(arr))
            } else {
                Err(crate::Error::from_type_and_code(
                    crate::xdr::ScErrorType::Value,
                    crate::xdr::ScErrorCode::InternalError,
                ))
            }
        }
    }
}
#[cfg(feature = "std")]
use std::string::{String, ToString};
#[cfg(feature = "std")]
impl From<SymbolSmall> for String {
    fn from(s: SymbolSmall) -> Self {
        s.to_string()
    }
}
#[cfg(feature = "std")]
impl From<SymbolStr> for String {
    fn from(s: SymbolStr) -> Self {
        s.to_string()
    }
}
#[cfg(feature = "std")]
impl ToString for SymbolSmall {
    fn to_string(&self) -> String {
        self.into_iter().collect()
    }
}
#[cfg(feature = "std")]
impl ToString for SymbolStr {
    fn to_string(&self) -> String {
        let s: &str = self.as_ref();
        s.to_string()
    }
}
impl IntoIterator for SymbolSmall {
    type Item = char;
    type IntoIter = SymbolSmallIter;
    fn into_iter(self) -> Self::IntoIter {
        SymbolSmallIter(self.as_val().get_body())
    }
}
#[repr(transparent)]
#[derive(Clone)]
pub struct SymbolSmallIter(u64);
impl Iterator for SymbolSmallIter {
    type Item = char;
    fn next(&mut self) -> Option<Self::Item> {
        while self.0 != 0 {
            let res = match ((self.0 >> ((MAX_SMALL_CHARS - 1) * CODE_BITS)) & CODE_MASK) as u8 {
                1 => b'_',
                n @ (2..=11) => b'0' + n - 2,
                n @ (12..=37) => b'A' + n - 12,
                n @ (38..=63) => b'a' + n - 38,
                _ => b'\0',
            };
            self.0 <<= CODE_BITS;
            if res != b'\0' {
                return Some(res as char);
            }
        }
        None
    }
}
#[cfg(feature = "testutils")]
impl FromIterator<char> for SymbolSmall {
    fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
        let mut accum: u64 = 0;
        for (n, i) in iter.into_iter().enumerate() {
            if n >= MAX_SMALL_CHARS {
                panic!("too many chars for SymbolSmall");
            }
            accum <<= CODE_BITS;
            let v = match i {
                '_' => 1,
                '0'..='9' => 2 + ((i as u64) - ('0' as u64)),
                'A'..='Z' => 12 + ((i as u64) - ('A' as u64)),
                'a'..='z' => 38 + ((i as u64) - ('a' as u64)),
                _ => break,
            };
            accum |= v;
        }
        unsafe { Self::from_body(accum) }
    }
}
#[cfg(feature = "std")]
use crate::xdr::{ScSymbol, ScVal};
#[cfg(feature = "std")]
impl TryFrom<ScVal> for SymbolSmall {
    type Error = crate::Error;
    fn try_from(v: ScVal) -> Result<Self, Self::Error> {
        (&v).try_into()
    }
}
#[cfg(feature = "std")]
impl TryFrom<&ScVal> for SymbolSmall {
    type Error = crate::Error;
    fn try_from(v: &ScVal) -> Result<Self, Self::Error> {
        if let ScVal::Symbol(ScSymbol(vec)) = v {
            vec.try_into().map_err(Into::into)
        } else {
            Err(ConversionError.into())
        }
    }
}
#[cfg(feature = "std")]
impl<E: Env> TryFromVal<E, ScVal> for Symbol {
    type Error = crate::Error;
    fn try_from_val(env: &E, v: &ScVal) -> Result<Self, Self::Error> {
        Symbol::try_from_val(env, &v)
    }
}
#[cfg(feature = "std")]
impl<E: Env> TryFromVal<E, &ScVal> for Symbol {
    type Error = crate::Error;
    fn try_from_val(env: &E, v: &&ScVal) -> Result<Self, Self::Error> {
        if let ScVal::Symbol(sym) = v {
            Symbol::try_from_val(env, &sym)
        } else {
            Err(ConversionError.into())
        }
    }
}
#[cfg(feature = "std")]
impl<E: Env> TryFromVal<E, ScSymbol> for Symbol {
    type Error = crate::Error;
    fn try_from_val(env: &E, v: &ScSymbol) -> Result<Self, Self::Error> {
        Symbol::try_from_val(env, &v)
    }
}
#[cfg(feature = "std")]
impl<E: Env> TryFromVal<E, &ScSymbol> for Symbol {
    type Error = crate::Error;
    fn try_from_val(env: &E, v: &&ScSymbol) -> Result<Self, Self::Error> {
        Symbol::try_from_val(env, &v.0.as_slice())
    }
}
#[cfg(feature = "std")]
impl TryFrom<SymbolSmall> for ScVal {
    type Error = crate::Error;
    fn try_from(s: SymbolSmall) -> Result<Self, crate::Error> {
        let res: Result<Vec<u8>, _> = s.into_iter().map(<u8 as TryFrom<char>>::try_from).collect();
        let vec = res.map_err(|_| {
            crate::Error::from_type_and_code(
                crate::xdr::ScErrorType::Value,
                crate::xdr::ScErrorCode::InvalidInput,
            )
        })?;
        Ok(ScVal::Symbol(vec.try_into()?))
    }
}
#[cfg(feature = "std")]
impl TryFrom<&SymbolSmall> for ScVal {
    type Error = crate::Error;
    fn try_from(s: &SymbolSmall) -> Result<Self, crate::Error> {
        (*s).try_into()
    }
}
#[cfg(feature = "std")]
impl<E: Env> TryFromVal<E, Symbol> for ScVal {
    type Error = crate::Error;
    fn try_from_val(e: &E, s: &Symbol) -> Result<Self, crate::Error> {
        Ok(ScVal::Symbol(ScSymbol::try_from_val(e, s)?))
    }
}
#[cfg(feature = "std")]
impl<E: Env> TryFromVal<E, Symbol> for ScSymbol {
    type Error = crate::Error;
    fn try_from_val(e: &E, s: &Symbol) -> Result<Self, crate::Error> {
        let sstr = SymbolStr::try_from_val(e, s)?;
        Ok(ScSymbol(sstr.0.as_slice()[0..sstr.len()].try_into()?))
    }
}
#[cfg(test)]
mod test_without_string {
    use super::{SymbolSmall, SymbolStr};
    #[test]
    fn test_roundtrip() {
        let input = "stellar";
        let sym = SymbolSmall::try_from_str(input).unwrap();
        let sym_str = SymbolStr::from(sym);
        let s: &str = sym_str.as_ref();
        assert_eq!(s, input);
    }
    #[test]
    fn test_roundtrip_zero() {
        let input = "";
        let sym = SymbolSmall::try_from_str(input).unwrap();
        let sym_str = SymbolStr::from(sym);
        let s: &str = sym_str.as_ref();
        assert_eq!(s, input);
    }
    #[test]
    fn test_roundtrip_nine() {
        let input = "123456789";
        let sym = SymbolSmall::try_from_str(input).unwrap();
        let sym_str = SymbolStr::from(sym);
        let s: &str = sym_str.as_ref();
        assert_eq!(s, input);
    }
    #[test]
    fn test_enc() {
        let vectors: &[(&str, u64)] = &[
            ("a",           0b__000_000__000_000__000_000__000_000__000_000__000_000__000_000__000_000__100_110_u64),
            ("ab",          0b__000_000__000_000__000_000__000_000__000_000__000_000__000_000__100_110__100_111_u64),
            ("abc",         0b__000_000__000_000__000_000__000_000__000_000__000_000__100_110__100_111__101_000_u64),
            ("ABC",         0b__000_000__000_000__000_000__000_000__000_000__000_000__001_100__001_101__001_110_u64),
            ("____5678",    0b__000_000__000_001__000_001__000_001__000_001__000_111__001_000__001_001__001_010_u64),
            ("____56789",   0b__000_001__000_001__000_001__000_001__000_111__001_000__001_001__001_010__001_011_u64),
        ];
        for (s, body) in vectors.iter() {
            let sym = SymbolSmall::try_from_str(s).unwrap();
            assert_eq!(unsafe { sym.get_body() }, *body);
        }
    }
    #[test]
    fn test_ord() {
        let vals = ["Hello", "hello", "hellos", "", "_________", "________"];
        for a in vals.iter() {
            let a_sym = SymbolSmall::try_from_str(a).unwrap();
            for b in vals.iter() {
                let b_sym = SymbolSmall::try_from_str(b).unwrap();
                assert_eq!(a.cmp(b), a_sym.cmp(&b_sym));
            }
        }
    }
}
#[cfg(all(test, feature = "std"))]
mod test_with_string {
    use super::SymbolSmall;
    use std::string::{String, ToString};
    #[test]
    fn test_roundtrip() {
        let input = "stellar";
        let sym = SymbolSmall::try_from_str(input).unwrap();
        let s: String = sym.to_string();
        assert_eq!(input, &s);
    }
    #[test]
    fn test_roundtrip_zero() {
        let input = "";
        let sym = SymbolSmall::try_from_str(input).unwrap();
        let s: String = sym.to_string();
        assert_eq!(input, &s);
    }
    #[test]
    fn test_roundtrip_nine() {
        let input = "123456789";
        let sym = SymbolSmall::try_from_str(input).unwrap();
        let s: String = sym.to_string();
        assert_eq!(input, &s);
    }
}