use crate::ArcDynHit;
use std::mem;
use std::ops::DerefMut;
use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::time::{Duration, Instant};
pub struct StaticHitCache {
	data: Box<[ArcDynHit]>,
}
impl StaticHitCache {
	pub fn new<H>(items: impl IntoIterator<Item = H>) -> Self
	where
		H: Into<ArcDynHit>,
	{
		Self {
			data: items.into_iter().map(Into::into).collect(),
		}
	}
	pub fn get(&self) -> &[ArcDynHit] {
		&self.data
	}
}
#[derive(Default, Debug)]
pub struct HitCache {
	data: RwLock<Data>,
	strategy: Strategy,
}
impl HitCache {
	pub fn max_age(mut self, age: Duration) -> Self {
		self.strategy = Strategy::MaxAge(age);
		self
	}
	pub fn clear(&self) {
		let mut guard = self.write();
		guard.hits.clear();
		guard.created = None;
	}
	pub fn get(&self) -> Option<Guard<'_>> {
		let guard = self.read();
		if self.is_stale(&guard) {
			return None;
		}
		Some(Guard(guard))
	}
	pub fn get_or<I, H>(&self, f: impl FnOnce() -> I) -> Guard<'_>
	where
		I: Iterator<Item = H>,
		H: Into<ArcDynHit>,
	{
		if let Some(a) = self.get() {
			return a;
		};
		self.set(f().map(Into::into));
		Guard(self.read())
	}
	pub fn set(&self, hits: impl Iterator<Item = ArcDynHit>) {
		let mut guard = self.write();
		guard.hits.clear();
		guard.hits.extend(hits);
		guard.created = Some(Instant::now());
	}
	#[doc(hidden)]
	pub fn _is_poisoned(&self) -> bool {
		self.data.is_poisoned()
	}
	fn is_stale(&self, data: &Data) -> bool {
		match self.strategy {
			Strategy::Lazy => data.created.is_none(),
			Strategy::MaxAge(max_age) => data.created.map(|c| c.elapsed() > max_age).unwrap_or(true),
		}
	}
	fn read(&self) -> RwLockReadGuard<'_, Data> {
		match self.data.read() {
			Ok(g) => g,
			Err(e) => {
				drop(e);
				drop(self.write());
				self.read()
			}
		}
	}
	fn write(&self) -> RwLockWriteGuard<'_, Data> {
		self.data.write().unwrap_or_else(|e| {
			log::error!("cache rwlock was poisoned! resetting cache and continuing");
			let mut guard = e.into_inner();
			mem::take(guard.deref_mut());
			self.data.clear_poison();
			guard
		})
	}
}
#[derive(Debug)]
enum Strategy {
	Lazy,
	MaxAge(Duration),
}
impl Default for Strategy {
	fn default() -> Self {
		Self::Lazy
	}
}
#[derive(Default, Debug)]
struct Data {
	hits: Vec<ArcDynHit>,
	created: Option<Instant>,
}
#[derive(Debug)]
pub struct Guard<'a>(RwLockReadGuard<'a, Data>);
impl Guard<'_> {
	pub fn get(&self) -> &[ArcDynHit] {
		&self.0.hits
	}
}