//! This module implements the chore schema language for this crate. See [`Expr::parse`] for the
//! schema language description, [`Expr::write_repr`] for the canonical serialization format, and
//! the [`Eval`] and [`EvalContext`] instances for the generation scheme.

mod chars;
mod context;
mod generator;
mod node;
mod parse;
mod repr;
mod util;

use std::{
    io::{Result, Write},
    sync::LazyLock,
};

use crypto_bigint::{NonZero, U256};
use secrecy::ExposeSecretMut;

pub use chars::{CharRange, Chars};
pub use context::Context;
pub use generator::{Generator, GeneratorFunc, Word, Words};
pub use node::Node;
pub use parse::Error as ParseError;

/// A fully parsed and bound schema expression.
///
/// It may be evaluated with [`Eval`] to generate passwords from its domain.
#[derive(Debug)]
pub struct Expr<'a> {
    pub root: Node,
    pub context: Option<&'a Context<'a>>,
}

/// The core expression sampling trait for this module.
///
/// A type which implements this trait knows how many input strings it contains, and knows how to
/// write a given one of them to a [`Write`] instance, without any other context.
pub trait Eval {
    /// Returns the total number of passwords generated by this type, or some reasonable
    /// approximation thereof (e.g. a type that constructs and samples its own RNG might return the
    /// size of the RNG state here), but caution should be used when deviating too far from the
    /// literal count/enumeration paradigm, as this may cause entropy estimates to be misleading.
    fn size(&self) -> NonZero<U256>;

    /// Write the string at the given index to the given [`Write`] instance. Typically the string
    /// at index 0 will be the lowest or lexicographically first password, and the string at
    /// `self.size() - 1` will be the highest or lexicographically last password, but this is not
    /// required.
    fn write_to(&self, w: &mut dyn Write, index: &mut dyn ExposeSecretMut<U256>) -> Result<()>;
}

/// Delineates a type that knows how to [`Eval`] itself but needs some extra
/// [`Context`][EvalContext::Context] to do so.
///
/// E.g., this may be a call to a custom generator function like `{word}` or `{bip39}`, which needs
/// the definition of that function to proceed.
///
/// Conceptually, you can think of an implementation of this trait like a
/// `impl<T: EvalContext> Eval for (T, T::Context)`.
pub trait EvalContext {
    /// This is the extra context that is needed to evaluate this type.
    type Context<'a>: ?Sized + 'a;

    /// Like [`Eval::size`] but with context.
    fn size(&self, context: &Self::Context<'_>) -> NonZero<U256>;

    /// Like [`Eval::write_to`] but with context.
    fn write_to(
        &self,
        context: &Self::Context<'_>,
        w: &mut dyn Write,
        index: &mut dyn ExposeSecretMut<U256>,
    ) -> Result<()>;
}

static DEFAULT_CONTEXT: LazyLock<Context<'static>> = LazyLock::new(Context::default);

impl Expr<'_> {
    /// Construct a new expression with the default generator context.
    pub fn new(root: Node) -> Self {
        Expr {
            root,
            context: None,
        }
    }
}

impl<'a> Expr<'a> {
    pub fn with_context(root: Node, context: &'a Context<'a>) -> Self {
        Expr {
            root,
            context: Some(context),
        }
    }

    pub fn get_context(&self) -> &Context<'a> {
        self.context.unwrap_or(&DEFAULT_CONTEXT)
    }
}

impl Eval for Expr<'_> {
    fn size(&self) -> NonZero<U256> {
        self.root.size(self.get_context())
    }

    fn write_to(&self, w: &mut dyn Write, index: &mut dyn ExposeSecretMut<U256>) -> Result<()> {
        self.root.write_to(self.get_context(), w, index)
    }
}
