use crate::internal::as2_pcode::PushValue;
use std::collections::HashMap;

#[derive(Debug, Default)]
pub struct Constants {
    strings: Vec<String>,
    lookup: HashMap<String, u16>,
}

impl Constants {
    pub fn empty() -> Self {
        Self::default()
    }

    pub fn add(&mut self, string: &str) -> PushValue {
        if let Some(index) = self.lookup.get(string) {
            return PushValue::Constant(*index);
        }
        if self.strings.len() < u16::MAX as usize {
            let index = self.strings.len() as u16;
            self.strings.push(string.to_owned());
            self.lookup.insert(string.to_owned(), index);
            return PushValue::Constant(index);
        }
        PushValue::String(string.to_owned())
    }
}

impl IntoIterator for Constants {
    type Item = String;
    type IntoIter = std::vec::IntoIter<String>;

    fn into_iter(self) -> Self::IntoIter {
        self.strings.into_iter()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add_single() {
        let mut constants = Constants::empty();
        assert_eq!(constants.add("foo"), PushValue::Constant(0));
    }

    #[test]
    fn test_duplicate_is_not_added() {
        let mut constants = Constants::empty();
        assert_eq!(constants.add("foo"), PushValue::Constant(0));
        assert_eq!(constants.add("foo"), PushValue::Constant(0));
    }

    #[test]
    fn test_none_after_full() {
        let mut constants = Constants::empty();
        for i in 0..u16::MAX {
            assert_eq!(constants.add(&format!("foo{}", i)), PushValue::Constant(i));
        }
        assert_eq!(constants.add("foo"), PushValue::String("foo".to_owned()));
    }
}
