// Copyright 2018 Zach Miller
//
// Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
// or the MIT License (https://opensource.org/licenses/MIT), at your option.
//
// This file may not be copied, modified, or distributed except according to those terms.

use failure::{Backtrace, Context, Fail};

use std::fmt;
use std::result;

/// A [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) type specifically for the
/// `checkpoint` library.
pub type Result<T> = result::Result<T, Error>;

/// The error type for Checkpoint operations.
#[derive(Debug)]
pub struct Error {
    ctx: Context<ErrorKind>,
    msg: String,
}

impl Error {
    /// Creates a new checkpoint error with the provided ErrorKind and provided message.
    fn new(kind: ErrorKind, msg: String) -> Self {
        Self {
            ctx: Context::new(kind),
            msg,
        }
    }

    /// Returns the ErrorKind of the error.
    pub fn kind(&self) -> ErrorKind {
        *self.ctx.get_context()
    }

    /// Convenience method to create an error with ErrorKind::CpAlreadyExists and the provided
    /// message.
    pub fn cp_already_exists(msg: String) -> Self {
        Self::new(ErrorKind::CpAlreadyExists, msg)
    }

    /// Convenience method to create an error with ErrorKind::CpDoesntExist and the provided
    /// message.
    pub fn cp_doesnt_exist(msg: String) -> Self {
        Self::new(ErrorKind::CpDoesntExist, msg)
    }

    /// Convenience method to create an error with ErrorKind::CpInUse and the provided message.
    pub fn cp_in_use(msg: String) -> Self {
        Self::new(ErrorKind::CpInUse, msg)
    }

    /// Convenience method to create an error with ErrorKind::CpKeyDoesntExist and the provided
    /// message.
    pub fn cp_key_doesnt_exist(msg: String) -> Self {
        Self::new(ErrorKind::CpKeyDoesntExist, msg)
    }

    /// Convenience method to create an error with ErrorKind::Deserialize and the provided message.
    pub fn deserialize(msg: String) -> Self {
        Self::new(ErrorKind::Deserialize, msg)
    }

    /// Convenience method to create an error with ErrorKind::Initialization and the provided
    /// message.
    pub fn initialization(msg: String) -> Self {
        Self::new(ErrorKind::Initialization, msg)
    }

    /// Convenience method to create an error with ErrorKind::Integrity and the provided message.
    pub fn integrity(msg: String) -> Self {
        Self::new(ErrorKind::Integrity, msg)
    }

    /// Convenience method to create an error with ErrorKind::InvalidIdentifier and the provided
    /// message.
    pub fn invalid_identifier(msg: String) -> Self {
        Self::new(ErrorKind::InvalidIdentifier, msg)
    }

    /// Convenience method to create an error with ErrorKind::Io and the provided message.
    pub fn io(msg: String) -> Self {
        Self::new(ErrorKind::Io, msg)
    }

    /// Convenience method to create an error with ErrorKind::Other and the provided message.
    pub fn other(msg: String) -> Self {
        Self::new(ErrorKind::Other, msg)
    }

    /// Convenience method to create an error with ErrorKind::Serialize and the provided message.
    pub fn serialize(msg: String) -> Self {
        Self::new(ErrorKind::Serialize, msg)
    }
}

impl Fail for Error {
    fn cause(&self) -> Option<&Fail> {
        self.ctx.cause()
    }

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

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.ctx.fmt(f)
    }
}

/// The general types of checkpoint error.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ErrorKind {
    /// The specified checkpoint already exists.
    CpAlreadyExists,
    /// The specified checkpoint does not exist.
    CpDoesntExist,
    /// The specified checkpoint is in use.
    ///
    /// Most likely to occur if checkpoint removal is attempted while a reference to the underlying
    /// data exists.
    CpInUse,
    /// The specified checkpoint key does not exist.
    CpKeyDoesntExist,
    /// Deserialization of checkpoint data failed.
    Deserialize,
    /// An initialization error occurred.
    ///
    /// Most likely to occur when initializing storage or storage wrappers.
    Initialization,
    /// An integrity check of checkpoint data failed.
    Integrity,
    /// An invalid identifier was used.
    ///
    /// Most likely to occur when specifying a checkpoint identifier or a checkpoint key.
    InvalidIdentifier,
    /// An I/O error occurred.
    Io,
    /// A checkpoint error occurred that is not covered by [`ErrorKind`](enum.ErrorKind.html).
    Other,
    /// Serialization of checkpoint data failed.
    Serialize,
}

impl fmt::Display for ErrorKind {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}
