mod definition;
pub mod inverse;
use crate::{Direction, LenientLangTag, LenientLangTagBuf, Term};
use contextual::WithContext;
use iref::IriBuf;
use json_ld_syntax::{KeywordType, Nullable};
use once_cell::sync::OnceCell;
use rdf_types::{BlankIdBuf, Id, Vocabulary};
use std::borrow::Borrow;
use std::hash::Hash;
pub use json_ld_syntax::context::{
	definition::{Key, KeyOrType, Type},
	term_definition::Nest,
};
pub use definition::*;
pub use inverse::InverseContext;
pub struct Context<T = IriBuf, B = BlankIdBuf> {
	original_base_url: Option<T>,
	base_iri: Option<T>,
	vocabulary: Option<Term<T, B>>,
	default_language: Option<LenientLangTagBuf>,
	default_base_direction: Option<Direction>,
	previous_context: Option<Box<Self>>,
	definitions: Definitions<T, B>,
	inverse: OnceCell<InverseContext<T, B>>,
}
impl<T, B> Default for Context<T, B> {
	fn default() -> Self {
		Self {
			original_base_url: None,
			base_iri: None,
			vocabulary: None,
			default_language: None,
			default_base_direction: None,
			previous_context: None,
			definitions: Definitions::default(),
			inverse: OnceCell::default(),
		}
	}
}
pub type DefinitionEntryRef<'a, T = IriBuf, B = BlankIdBuf> = (&'a Key, &'a TermDefinition<T, B>);
impl<T, B> Context<T, B> {
	pub fn new(base_iri: Option<T>) -> Self
	where
		T: Clone,
	{
		Self {
			original_base_url: base_iri.clone(),
			base_iri,
			vocabulary: None,
			default_language: None,
			default_base_direction: None,
			previous_context: None,
			definitions: Definitions::default(),
			inverse: OnceCell::default(),
		}
	}
	pub fn get<Q>(&self, term: &Q) -> Option<TermDefinitionRef<T, B>>
	where
		Key: Borrow<Q>,
		KeywordType: Borrow<Q>,
		Q: ?Sized + Hash + Eq,
	{
		self.definitions.get(term)
	}
	pub fn get_normal<Q>(&self, term: &Q) -> Option<&NormalTermDefinition<T, B>>
	where
		Key: Borrow<Q>,
		Q: ?Sized + Hash + Eq,
	{
		self.definitions.get_normal(term)
	}
	pub fn get_type(&self) -> Option<&TypeTermDefinition> {
		self.definitions.get_type()
	}
	pub fn contains_term<Q>(&self, term: &Q) -> bool
	where
		Key: Borrow<Q>,
		KeywordType: Borrow<Q>,
		Q: ?Sized + Hash + Eq,
	{
		self.definitions.contains_term(term)
	}
	pub fn original_base_url(&self) -> Option<&T> {
		self.original_base_url.as_ref()
	}
	pub fn base_iri(&self) -> Option<&T> {
		self.base_iri.as_ref()
	}
	pub fn vocabulary(&self) -> Option<&Term<T, B>> {
		match &self.vocabulary {
			Some(v) => Some(v),
			None => None,
		}
	}
	pub fn default_language(&self) -> Option<&LenientLangTag> {
		self.default_language
			.as_ref()
			.map(|tag| tag.as_lenient_lang_tag_ref())
	}
	pub fn default_base_direction(&self) -> Option<Direction> {
		self.default_base_direction
	}
	pub fn previous_context(&self) -> Option<&Self> {
		match &self.previous_context {
			Some(c) => Some(c),
			None => None,
		}
	}
	pub fn len(&self) -> usize {
		self.definitions.len()
	}
	pub fn is_empty(&self) -> bool {
		self.definitions.is_empty()
	}
	pub fn definitions(&self) -> &Definitions<T, B> {
		&self.definitions
	}
	pub fn has_protected_items(&self) -> bool {
		for binding in self.definitions() {
			if binding.definition().protected() {
				return true;
			}
		}
		false
	}
	pub fn inverse(&self) -> &InverseContext<T, B>
	where
		T: Clone + Hash + Eq,
		B: Clone + Hash + Eq,
	{
		self.inverse.get_or_init(|| self.into())
	}
	pub fn set_normal(
		&mut self,
		key: Key,
		definition: Option<NormalTermDefinition<T, B>>,
	) -> Option<NormalTermDefinition<T, B>> {
		self.inverse.take();
		self.definitions.set_normal(key, definition)
	}
	pub fn set_type(&mut self, type_: Option<TypeTermDefinition>) -> Option<TypeTermDefinition> {
		self.definitions.set_type(type_)
	}
	pub fn set_base_iri(&mut self, iri: Option<T>) {
		self.inverse.take();
		self.base_iri = iri
	}
	pub fn set_vocabulary(&mut self, vocab: Option<Term<T, B>>) {
		self.inverse.take();
		self.vocabulary = vocab;
	}
	pub fn set_default_language(&mut self, lang: Option<LenientLangTagBuf>) {
		self.inverse.take();
		self.default_language = lang;
	}
	pub fn set_default_base_direction(&mut self, dir: Option<Direction>) {
		self.inverse.take();
		self.default_base_direction = dir;
	}
	pub fn set_previous_context(&mut self, previous: Self) {
		self.inverse.take();
		self.previous_context = Some(Box::new(previous))
	}
	pub fn into_syntax_definition(
		self,
		vocabulary: &impl Vocabulary<Iri = T, BlankId = B>,
	) -> json_ld_syntax::context::Definition {
		let (bindings, type_) = self.definitions.into_parts();
		json_ld_syntax::context::Definition {
			base: self
				.base_iri
				.map(|i| Nullable::Some(vocabulary.iri(&i).unwrap().to_owned().into())),
			import: None,
			language: self.default_language.map(Nullable::Some),
			direction: self.default_base_direction.map(Nullable::Some),
			propagate: None,
			protected: None,
			type_: type_.map(TypeTermDefinition::into_syntax_definition),
			version: None,
			vocab: self.vocabulary.map(|v| match v {
				Term::Null => Nullable::Null,
				Term::Id(r) => Nullable::Some(r.with(vocabulary).to_string().into()),
				Term::Keyword(_) => panic!("invalid vocab"),
			}),
			bindings: bindings
				.into_iter()
				.map(|(key, definition)| (key, definition.into_syntax_definition(vocabulary)))
				.collect(),
		}
	}
	pub fn map_ids<U, C>(
		self,
		mut map_iri: impl FnMut(T) -> U,
		mut map_id: impl FnMut(Id<T, B>) -> Id<U, C>,
	) -> Context<U, C> {
		self.map_ids_with(&mut map_iri, &mut map_id)
	}
	fn map_ids_with<U, C>(
		self,
		map_iri: &mut impl FnMut(T) -> U,
		map_id: &mut impl FnMut(Id<T, B>) -> Id<U, C>,
	) -> Context<U, C> {
		Context {
			original_base_url: self.original_base_url.map(&mut *map_iri),
			base_iri: self.base_iri.map(&mut *map_iri),
			vocabulary: self.vocabulary.map(|v| v.map_id(&mut *map_id)),
			default_language: self.default_language,
			default_base_direction: self.default_base_direction,
			previous_context: self
				.previous_context
				.map(|c| Box::new((*c).map_ids_with(map_iri, map_id))),
			definitions: self.definitions.map_ids(map_iri, map_id),
			inverse: OnceCell::new(),
		}
	}
}
pub trait IntoSyntax<T = IriBuf, B = BlankIdBuf> {
	fn into_syntax(
		self,
		vocabulary: &impl Vocabulary<Iri = T, BlankId = B>,
	) -> json_ld_syntax::context::Context;
}
impl<T, B> IntoSyntax<T, B> for json_ld_syntax::context::Context {
	fn into_syntax(
		self,
		_namespace: &impl Vocabulary<Iri = T, BlankId = B>,
	) -> json_ld_syntax::context::Context {
		self
	}
}
impl<T, B: Clone> IntoSyntax<T, B> for Context<T, B> {
	fn into_syntax(
		self,
		vocabulary: &impl Vocabulary<Iri = T, BlankId = B>,
	) -> json_ld_syntax::context::Context {
		json_ld_syntax::context::Context::One(json_ld_syntax::ContextEntry::Definition(
			self.into_syntax_definition(vocabulary),
		))
	}
}
impl<T: Clone, B: Clone> Clone for Context<T, B> {
	fn clone(&self) -> Self {
		Self {
			original_base_url: self.original_base_url.clone(),
			base_iri: self.base_iri.clone(),
			vocabulary: self.vocabulary.clone(),
			default_language: self.default_language.clone(),
			default_base_direction: self.default_base_direction,
			previous_context: self.previous_context.clone(),
			definitions: self.definitions.clone(),
			inverse: OnceCell::default(),
		}
	}
}
impl<T: PartialEq, B: PartialEq> PartialEq for Context<T, B> {
	fn eq(&self, other: &Self) -> bool {
		self.original_base_url == other.original_base_url
			&& self.base_iri == other.base_iri
			&& self.vocabulary == other.vocabulary
			&& self.default_language == other.default_language
			&& self.default_base_direction == other.default_base_direction
			&& self.previous_context == other.previous_context
	}
}