use ffi;
use std::convert::{From, Into};
use std::{error, fmt};

/// An error.
#[derive(Debug)]
pub struct Error {
    pub kind: ErrorKind,
    pub message: Option<String>,
}

macro_rules! declare(
    ($($left:ident => $right:ident,)*) => (
        /// An error kind.
        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
        pub enum ErrorKind {
            $($left = ffi::$right as isize,)*
            Unknown,
        }

        impl From<isize> for ErrorKind {
            fn from(code: isize) -> ErrorKind {
                match code as ::libc::c_int {
                    $(ffi::$right => ErrorKind::$left,)*
                    _ => ErrorKind::Unknown,
                }
            }
        }
    );
);

declare!(
    Abort => SQLITE_ABORT,
    Authorization => SQLITE_AUTH,
    Busy => SQLITE_BUSY,
    CantOpen => SQLITE_CANTOPEN,
    Constraint => SQLITE_CONSTRAINT,
    Corruption => SQLITE_CORRUPT,
    Done => SQLITE_DONE,
    Empty => SQLITE_EMPTY,
    Error => SQLITE_ERROR,
    Format => SQLITE_FORMAT,
    Full => SQLITE_FULL,
    Internal => SQLITE_INTERNAL,
    Interruption => SQLITE_INTERRUPT,
    IOError => SQLITE_IOERR,
    Locked => SQLITE_LOCKED,
    Mismatch => SQLITE_MISMATCH,
    Misuse => SQLITE_MISUSE,
    NoLargeFileSupport => SQLITE_NOLFS,
    NoMemory => SQLITE_NOMEM,
    NotDatabase => SQLITE_NOTADB,
    NotFound => SQLITE_NOTFOUND,
    Notice => SQLITE_NOTICE,
    OK => SQLITE_OK,
    Permission => SQLITE_PERM,
    Protocol => SQLITE_PROTOCOL,
    Range => SQLITE_RANGE,
    ReadOnly => SQLITE_READONLY,
    Row => SQLITE_ROW,
    Schema => SQLITE_SCHEMA,
    TooBig => SQLITE_TOOBIG,
    Warning => SQLITE_WARNING,
);

impl<T> From<T> for Error where T: Into<String> {
    #[inline]
    fn from(message: T) -> Error {
        Error { kind: ErrorKind::Unknown, message: Some(message.into()) }
    }
}

impl From<ErrorKind> for Error {
    #[inline]
    fn from(kind: ErrorKind) -> Error {
        Error { kind: kind, message: None }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        match self.message {
            Some(ref message) => message.fmt(formatter),
            _ => self.kind.fmt(formatter),
        }
    }
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match self.message {
            Some(ref message) => message,
            _ => "an SQLite error",
        }
    }
}

impl fmt::Display for ErrorKind {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            ErrorKind::Unknown => write!(formatter, "an unknown SQLite result code"),
            _ => write!(formatter, "SQLite result code {}", *self as isize),
        }
    }
}

pub fn last(raw: *mut ffi::sqlite3) -> Option<Error> {
    unsafe {
        let code = ffi::sqlite3_errcode(raw);
        if code == ffi::SQLITE_OK {
            return None;
        }
        let message = ffi::sqlite3_errmsg(raw);
        if message.is_null() {
            return None;
        }
        Some(Error {
            kind: ErrorKind::from(code as isize),
            message: Some(c_str_to_string!(message)),
        })
    }
}

#[cfg(test)]
mod tests {
    use super::ErrorKind;

    #[test]
    fn fmt() {
        assert_eq!(format!("{}", ErrorKind::OK),
                   String::from("SQLite result code 0"));
        assert_eq!(format!("{}", ErrorKind::from(777)),
                   String::from("an unknown SQLite result code"));
    }
}