use entroll_core::AsciiFaces;
use entroll_core::{Compact, Faces};

pub mod ascii;
pub mod wordlist;

pub trait Dirctionary {
    fn name(&self) -> &str;
}

pub trait Dictionary {
    fn name(&self) -> &str;
    fn canonical(&self) -> &str {
        self.name()
    }
}

pub trait AsciiDictionary: Compact {
    fn name(&self) -> &str;
    fn canonical(&self) -> &str {
        self.name()
    }
}

pub struct AsciiDictionaryWrapper<T>
where
    T: AsciiDictionary,
{
    inner: T,
    faces: AsciiFaces,
}

impl<T> AsciiDictionaryWrapper<T>
where
    T: AsciiDictionary,
{
    pub fn new(inner: T) -> Self {
        let faces = AsciiFaces::try_from(inner.ranges()).unwrap();
        Self { inner, faces }
    }
}

impl<T> Default for AsciiDictionaryWrapper<T>
where
    T: AsciiDictionary + Default,
{
    fn default() -> Self {
        let inner = T::default();
        let faces = AsciiFaces::try_from(inner.ranges()).unwrap();
        Self { inner, faces }
    }
}

impl<T> Dictionary for AsciiDictionaryWrapper<T>
where
    T: AsciiDictionary,
{
    fn name(&self) -> &str {
        self.inner.name()
    }

    fn canonical(&self) -> &str {
        self.inner.canonical()
    }
}

impl<T> Faces for AsciiDictionaryWrapper<T>
where
    T: AsciiDictionary,
{
    fn len(&self) -> usize {
        self.faces.len()
    }
}

impl<T> std::ops::Index<usize> for AsciiDictionaryWrapper<T>
where
    T: AsciiDictionary,
{
    type Output = u8;

    fn index(&self, idx: usize) -> &u8 {
        &self.faces[idx]
    }
}

impl<T> rand::distributions::Distribution<u8> for AsciiDictionaryWrapper<T>
where
    T: AsciiDictionary,
{
    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> u8 {
        let idx = rng.gen_range(0..self.len());
        self[idx]
    }
}

pub struct WordList<const N: usize> {
    words: [&'static str; N],
    name: &'static str,
}

impl<const N: usize> Faces for WordList<N> {
    fn len(&self) -> usize {
        self.words.len()
    }
}

impl<const N: usize> std::ops::Index<usize> for WordList<N> {
    type Output = str;

    fn index(&self, idx: usize) -> &str {
        self.words[idx]
    }
}

impl<const N: usize> rand::distributions::Distribution<String> for WordList<N> {
    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> String {
        let idx = rng.gen_range(0..self.len());
        self[idx].to_string()
    }
}

impl<const N: usize> Dictionary for WordList<N> {
    fn name(&self) -> &str {
        self.name
    }
}

pub type ArabicNumerals = AsciiDictionaryWrapper<ascii::ArabicNumerals>;
pub type Hexadecimal = AsciiDictionaryWrapper<ascii::Hexadecimal>;
pub type Alphanumeric = AsciiDictionaryWrapper<ascii::Alphanumeric>;
pub type Expect = AsciiDictionaryWrapper<ascii::Expect>;

pub use wordlist::EFFDiceware5;
