use once_cell::sync::Lazy;
use std::{
    collections::{HashMap, HashSet},
    error::Error,
    fmt::{self, Display},
};
use crate::{NewName, NewNameRule, ReturnsBool};
pub static RESERVED: Lazy<HashSet<&'static str>> = Lazy::new(|| {
    let mut reserved = HashSet::new();
    reserved.insert("");
    reserved.insert("as");
    reserved.insert("const"); 
    reserved.insert("else");
    reserved.insert("false");
    reserved.insert("for");
    reserved.insert("if");
    reserved.insert("in");
    reserved.insert("mut"); 
    reserved.insert("optional"); 
    reserved.insert("or_init"); 
    reserved.insert("owned"); 
    reserved.insert("ref"); 
    reserved.insert("some"); 
    reserved.insert("true");
    reserved.insert("unchecked");
    reserved.insert("unchecked_mut");
    reserved.insert("where");
    reserved.insert("while");
    reserved
});
pub static EXACT_SUFFIX_SUBSTITUTES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
    let mut exact_subs = HashMap::new();
    exact_subs.insert("async", "async_");
    exact_subs.insert("await", "await_");
    exact_subs.insert("break", "break_");
    exact_subs.insert("crate", "crate_");
    exact_subs.insert("continue", "continue_");
    exact_subs.insert("dyn", "dyn_");
    exact_subs.insert("enum", "enum_");
    exact_subs.insert("extern", "extern_");
    exact_subs.insert("fn", "fn_");
    exact_subs.insert("impl", "impl_");
    exact_subs.insert("loop", "loop_");
    exact_subs.insert("match", "match_");
    exact_subs.insert("mod", "mod_");
    exact_subs.insert("move", "move_");
    exact_subs.insert("pub", "pub_");
    exact_subs.insert("return", "return_");
    exact_subs.insert("self", "self_");
    exact_subs.insert("static", "static_");
    exact_subs.insert("struct", "struct_");
    exact_subs.insert("super", "super_");
    exact_subs.insert("trait", "trait_");
    exact_subs.insert("type", "type_");
    exact_subs.insert("union", "union_");
    exact_subs.insert("unsafe", "unsafe_");
    exact_subs.insert("use", "use_");
    exact_subs
});
pub static BOOL_FIRST_TOKEN_SUBSTITUTES: Lazy<HashMap<&'static str, &'static str>> =
    Lazy::new(|| {
        let mut first_token_subs = HashMap::new();
        first_token_subs.insert("activate", "activates");
        first_token_subs.insert("accept", "accepts");
        first_token_subs.insert("allow", "allows");
        
        first_token_subs.insert("always", "must_always");
        first_token_subs.insert("close", "closes");
        first_token_subs.insert("create", "creates");
        
        first_token_subs.insert("destroy", "must_destroy");
        first_token_subs.insert("do", "does");
        first_token_subs.insert("draw", "draws");
        first_token_subs.insert("embed", "embeds");
        first_token_subs.insert("emit", "emits");
        first_token_subs.insert("enable", "enables");
        first_token_subs.insert("exit", "exits");
        first_token_subs.insert("expand", "expands");
        first_token_subs.insert("fill", "fills");
        first_token_subs.insert("fit", "fits");
        
        first_token_subs.insert("focus", "gets_focus");
        first_token_subs.insert("follow", "follows");
        first_token_subs.insert("hide", "hides");
        first_token_subs.insert("ignore", "ignores");
        first_token_subs.insert("invert", "inverts");
        first_token_subs.insert("mute", "is_muted");
        first_token_subs.insert("need", "needs");
        first_token_subs.insert("propagate", "propagates");
        first_token_subs.insert("populate", "populates");
        first_token_subs.insert("receive", "receives");
        first_token_subs.insert("reset", "resets");
        first_token_subs.insert("require", "requires");
        
        first_token_subs.insert("reserve", "must_reserve");
        first_token_subs.insert("resize", "resizes");
        first_token_subs.insert("restrict", "restricts");
        first_token_subs.insert("reveal", "reveals");
        first_token_subs.insert("select", "selects");
        first_token_subs.insert("show", "shows");
        first_token_subs.insert("shrink", "shrinks");
        first_token_subs.insert("skip", "skips");
        first_token_subs.insert("snap", "snaps");
        first_token_subs.insert("support", "supports");
        first_token_subs.insert("take", "takes");
        first_token_subs.insert("track", "tracks");
        
        first_token_subs.insert("truncate", "must_truncate");
        first_token_subs.insert("use", "uses");
        first_token_subs.insert("wrap", "wraps");
        first_token_subs
    });
