use std::collections::HashSet;
use std::fmt;
use std::iter::IntoIterator;
use std::error;
use log::LevelFilter;
use append::Append;
use filter::Filter;
use {ConfigPrivateExt, PrivateConfigAppenderExt};
#[derive(Debug)]
pub struct Root {
    level: LevelFilter,
    appenders: Vec<String>,
}
impl Root {
    
    pub fn builder() -> RootBuilder {
        RootBuilder { appenders: vec![] }
    }
    
    pub fn level(&self) -> LevelFilter {
        self.level
    }
    
    pub fn appenders(&self) -> &[String] {
        &self.appenders
    }
}
#[derive(Debug)]
pub struct RootBuilder {
    appenders: Vec<String>,
}
impl RootBuilder {
    
    pub fn appender<T>(mut self, appender: T) -> RootBuilder
    where
        T: Into<String>,
    {
        self.appenders.push(appender.into());
        self
    }
    
    pub fn appenders<I>(mut self, appenders: I) -> RootBuilder
    where
        I: IntoIterator,
        I::Item: Into<String>,
    {
        self.appenders.extend(appenders.into_iter().map(Into::into));
        self
    }
    
    pub fn build(self, level: LevelFilter) -> Root {
        Root {
            level: level,
            appenders: self.appenders,
        }
    }
}
#[derive(Debug)]
pub struct Appender {
    name: String,
    appender: Box<Append>,
    filters: Vec<Box<Filter>>,
}
impl Appender {
    
    pub fn builder() -> AppenderBuilder {
        AppenderBuilder { filters: vec![] }
    }
    
    pub fn name(&self) -> &str {
        &self.name
    }
    
    pub fn appender(&self) -> &Append {
        &*self.appender
    }
    
    pub fn filters(&self) -> &[Box<Filter>] {
        &self.filters
    }
}
impl PrivateConfigAppenderExt for Appender {
    fn unpack(self) -> (String, Box<Append>, Vec<Box<Filter>>) {
        let Appender {
            name,
            appender,
            filters,
        } = self;
        (name, appender, filters)
    }
}
#[derive(Debug)]
pub struct AppenderBuilder {
    filters: Vec<Box<Filter>>,
}
impl AppenderBuilder {
    
    pub fn filter(mut self, filter: Box<Filter>) -> AppenderBuilder {
        self.filters.push(filter);
        self
    }
    
    pub fn filters<I>(mut self, filters: I) -> AppenderBuilder
    where
        I: IntoIterator<Item = Box<Filter>>,
    {
        self.filters.extend(filters);
        self
    }
    
    pub fn build<T>(self, name: T, appender: Box<Append>) -> Appender
    where
        T: Into<String>,
    {
        Appender {
            name: name.into(),
            appender: appender,
            filters: self.filters,
        }
    }
}
#[derive(Debug)]
pub struct Logger {
    name: String,
    level: LevelFilter,
    appenders: Vec<String>,
    additive: bool,
}
impl Logger {
    
    
    
    pub fn builder() -> LoggerBuilder {
        LoggerBuilder {
            appenders: vec![],
            additive: true,
        }
    }
    
    pub fn name(&self) -> &str {
        &self.name
    }
    
    pub fn level(&self) -> LevelFilter {
        self.level
    }
    
    pub fn appenders(&self) -> &[String] {
        &self.appenders
    }
    
    pub fn additive(&self) -> bool {
        self.additive
    }
}
#[derive(Debug)]
pub struct LoggerBuilder {
    appenders: Vec<String>,
    additive: bool,
}
impl LoggerBuilder {
    
    pub fn appender<T>(mut self, appender: T) -> LoggerBuilder
    where
        T: Into<String>,
    {
        self.appenders.push(appender.into());
        self
    }
    
    pub fn appenders<I>(mut self, appenders: I) -> LoggerBuilder
    where
        I: IntoIterator,
        I::Item: Into<String>,
    {
        self.appenders.extend(appenders.into_iter().map(Into::into));
        self
    }
    
    pub fn additive(mut self, additive: bool) -> LoggerBuilder {
        self.additive = additive;
        self
    }
    
    pub fn build<T>(self, name: T, level: LevelFilter) -> Logger
    where
        T: Into<String>,
    {
        Logger {
            name: name.into(),
            level: level,
            appenders: self.appenders,
            additive: self.additive,
        }
    }
}
#[derive(Debug)]
pub struct Config {
    appenders: Vec<Appender>,
    root: Root,
    loggers: Vec<Logger>,
}
impl Config {
    
    pub fn builder() -> ConfigBuilder {
        ConfigBuilder {
            appenders: vec![],
            loggers: vec![],
        }
    }
    
    pub fn appenders(&self) -> &[Appender] {
        &self.appenders
    }
    
    pub fn root(&self) -> &Root {
        &self.root
    }
    
    pub fn loggers(&self) -> &[Logger] {
        &self.loggers
    }
}
pub struct ConfigBuilder {
    appenders: Vec<Appender>,
    loggers: Vec<Logger>,
}
impl ConfigBuilder {
    
    pub fn appender(mut self, appender: Appender) -> ConfigBuilder {
        self.appenders.push(appender);
        self
    }
    
