use diesel;
use failure::{Backtrace, Context, Error, Fail};
use std::fmt;

#[derive(Debug)]
pub struct DatabaseError {
    inner: Context<DatabaseErrorKind>,
}

#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)]
pub enum DatabaseErrorKind {
    #[fail(display = "Error connecting to the database")]
    Open,
    #[fail(display = "Invalid data directory")]
    InvalidPath,
    #[fail(display = "Error updating data")]
    Update,
    #[fail(display = "Error migrating db schema")]
    Migration,
    #[fail(display = "Error deleting data")]
    Delete,
    #[fail(display = "Error retrieving data")]
    Select,
    #[fail(display = "Error inserting data")]
    Insert,
    #[fail(display = "Error setting options")]
    Options,
    #[fail(display = "Unknown Error")]
    Unknown,
}

impl Fail for DatabaseError {
    fn cause(&self) -> Option<&dyn Fail> {
        self.inner.cause()
    }

    fn backtrace(&self) -> Option<&Backtrace> {
        self.inner.backtrace()
    }
}

impl fmt::Display for DatabaseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(&self.inner, f)
    }
}

impl DatabaseError {
    pub fn kind(&self) -> DatabaseErrorKind {
        *self.inner.get_context()
    }
}

impl From<DatabaseErrorKind> for DatabaseError {
    fn from(kind: DatabaseErrorKind) -> DatabaseError {
        DatabaseError { inner: Context::new(kind) }
    }
}

impl From<Context<DatabaseErrorKind>> for DatabaseError {
    fn from(inner: Context<DatabaseErrorKind>) -> DatabaseError {
        DatabaseError { inner }
    }
}

impl From<Error> for DatabaseError {
    fn from(_: Error) -> DatabaseError {
        DatabaseError {
            inner: Context::new(DatabaseErrorKind::Unknown),
        }
    }
}

impl From<diesel::result::Error> for DatabaseError {
    fn from(_: diesel::result::Error) -> DatabaseError {
        DatabaseError {
            inner: Context::new(DatabaseErrorKind::Unknown),
        }
    }
}