pub static BOOL_FIRST_TOKEN_NO_PREFIX: Lazy<HashSet<&'static str>> = Lazy::new(|| {
    let mut first_tokens = HashSet::new();
    first_tokens.insert("can");
    first_tokens.insert("has");
    first_tokens.insert("must");
    first_tokens.insert("should");
    first_tokens.insert("state");
    
    for bool_substitute in BOOL_FIRST_TOKEN_SUBSTITUTES.values() {
        first_tokens.insert(bool_substitute);
    }
    first_tokens
});
pub static BOOL_EXACT_SUBSTITUTES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
    let mut exact_subs = HashMap::new();
    exact_subs.insert("result", "result");
    exact_subs.insert("overwrite", "overwrites");
    exact_subs.insert("visibility", "is_visible");
    exact_subs
});
pub const BOOL_ABLE_PREFIX: &str = "able";
pub static PREFIX_TO_POSTFIX: Lazy<HashSet<&'static str>> = Lazy::new(|| {
    let mut prefix_to_postfix = HashSet::new();
    prefix_to_postfix.insert("mut");
    prefix_to_postfix
});
pub fn try_rename_would_be_getter(
    name: &str,
    returns_bool: impl Into<ReturnsBool>,
) -> Result<NewName, RenameError> {
    let suffix = match name.strip_prefix("get_") {
        Some(suffix) => suffix,
        None => return Err(RenameError::NotGetFn),
    };
    try_rename_getter_suffix(suffix, returns_bool)
}
pub fn try_rename_getter_suffix(
    suffix: &str,
    returns_bool: impl Into<ReturnsBool>,
) -> Result<NewName, RenameError> {
    use ReturnsBool::*;
    let returns_bool = match returns_bool.into() {
        False => ReturnsBool::False,
        True => return Ok(rename_bool_getter(suffix)),
        Maybe => {
            if let Some(rename) = guesstimate_boolness_then_rename(suffix) {
                return Ok(rename);
            }
            ReturnsBool::Maybe
        }
    };
    if let Some(substitute) = EXACT_SUFFIX_SUBSTITUTES.get(suffix) {
        return Ok(NewName {
            new_name: substitute.to_string(),
            returns_bool,
            rule: NewNameRule::Substituted,
        });
    }
    let splits: Vec<&str> = suffix.splitn(2, '_').collect();
    if splits.len() > 1 && PREFIX_TO_POSTFIX.contains(splits[0]) {
        Ok(NewName {
            new_name: format!("{}_{}", splits[1], splits[0]),
            returns_bool,
            rule: NewNameRule::Fixed,
        })
    } else if RESERVED.contains(suffix) {
        Err(RenameError::Reserved)
    } else {
        Ok(NewName {
            new_name: suffix.to_string(),
            returns_bool,
            rule: NewNameRule::Regular,
        })
    }
}
#[inline]
pub fn rename_bool_getter(suffix: &str) -> NewName {
    if let Some(substitute) = BOOL_EXACT_SUBSTITUTES.get(suffix) {
        return NewName {
            new_name: substitute.to_string(),
            returns_bool: true.into(),
            rule: NewNameRule::Substituted,
        };
    }
    if let Some(new_name) = try_rename_bool_getter(suffix) {
        new_name
    } else {
        NewName {
            new_name: format!("is_{}", suffix),
            returns_bool: true.into(),
            rule: NewNameRule::Regular,
        }
    }
}
#[inline]
fn try_rename_bool_getter(suffix: &str) -> Option<NewName> {
    let mut working_suffix = suffix;
    let mut has_is_prefix = false;
    if let Some(suffix_without_is) = suffix.strip_prefix("is_") {
        working_suffix = suffix_without_is;
        has_is_prefix = true;
    }
    let splits: Vec<&str> = working_suffix.splitn(2, '_').collect();
    BOOL_FIRST_TOKEN_SUBSTITUTES
        .get(splits[0])
        .map(|substitute| {
            if splits.len() == 1 {
                NewName {
                    new_name: substitute.to_string(),
                    returns_bool: true.into(),
                    rule: NewNameRule::Substituted,
                }
            } else {
                NewName {
                    new_name: format!("{}_{}", substitute, splits[1]),
                    returns_bool: true.into(),
                    rule: NewNameRule::Substituted,
                }
            }
        })
        .or_else(|| {
            BOOL_FIRST_TOKEN_NO_PREFIX.get(splits[0]).map(|_| {
                if splits.len() == 1 {
                    NewName {
                        new_name: splits[0].to_string(),
                        returns_bool: true.into(),
                        rule: NewNameRule::NoPrefix,
                    }
                } else {
                    NewName {
                        new_name: format!("{}_{}", splits[0], splits[1]),
                        returns_bool: true.into(),
                        rule: NewNameRule::NoPrefix,
                    }
                }
            })
        })
        .or_else(|| {
            
            if has_is_prefix {
                
                Some(NewName {
                    new_name: suffix.to_string(),
                    returns_bool: true.into(),
                    rule: NewNameRule::Regular,
                })
            } else {
                None
            }
        })
}
#[inline]
pub fn guesstimate_boolness_then_rename(suffix: &str) -> Option<NewName> {
    if let Some(new_name) = try_rename_bool_getter(suffix) {
        return Some(new_name);
    }
    let splits: Vec<&str> = suffix.splitn(2, '_').collect();
    if splits[0].ends_with(BOOL_ABLE_PREFIX) {
        Some(NewName {
            new_name: format!("is_{}", suffix),
            returns_bool: true.into(),
            rule: NewNameRule::Regular,
        })
    } else {
        None
    }
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[non_exhaustive]
pub enum RenameError {
    
    NotGetFn,
    
    Reserved,
}
impl RenameError {
    pub fn is_not_get_fn(&self) -> bool {
        matches!(self, RenameError::NotGetFn)
    }
    pub fn is_reserved(&self) -> bool {
        matches!(self, RenameError::Reserved)
    }
}
impl Display for RenameError {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        use RenameError::*;
        match self {
            NotGetFn => write!(f, "not a get function"),
            Reserved => write!(f, "name is reserved"),
        }
    }
}
impl Error for RenameError {}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn bool_getter_rename_attempt() {
        let new_name = try_rename_bool_getter(&"mute").unwrap();
        assert!(new_name.is_substituted());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "is_muted");
        let new_name = try_rename_bool_getter(&"emit_eos").unwrap();
        assert!(new_name.is_substituted());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "emits_eos");
        let new_name = try_rename_bool_getter(&"has_entry").unwrap();
        assert!(new_name.is_no_prefix());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "has_entry");
        let new_name = try_rename_bool_getter(&"is_emit_eos").unwrap();
        assert!(new_name.is_substituted());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "emits_eos");
        let new_name = try_rename_bool_getter(&"is_activated").unwrap();
        assert!(new_name.is_regular());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "is_activated");
        assert!(try_rename_bool_getter(&"name").is_none());
    }
    #[test]
    fn bool_getter_suffix() {
        let new_name = rename_bool_getter(&"result");
        assert!(new_name.is_substituted());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "result");
        let new_name = rename_bool_getter(&"activable");
        assert!(new_name.is_regular());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "is_activable");
        let new_name = rename_bool_getter(&"mute");
        assert!(new_name.is_substituted());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "is_muted");
        let new_name = rename_bool_getter(&"emit_eos");
        assert!(new_name.is_substituted());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "emits_eos");
        let new_name = rename_bool_getter(&"can_acquire");
        assert!(new_name.is_no_prefix());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "can_acquire");
    }
    #[test]
    fn boolness_guestimation() {
        assert!(guesstimate_boolness_then_rename(&"result").is_none());
        assert!(guesstimate_boolness_then_rename(&"name").is_none());
        let new_name = guesstimate_boolness_then_rename(&"mute").unwrap();
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "is_muted");
        let new_name = guesstimate_boolness_then_rename(&"does_ts").unwrap();
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "does_ts");
        let new_name = guesstimate_boolness_then_rename(&"emit_eos").unwrap();
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "emits_eos");
        let new_name = guesstimate_boolness_then_rename(&"emits_eos").unwrap();
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "emits_eos");
        let new_name = guesstimate_boolness_then_rename(&"is_emits_eos").unwrap();
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "emits_eos");
        let new_name = guesstimate_boolness_then_rename(&"is_activated").unwrap();
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "is_activated");
        let new_name = guesstimate_boolness_then_rename(&"activable").unwrap();
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "is_activable");
    }
    #[test]
    fn rename_getter_non_bool() {
        let new_name = try_rename_would_be_getter(&"get_structure", false).unwrap();
        assert!(new_name.is_regular());
        assert!(new_name.returns_bool().is_false());
        assert_eq!(new_name, "structure");
        let new_name = try_rename_would_be_getter(&"get_type", false).unwrap();
        assert!(new_name.is_substituted());
        assert!(new_name.returns_bool().is_false());
        assert_eq!(new_name, "type_");
        
        let new_name = try_rename_would_be_getter(&"get_activable", false).unwrap();
        assert!(new_name.is_regular());
        assert!(new_name.returns_bool().is_false());
        assert_eq!(new_name, "activable");
        
        let new_name = try_rename_would_be_getter(&"get_mut_structure", false).unwrap();
        assert!(new_name.is_fixed());
        assert!(new_name.returns_bool().is_false());
        assert_eq!(new_name, "structure_mut");
        assert!(try_rename_would_be_getter(&"get_mut", false)
            .unwrap_err()
            .is_reserved());
        assert!(try_rename_would_be_getter(&"not_a_getter", false)
            .unwrap_err()
            .is_not_get_fn());
    }
    #[test]
    fn rename_getter_bool() {
        let new_name = try_rename_would_be_getter(&"get_structure", true).unwrap();
        assert!(new_name.is_regular());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "is_structure");
        let new_name = try_rename_would_be_getter(&"get_type", true).unwrap();
        assert!(new_name.is_regular());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "is_type");
        let new_name = try_rename_would_be_getter(&"get_mute", true).unwrap();
        assert!(new_name.is_substituted());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "is_muted");
        let new_name = try_rename_would_be_getter(&"get_emit_eos", true).unwrap();
        assert!(new_name.is_substituted());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "emits_eos");
        let new_name = try_rename_would_be_getter(&"get_emits_eos", true).unwrap();
        assert!(new_name.is_no_prefix());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "emits_eos");
        let new_name = try_rename_would_be_getter(&"get_is_emit_eos", true).unwrap();
        assert!(new_name.is_substituted());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "emits_eos");
        let new_name = try_rename_would_be_getter(&"get_is_activated", true).unwrap();
        assert!(new_name.is_regular());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "is_activated");
        let new_name = try_rename_would_be_getter(&"get_activable", true).unwrap();
        assert!(new_name.is_regular());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "is_activable");
        let new_name = try_rename_would_be_getter(&"get_mut", true).unwrap();
        assert!(new_name.is_regular());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "is_mut");
        let new_name = try_rename_would_be_getter(&"get_overwrite", true).unwrap();
        assert!(new_name.is_substituted());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "overwrites");
        let new_name = try_rename_would_be_getter(&"get_overwrite_mode", true).unwrap();
        assert!(new_name.is_regular());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "is_overwrite_mode");
        assert!(try_rename_would_be_getter(&"not_a_getter", true)
            .unwrap_err()
            .is_not_get_fn());
    }
    #[test]
    fn rename_getter_maybe_bool() {
        let new_name = try_rename_would_be_getter(&"get_structure", ReturnsBool::Maybe).unwrap();
        assert!(new_name.is_regular());
        assert!(new_name.returns_bool().is_maybe());
        assert_eq!(new_name, "structure");
        let new_name = try_rename_would_be_getter(&"get_type", ReturnsBool::Maybe).unwrap();
        assert!(new_name.is_substituted());
        assert!(new_name.returns_bool().is_maybe());
        assert_eq!(new_name, "type_");
        let new_name = try_rename_would_be_getter(&"get_mute", ReturnsBool::Maybe).unwrap();
        assert!(new_name.is_substituted());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "is_muted");
        let new_name = try_rename_would_be_getter(&"get_emit_eos", ReturnsBool::Maybe).unwrap();
        assert!(new_name.is_substituted());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "emits_eos");
        let new_name = try_rename_would_be_getter(&"get_emits_eos", ReturnsBool::Maybe).unwrap();
        assert!(new_name.is_no_prefix());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "emits_eos");
        let new_name = try_rename_would_be_getter(&"get_is_emit_eos", ReturnsBool::Maybe).unwrap();
        assert!(new_name.is_substituted());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "emits_eos");
        let new_name = try_rename_would_be_getter(&"get_is_activated", ReturnsBool::Maybe).unwrap();
        assert!(new_name.is_regular());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "is_activated");
        let new_name = try_rename_would_be_getter(&"get_activable", ReturnsBool::Maybe).unwrap();
        assert!(new_name.is_regular());
        assert!(new_name.returns_bool().is_true());
        assert_eq!(new_name, "is_activable");
        assert!(try_rename_would_be_getter(&"get_mut", ReturnsBool::Maybe)
            .unwrap_err()
            .is_reserved());
        assert!(
            try_rename_would_be_getter(&"not_a_getter", ReturnsBool::Maybe)
                .unwrap_err()
                .is_not_get_fn()
        );
    }
}