use std::error;
use std::fmt;

use crate::Key;

#[derive(Debug, thiserror::Error)]
pub enum Error<K: Key> {
	#[error("I/O error")]
	IO(#[from] std::io::Error),
	#[error("codec error")]
	Codec(#[from] Box<bincode::ErrorKind>),
	#[error("key {0} not found")]
	NotFound(K),
	#[error("missing value")]
	MissingValue
}

/// Wraps both [indexkv errors](Error) and generic errors that could end up in the stream passed to
/// [Store::write](crate::Store::write)
#[derive(Debug)]
pub enum StreamError<E: error::Error, K: Key> {
	Internal(Error<K>),
	Caller(E)
}

impl<E: error::Error, K: Key> fmt::Display for StreamError<E, K> {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		match self {
			Self::Internal(e) => write!(f, "indexkv error writing stream: {e}"),
			Self::Caller(e) => write!(f, "external error writing stream: {e}")
		}
	}
}

impl<E: error::Error + 'static, K: Key + 'static> error::Error for StreamError<E, K> {
	fn source(&self) -> Option<&(dyn error::Error + 'static)> {
		match self {
			Self::Internal(e) => Some(e),
			Self::Caller(e) => Some(e)
		}
	}
}

impl<E: error::Error, K: Key, T> From<T> for StreamError<E, K> where Error<K>: From<T> {
	fn from(inner: T) -> Self {
		Self::Internal(Error::from(inner))
	}
}

impl<E: error::Error, K: Key> StreamError<E, K> {
	pub(crate) fn from_external(inner: E) -> Self {
		Self::Caller(inner)
	}
}

