#![forbid(unsafe_code)]
#![cfg_attr(test, allow(clippy::assertions_on_result_states))]
mod equal;
mod helpers;
#[cfg(test)]
use console::TestRng;
#[cfg(test)]
use snarkvm_circuit_environment::assert_scope;
use snarkvm_circuit_environment::prelude::*;
use snarkvm_circuit_types_boolean::Boolean;
use snarkvm_circuit_types_field::Field;
use snarkvm_circuit_types_integers::U8;
#[derive(Clone)]
pub struct StringType<E: Environment> {
    mode: Mode,
    bytes: Vec<U8<E>>,
    size_in_bytes: Field<E>,
}
impl<E: Environment> StringTrait for StringType<E> {}
#[cfg(console)]
impl<E: Environment> Inject for StringType<E> {
    type Primitive = console::StringType<E::Network>;
    fn new(mode: Mode, string: Self::Primitive) -> Self {
        let num_bytes =
            console::Field::from_u32(u32::try_from(string.len()).unwrap_or_else(|error| E::halt(error.to_string())));
        let expected_size_in_bytes = Field::constant(num_bytes);
        let size_in_bytes = match mode.is_constant() {
            true => expected_size_in_bytes.clone(),
            false => Field::new(Mode::Private, num_bytes),
        };
        E::assert_eq(&expected_size_in_bytes, &size_in_bytes);
        Self {
            mode,
            bytes: string.as_bytes().iter().map(|byte| U8::new(mode, console::Integer::new(*byte))).collect(),
            size_in_bytes,
        }
    }
}
#[cfg(console)]
impl<E: Environment> Eject for StringType<E> {
    type Primitive = console::StringType<E::Network>;
    fn eject_mode(&self) -> Mode {
        match self.bytes.is_empty() {
            true => self.mode,
            false => self.bytes.eject_mode(),
        }
    }
    fn eject_value(&self) -> Self::Primitive {
        let num_bytes = self.bytes.len();
        match num_bytes <= E::MAX_STRING_BYTES as usize {
            true => console::StringType::new(
                &String::from_utf8(self.bytes.eject_value().into_iter().map(|byte| *byte).collect())
                    .unwrap_or_else(|error| E::halt(format!("Failed to eject a string value: {error}"))),
            ),
            false => E::halt(format!("Attempted to eject a string of size {num_bytes}")),
        }
    }
}
#[cfg(console)]
impl<E: Environment> Parser for StringType<E> {
    #[inline]
    fn parse(string: &str) -> ParserResult<Self> {
        let (string, content) = console::StringType::parse(string)?;
        let (string, mode) = opt(pair(tag("."), Mode::parse))(string)?;
        match mode {
            Some((_, mode)) => Ok((string, StringType::new(mode, content))),
            None => Ok((string, StringType::new(Mode::Constant, content))),
        }
    }
}
#[cfg(console)]
impl<E: Environment> FromStr for StringType<E> {
    type Err = Error;
    #[inline]
    fn from_str(string: &str) -> Result<Self> {
        match Self::parse(string) {
            Ok((remainder, object)) => {
                ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
                Ok(object)
            }
            Err(error) => bail!("Failed to parse string. {error}"),
        }
    }
}
#[cfg(console)]
impl<E: Environment> TypeName for StringType<E> {
    #[inline]
    fn type_name() -> &'static str {
        console::StringType::<E::Network>::type_name()
    }
}
#[cfg(console)]
impl<E: Environment> Debug for StringType<E> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        Display::fmt(self, f)
    }
}
#[cfg(console)]
impl<E: Environment> Display for StringType<E> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}.{}", self.eject_value(), self.eject_mode())
    }
}