use std::ops::Deref;
use std::ops::DerefMut;

use crate::Error;

#[derive(Debug, Default)]
pub struct FailManager {
    fails: Vec<Error>,
}

impl FailManager {
    pub fn push(&mut self, err: Error) -> &mut Self {
        self.fails.push(err);
        self
    }

    pub fn cause(mut self, new_err: Error) -> Error {
        if self.is_empty() {
            new_err
        } else {
            let mut err = self.fails.remove(0);

            for fail in self.fails {
                err = err.cause(fail);
            }
            new_err.cause_by(err)
        }
    }

    pub fn cause_uid(self, new_err: Error) -> Error {
        if self.is_empty() {
            new_err
        } else {
            let mut fails = self.fails.into_iter().filter(|v| new_err.intersection(v));

            if let Some(fail) = fails.next() {
                let mut err = fail;

                for fail in fails {
                    err = err.cause(fail);
                }
                new_err.cause_by(err)
            } else {
                new_err
            }
        }
    }

    pub fn process<T, E>(self, ret: Result<T, E>) -> Result<T, Error>
    where
        E: Into<Error>,
    {
        match ret {
            Ok(v) => Ok(v),
            Err(e) => Err(self.cause_uid(e.into())),
        }
    }
}

impl Deref for FailManager {
    type Target = Vec<Error>;

    fn deref(&self) -> &Self::Target {
        &self.fails
    }
}

impl DerefMut for FailManager {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.fails
    }
}