    pub fn appenders<I>(mut self, appenders: I) -> ConfigBuilder
    where
        I: IntoIterator<Item = Appender>,
    {
        self.appenders.extend(appenders);
        self
    }
    
    pub fn logger(mut self, logger: Logger) -> ConfigBuilder {
        self.loggers.push(logger);
        self
    }
    
    pub fn loggers<I>(mut self, loggers: I) -> ConfigBuilder
    where
        I: IntoIterator<Item = Logger>,
    {
        self.loggers.extend(loggers);
        self
    }
    
    
    
    
    pub fn build_lossy(self, mut root: Root) -> (Config, Vec<Error>) {
        let mut errors = vec![];
        let ConfigBuilder { appenders, loggers } = self;
        let mut ok_appenders = vec![];
        let mut appender_names = HashSet::new();
        for appender in appenders {
            if appender_names.insert(appender.name.clone()) {
                ok_appenders.push(appender);
            } else {
                errors.push(Error::DuplicateAppenderName(appender.name));
            }
        }
        let mut ok_root_appenders = vec![];
        for appender in root.appenders {
            if appender_names.contains(&appender) {
                ok_root_appenders.push(appender);
            } else {
                errors.push(Error::NonexistentAppender(appender));
            }
        }
        root.appenders = ok_root_appenders;
        let mut ok_loggers = vec![];
        let mut logger_names = HashSet::new();
        for mut logger in loggers {
            if !logger_names.insert(logger.name.clone()) {
                errors.push(Error::DuplicateLoggerName(logger.name));
                continue;
            }
            if let Err(err) = check_logger_name(&logger.name) {
                errors.push(err);
                continue;
            }
            let mut ok_logger_appenders = vec![];
            for appender in logger.appenders {
                if appender_names.contains(&appender) {
                    ok_logger_appenders.push(appender);
                } else {
                    errors.push(Error::NonexistentAppender(appender));
                }
            }
            logger.appenders = ok_logger_appenders;
            ok_loggers.push(logger);
        }
        let config = Config {
            appenders: ok_appenders,
            root: root,
            loggers: ok_loggers,
        };
        (config, errors)
    }
    
    pub fn build(self, root: Root) -> Result<Config, Errors> {
        let (config, errors) = self.build_lossy(root);
        if errors.is_empty() {
            Ok(config)
        } else {
            Err(Errors { errors: errors })
        }
    }
}
fn check_logger_name(name: &str) -> Result<(), Error> {
    if name.is_empty() {
        return Err(Error::InvalidLoggerName(name.to_owned()));
    }
    let mut streak = 0;
    for ch in name.chars() {
        if ch == ':' {
            streak += 1;
            if streak > 2 {
                return Err(Error::InvalidLoggerName(name.to_owned()));
            }
        } else {
            if streak > 0 && streak != 2 {
                return Err(Error::InvalidLoggerName(name.to_owned()));
            }
            streak = 0;
        }
    }
    if streak > 0 {
        Err(Error::InvalidLoggerName(name.to_owned()))
    } else {
        Ok(())
    }
}
impl ConfigPrivateExt for Config {
    fn unpack(self) -> (Vec<Appender>, Root, Vec<Logger>) {
        let Config {
            appenders,
            root,
            loggers,
        } = self;
        (appenders, root, loggers)
    }
}
#[derive(Debug)]
pub struct Errors {
    errors: Vec<Error>,
}
impl Errors {
    
    pub fn errors(&self) -> &[Error] {
        &self.errors
    }
}
impl fmt::Display for Errors {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        for error in &self.errors {
            writeln!(fmt, "{}", error)?;
        }
        Ok(())
    }
}
impl error::Error for Errors {
    fn description(&self) -> &str {
        "Errors encountered when validating a log4rs `Config`"
    }
}
#[derive(Debug)]
pub enum Error {
    
    DuplicateAppenderName(String),
    
    NonexistentAppender(String),
    
    DuplicateLoggerName(String),
    
    InvalidLoggerName(String),
    #[doc(hidden)] __Extensible,
}
impl fmt::Display for Error {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::DuplicateAppenderName(ref n) => write!(fmt, "Duplicate appender name `{}`", n),
            Error::NonexistentAppender(ref n) => {
                write!(fmt, "Reference to nonexistent appender: `{}`", n)
            }
            Error::DuplicateLoggerName(ref n) => write!(fmt, "Duplicate logger name `{}`", n),
            Error::InvalidLoggerName(ref n) => write!(fmt, "Invalid logger name `{}`", n),
            Error::__Extensible => unreachable!(),
        }
    }
}
impl error::Error for Error {
    fn description(&self) -> &str {
        "An error constructing a log4rs `Config`"
    }
}
#[cfg(test)]
mod test {
    #[test]
    fn check_logger_name() {
        let tests = [
            ("", false),
            ("asdf", true),
            ("asdf::jkl", true),
            ("::", false),
            ("asdf::jkl::", false),
            ("asdf:jkl", false),
            ("asdf:::jkl", false),
            ("asdf::jkl::", false),
        ];
        for &(ref name, expected) in &tests {
            assert!(
                expected == super::check_logger_name(name).is_ok(),
                "{}",
                name
            );
        }
    }
}