use std::{
    cmp,
    collections::{hash_map::Entry, HashMap},
    iter,
};

use crate::zhuyin::SyllableSlice;

use super::{DictEntries, Dictionary, DictionaryInfo, DictionaryUpdateError, Phrase};

/// A collection of dictionaries that returns the union of the lookup results.
/// # Examples
///
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use std::collections::HashMap;
///
/// use chewing::{dictionary::{LayeredDictionary, Dictionary, Phrase}, syl, zhuyin::Bopomofo};
///
/// let mut sys_dict = Box::new(HashMap::new());
/// let mut user_dict = Box::new(HashMap::new());
/// sys_dict.insert(
///     vec![syl![Bopomofo::C, Bopomofo::E, Bopomofo::TONE4]],
///     vec![("測", 1).into(), ("冊", 1).into(), ("側", 1).into()]
/// );
/// user_dict.insert(
///     vec![syl![Bopomofo::C, Bopomofo::E, Bopomofo::TONE4]],
///     vec![("策", 100).into(), ("冊", 100).into()]
/// );
///
/// let dict = LayeredDictionary::new(vec![sys_dict], user_dict);
/// assert_eq!(
///     [
///         ("測", 1).into(),
///         ("冊", 100).into(),
///         ("側", 1).into(),
///         ("策", 100).into(),
///     ]
///     .into_iter()
///     .collect::<Vec<Phrase>>(),
///     dict.lookup_all_phrases(&[
///         syl![Bopomofo::C, Bopomofo::E, Bopomofo::TONE4]
///     ]),
/// );
/// # Ok(())
/// # }
/// ```
#[derive(Debug)]
pub struct LayeredDictionary {
    sys_dict: Vec<Box<dyn Dictionary>>,
    user_dict: Box<dyn Dictionary>,
}

impl LayeredDictionary {
    /// Creates a new `LayeredDictionary` with the list of dictionaries.
    pub fn new(
        sys_dict: Vec<Box<dyn Dictionary>>,
        user_dict: Box<dyn Dictionary>,
    ) -> LayeredDictionary {
        LayeredDictionary {
            sys_dict,
            user_dict,
        }
    }
    pub fn user_dict(&mut self) -> &mut dyn Dictionary {
        self.user_dict.as_mut()
    }
}

impl Dictionary for LayeredDictionary {
    /// Lookup phrases from all underlying dictionaries.
    ///
    /// Phrases are ordered by their first apperance in the underlying dictionaries.
    ///
    /// Pseudo code
    ///
    /// ```pseudo_code
    /// Set phrases = list()
    /// Foreach d in d_layers
    ///   Foreach phrase, freq in d.lookup_syllables()
    ///     If phrase in phrases
    ///       Set phrases[phrase].freq = max(phrases[phrase].freq, freq)
    ///     Else
    ///       Add phrases <- (phrase, freq)
    /// ```
    fn lookup_first_n_phrases(&self, syllables: &dyn SyllableSlice, first: usize) -> Vec<Phrase> {
        let mut sort_map: HashMap<String, usize> = HashMap::new();
        let mut phrases: Vec<Phrase> = Vec::new();

        self.sys_dict
            .iter()
            .chain(iter::once(&self.user_dict))
            .for_each(|d| {
                for phrase in d.lookup_all_phrases(syllables) {
                    match sort_map.entry(phrase.to_string()) {
                        Entry::Occupied(entry) => {
                            let index = *entry.get();
                            phrases[index] = cmp::max(&phrase, &phrases[index]).clone();
                        }
                        Entry::Vacant(entry) => {
                            entry.insert(phrases.len());
                            phrases.push(phrase);
                        }
                    }
                }
            });
        phrases.truncate(first);
        phrases
    }

    fn entries(&self) -> Option<DictEntries> {
        None
    }

    fn about(&self) -> DictionaryInfo {
        DictionaryInfo {
            name: Some("Built-in LayeredDictionary".to_string()),
            ..Default::default()
        }
    }

    fn reopen(&mut self) -> Result<(), DictionaryUpdateError> {
        self.user_dict.reopen()
    }

    fn flush(&mut self) -> Result<(), DictionaryUpdateError> {
        self.user_dict.flush()
    }

    fn add_phrase(
        &mut self,
        syllables: &dyn SyllableSlice,
        phrase: Phrase,
    ) -> Result<(), DictionaryUpdateError> {
        self.user_dict.add_phrase(syllables, phrase)
    }

    fn update_phrase(
        &mut self,
        syllables: &dyn SyllableSlice,
        phrase: Phrase,
        user_freq: u32,
        time: u64,
    ) -> Result<(), DictionaryUpdateError> {
        self.user_dict
            .update_phrase(syllables, phrase, user_freq, time)
    }

    fn remove_phrase(
        &mut self,
        syllables: &dyn SyllableSlice,
        phrase_str: &str,
    ) -> Result<(), DictionaryUpdateError> {
        self.user_dict.remove_phrase(syllables, phrase_str)
    }
}
