// Copyright 2018-2019 Mozilla
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.

use std::{
    io,
    path::PathBuf,
    sync,
    thread,
    thread::ThreadId,
};

use thiserror::Error;

pub use crate::backend::SafeModeError;
use crate::value::Type;

#[derive(Debug, Error)]
pub enum DataError {
    #[error("unknown type tag: {0}")]
    UnknownType(u8),

    #[error("unexpected type tag: expected {expected}, got {actual}")]
    UnexpectedType {
        expected: Type,
        actual: Type,
    },

    #[error("empty data; expected tag")]
    Empty,

    #[error("invalid value for type {value_type}: {err}")]
    DecodingError {
        value_type: Type,
        err: Box<bincode::ErrorKind>,
    },

    #[error("couldn't encode value: {0}")]
    EncodingError(#[from] Box<bincode::ErrorKind>),

    #[error("invalid uuid bytes")]
    InvalidUuid,
}

#[derive(Debug, Error)]
pub enum StoreError {
    #[error("manager poisoned")]
    ManagerPoisonError,

    #[error("database corrupted")]
    DatabaseCorrupted,

    #[error("key/value pair not found")]
    KeyValuePairNotFound,

    #[error("unsupported size of key/DB name/data")]
    KeyValuePairBadSize,

    #[error("file is not a valid database")]
    FileInvalid,

    #[error("environment mapsize reached")]
    MapFull,

    #[error("environment maxdbs reached")]
    DbsFull,

    #[error("environment maxreaders reached")]
    ReadersFull,

    #[error("I/O error: {0:?}")]
    IoError(#[from] io::Error),

    #[error("environment path does not exist or not the right type: {0:?}")]
    UnsuitableEnvironmentPath(PathBuf),

    #[error("data error: {0:?}")]
    DataError(#[from] DataError),

    #[cfg(feature = "lmdb")]
    #[error("lmdb backend error: {0}")]
    LmdbError(lmdb::Error),

    #[error("safe mode backend error: {0}")]
    SafeModeError(SafeModeError),

    #[error("read transaction already exists in thread {0:?}")]
    ReadTransactionAlreadyExists(ThreadId),

    #[error("attempted to open DB during transaction in thread {0:?}")]
    OpenAttemptedDuringTransaction(ThreadId),
}

impl StoreError {
    pub fn open_during_transaction() -> StoreError {
        StoreError::OpenAttemptedDuringTransaction(thread::current().id())
    }

    pub fn read_transaction_already_exists() -> StoreError {
        StoreError::ReadTransactionAlreadyExists(thread::current().id())
    }
}

impl<T> From<sync::PoisonError<T>> for StoreError {
    fn from(_: sync::PoisonError<T>) -> StoreError {
        StoreError::ManagerPoisonError
    }
}

#[derive(Debug, Error)]
pub enum CloseError {
    #[error("manager poisoned")]
    ManagerPoisonError,

    #[error("close attempted while manager has an environment still open")]
    EnvironmentStillOpen,

    #[error("close attempted while an environment not known to the manager is still open")]
    UnknownEnvironmentStillOpen,

    #[error("I/O error: {0:?}")]
    IoError(#[from] io::Error),
}

impl<T> From<sync::PoisonError<T>> for CloseError {
    fn from(_: sync::PoisonError<T>) -> CloseError {
        CloseError::ManagerPoisonError
    }
}

#[derive(Debug, Error)]
pub enum MigrateError {
    #[error("store error: {0}")]
    StoreError(#[from] StoreError),

    #[error("close error: {0}")]
    CloseError(#[from] CloseError),

    #[error("manager poisoned")]
    ManagerPoisonError,

    #[error("source is empty")]
    SourceEmpty,

    #[error("destination is not empty")]
    DestinationNotEmpty,
}

impl<T> From<sync::PoisonError<T>> for MigrateError {
    fn from(_: sync::PoisonError<T>) -> MigrateError {
        MigrateError::ManagerPoisonError
    }
}
