/*!
# Strongly-typed strings for use with XML 1.0 documents

These are mostly re-exported from [`rxml_validation`]. See that crate's
documentation for an overview over the different types and their use cases.

## Construction

In addition to the construction methods described in [`rxml_validation`],
[`str`]-like references ([`&NameStr`][`NameStr`], [`&NcNameStr`][`NcNameStr`])
can be created from a string literal, using the macros offered when this crate
is built with the `macros` feature:
[`xml_name!`][`crate::xml_name`], [`xml_ncname!`][`crate::xml_ncname`].
*/
use alloc::borrow::ToOwned;
use alloc::string::String;
use alloc::sync::Arc;
use core::borrow::Borrow;
use core::cmp::Ordering;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::ops::Deref;

pub use rxml_validation::{CompactString, Error, Name, NameStr, NcName, NcNameStr};

use crate::error::{Error as XmlError, ErrorContext};

enum Shared {
	Static(&'static str),
	Ptr(Arc<String>),
}

enum Inner {
	Owned(String),
	Shared(Shared),
}

/// An XML namespace name.
///
/// Commonly, XML namespace names are URIs. Almost all URIs are also valid
/// namespace names, with the prominent exception of the empty URI: namespace
/// names in XML must not be empty. The empty namespace name signals that an
/// element is not namespaced. For purposes of comparison and others, we can
/// treat the empty namespace name as just another namespace, which is why it
/// is an allowed value for this struct.
///
/// Internally, `Namespace` is based on [`String`] or a static [`str`]
/// slice. It can be accessed mutably through [`Namespace::make_mut`].
///
/// Depending on the history of a specific `Namespace` instance, cloning
/// is either `O(1)` or `O(n)`. To ensure that the next clone is `O(1)`, call
/// [`Namespace::make_shared`] or use [`Namespace::share`] instead of
/// `clone()`.
///
/// `Namespace` is designed such that, no matter which internal
/// representation is at work, it hashes, compares, and orders exactly like a
/// `String` (or `str`), allowing it to be borrowed as such.
///
/// # Advantages over `String`
///
/// Unlike [`String`], `Namespace` is optimized for strings which:
///
/// - are not known by the `rxml` library at compile-time
/// - need to be passed around freely (static lifetime)
///
/// Internally, `Namespace` can keep either a `&'static str`, a
/// shared (ref-counted) pointer to a `String` or an owned instance of a
/// `String`. If the crate is built with the `shared_ns` feature,
/// `Namespace` is `Send` and `Sync`, otherwise it is neither.
pub struct Namespace(Inner);

const fn xml() -> Namespace {
	Namespace(Inner::Shared(Shared::Static(crate::XMLNS_XML)))
}

const fn xmlns() -> Namespace {
	Namespace(Inner::Shared(Shared::Static(crate::XMLNS_XMLNS)))
}

const fn none() -> Namespace {
	Namespace(Inner::Shared(Shared::Static(
		crate::parser::XMLNS_UNNAMESPACED,
	)))
}

impl Namespace {
	/// `Namespace` representing the built-in XML namespace.
	///
	/// See also [`crate::XMLNS_XML`].
	pub const XML: Namespace = xml();

	/// `Namespace` representing the built-in XML namespacing namespace.
	///
	/// See also [`crate::XMLNS_XMLNS`].
	pub const XMLNS: Namespace = xmlns();

	/// `Namespace` representing the empty namespace name.
	pub const NONE: Namespace = none();

	/// Construct a namespace name for the built-in XML namespace.
	///
	/// This function does not allocate.
	///
	/// # Example
	///
	/// ```
	/// # use rxml::strings::Namespace;
	/// let ns = Namespace::xml();
	/// assert_eq!(ns, rxml::XMLNS_XML);
	/// ```
	#[inline(always)]
	pub fn xml() -> &'static Self {
		static RESULT: Namespace = Namespace::XML;
		&RESULT
	}

	/// Construct a namespace name for the built-in XML namespacing namespace.
	///
	/// This function does not allocate.
	///
	/// # Example
	///
	/// ```
	/// # use rxml::strings::Namespace;
	/// let ns = Namespace::xmlns();
	/// assert_eq!(ns, rxml::XMLNS_XMLNS);
	/// ```
	#[inline(always)]
	pub fn xmlns() -> &'static Self {
		static RESULT: Namespace = Namespace::XMLNS;
		&RESULT
	}

	/// Construct an empty namespace name, representing unnamespaced elements.
	///
	/// # Example
	///
	/// ```
	/// # use rxml::strings::Namespace;
	/// let ns = Namespace::none();
	/// assert_eq!(ns, "");
	/// ```
	#[inline(always)]
	pub fn none() -> &'static Self {
		static RESULT: Namespace = Namespace::NONE;
		&RESULT
	}

	/// Attempts to deduplicate the given namespace name with a set of
	/// statically compiled well-known namespaces.
	///
	/// If no matching statically compiled namespace is found, `None` is
	/// returned.
	///
	/// Otherwise, a `Namespace` is created without allocating and without
	/// sharing data with `s`.
	pub fn try_share_static(s: &str) -> Option<Self> {
		if s.is_empty() {
			return Some(Self::NONE);
		}
		if s == crate::XMLNS_XML {
			return Some(Self::XML);
		}
		if s == crate::XMLNS_XMLNS {
			return Some(Self::XMLNS);
		}
		None
	}

	/// Construct a namespace name for a custom namespace from a static string.
	///
	/// This function is provided for use in `const` contexts, while normally
	/// you would use the `From<&'static str>` implementation.
	///
	/// # Example
	///
	/// ```
	/// # use rxml::strings::Namespace;
	/// static NS: Namespace = Namespace::from_str("jabber:client");
	/// assert_eq!(NS, "jabber:client");
	/// ```
	pub const fn from_str(s: &'static str) -> Self {
		Self(Inner::Shared(Shared::Static(s)))
	}

	/// Make the `Namespace` mutable and return a mutable reference to its
	/// inner [`String`].
	///
	/// Depending on how the `Namespace` was constructed and how it has
	/// been used, calling this function may require copying its contents.
	pub fn make_mut(&mut self) -> &mut String {
		match self.0 {
			Inner::Shared(Shared::Static(v)) => {
				let mut tmp = Inner::Owned(v.to_owned());
				core::mem::swap(&mut self.0, &mut tmp);
				let Inner::Owned(ref mut v) = self.0 else {
					unreachable!()
				};
				v
			}
			Inner::Shared(Shared::Ptr(ref mut v)) => Arc::make_mut(v),
			Inner::Owned(ref mut v) => v,
		}
	}

	/// Change the representation of this `Namespace` such that it can be
	/// cloned cheaply.
	///
	/// This may make the next call to a mutating method, such as
	/// [`make_mut`][`Self::make_mut`] more expensive as it may then require
	/// copying the data.
	pub fn make_shared(&mut self) {
		if let Inner::Owned(ref mut v) = self.0 {
			if let Some(result) = Self::try_share_static(v) {
				*self = result;
				return;
			}
			let mut tmp = String::new();
			core::mem::swap(&mut tmp, v);
			self.0 = Inner::Shared(Shared::Ptr(Arc::new(tmp)));
		}
	}

	/// Make this `Namespace` instance cheaply cloneable and clone it.
	///
	/// Because this may change the layout of the `Namespace` to make it
	/// efficiently cloneable, it requires mutating access.
	pub fn share(&mut self) -> Self {
		self.make_shared();
		self.clone()
	}

	/// Consume this `Namespace` instance, make it cheaply cloneable, and
	/// return it.
	pub fn shared(mut self) -> Self {
		self.make_shared();
		self.clone()
	}

	/// Clone this `Namespace` instance and then make sure that further
	/// clones from the returned value can be made efficiently.
	pub fn clone_shared(&self) -> Self {
		self.clone().shared()
	}

	/// Return a reference to the inner namespace name if it is not empty or
	/// `None` otherwise.
	pub fn as_namespace_name(&self) -> Option<&str> {
		let s = self.deref();
		if s.is_empty() {
			None
		} else {
			Some(s)
		}
	}

	/// Return true if this `Namespace` is the empty namespace name.
	///
	/// The empty namespace name is not a valid namespace and is thus used to
	/// identify unnamespaced elements.
	pub fn is_none(&self) -> bool {
		self.deref().is_empty()
	}

	/// Return true if this `Namespace` is a valid namespace name.
	pub fn is_some(&self) -> bool {
		!self.deref().is_empty()
	}

	/// Return a reference to the string slice inside this [`Namespace`].
	pub fn as_str(&self) -> &str {
		self.deref()
	}
}

impl Deref for Namespace {
	type Target = str;

	fn deref(&self) -> &Self::Target {
		match self.0 {
			Inner::Shared(Shared::Static(v)) => v,
			Inner::Shared(Shared::Ptr(ref v)) => v.deref().deref(),
			Inner::Owned(ref v) => v.deref(),
		}
	}
}

impl AsRef<str> for Namespace {
	fn as_ref(&self) -> &str {
		self.deref()
	}
}

impl Borrow<str> for Namespace {
	fn borrow(&self) -> &str {
		self.deref()
	}
}

impl Clone for Namespace {
	fn clone(&self) -> Self {
		Self(match self.0 {
			Inner::Shared(Shared::Static(v)) => Inner::Shared(Shared::Static(v)),
			Inner::Shared(Shared::Ptr(ref v)) => Inner::Shared(Shared::Ptr(Arc::clone(v))),
			Inner::Owned(ref v) => Inner::Owned(v.clone()),
		})
	}
}

impl fmt::Debug for Namespace {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		let mut wrapper = match self.0 {
			Inner::Shared(Shared::Static(_)) => f.debug_tuple("Namespace<{Static}>"),
			Inner::Shared(Shared::Ptr(_)) => f.debug_tuple("Namespace<{Ptr}>"),
			Inner::Owned(_) => f.debug_tuple("Namespace<{Owned}>"),
		};
		wrapper.field(&self.deref()).finish()
	}
}

impl fmt::Display for Namespace {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		<str as fmt::Display>::fmt(self.deref(), f)
	}
}

impl Eq for Namespace {}

impl From<Namespace> for String {
	fn from(other: Namespace) -> Self {
		match other.0 {
			Inner::Owned(v) => v,
			Inner::Shared(Shared::Static(v)) => v.to_owned(),
			Inner::Shared(Shared::Ptr(v)) => Arc::unwrap_or_clone(v),
		}
	}
}

impl From<Namespace> for Arc<String> {
	fn from(other: Namespace) -> Self {
		match other.0 {
			Inner::Owned(v) => Arc::new(v),
			Inner::Shared(Shared::Static(v)) => Arc::new(v.to_owned()),
			Inner::Shared(Shared::Ptr(v)) => v,
		}
	}
}

impl From<Name> for Namespace {
	fn from(other: Name) -> Self {
		let v: String = other.into();
		debug_assert!(!v.is_empty()); // enforced by Name
		Self(Inner::Owned(v))
	}
}

impl From<NcName> for Namespace {
	fn from(other: NcName) -> Self {
		let v: String = other.into();
		debug_assert!(!v.is_empty()); // enforced by NcName
		Self(Inner::Owned(v))
	}
}

impl From<&'static Name> for Namespace {
	fn from(other: &'static Name) -> Self {
		let v: &'static str = other.as_ref();
		debug_assert!(!v.is_empty()); // enforced by NameStr
		Self(Inner::Shared(Shared::Static(v)))
	}
}

impl From<&'static NcName> for Namespace {
	fn from(other: &'static NcName) -> Self {
		let v: &'static str = other.as_ref();
		debug_assert!(!v.is_empty()); // enforced by NcNameStr
		Self(Inner::Shared(Shared::Static(v)))
	}
}

impl From<&'static str> for Namespace {
	fn from(other: &'static str) -> Self {
		Self(Inner::Shared(Shared::Static(other)))
	}
}

impl From<String> for Namespace {
	fn from(other: String) -> Self {
		if let Some(result) = Self::try_share_static(&other) {
			return result;
		}
		Self(Inner::Owned(other))
	}
}

impl From<Arc<String>> for Namespace {
	fn from(other: Arc<String>) -> Self {
		if let Some(result) = Self::try_share_static(&other) {
			return result;
		}
		Self(Inner::Shared(Shared::Ptr(other)))
	}
}

impl Hash for Namespace {
	fn hash<H: Hasher>(&self, h: &mut H) {
		self.deref().hash(h)
	}
}

impl Ord for Namespace {
	fn cmp(&self, other: &Namespace) -> Ordering {
		self.deref().cmp(other.deref())
	}
}

impl PartialEq for Namespace {
	fn eq(&self, other: &Namespace) -> bool {
		self.deref() == other.deref()
	}
}

impl PartialEq<&str> for Namespace {
	fn eq(&self, other: &&str) -> bool {
		self.deref() == *other
	}
}

impl PartialEq<Namespace> for &str {
	fn eq(&self, other: &Namespace) -> bool {
		*self == other.deref()
	}
}

impl PartialEq<str> for Namespace {
	fn eq(&self, other: &str) -> bool {
		self.deref() == other
	}
}

impl PartialEq<Namespace> for str {
	fn eq(&self, other: &Namespace) -> bool {
		self == other.deref()
	}
}

impl PartialOrd for Namespace {
	fn partial_cmp(&self, other: &Namespace) -> Option<Ordering> {
		Some(self.cmp(other))
	}
}

/**
Check whether a str is valid XML 1.0 CData

# Example

```rust
use rxml::Error;
use rxml::strings::validate_cdata;

assert!(validate_cdata("foo bar baz <fnord!>").is_ok());
assert!(matches!(validate_cdata("\x01"), Err(Error::UnexpectedChar(_, '\x01', _))));
*/
pub fn validate_cdata(s: &str) -> Result<(), XmlError> {
	rxml_validation::validate_cdata(s).map_err(|e| XmlError::from_validation(e, None))
}

/**
Check whether a str is a valid XML 1.0 Name

**Note:** This does *not* enforce that the name contains only a single colon.

# Example

```rust
use rxml::Error;
use rxml::strings::validate_name;

assert!(validate_name("foobar").is_ok());
assert!(validate_name("foo:bar").is_ok());
assert!(matches!(validate_name("foo bar"), Err(Error::UnexpectedChar(_, ' ', _))));
assert!(matches!(validate_name(""), Err(Error::InvalidSyntax(_))));
*/
pub fn validate_name(s: &str) -> Result<(), XmlError> {
	rxml_validation::validate_name(s)
		.map_err(|e| XmlError::from_validation(e, Some(ErrorContext::Name)))
}

/**
Check whether a str is a valid XML 1.0 Name, without colons.

# Example

```rust
use rxml::Error;
use rxml::strings::validate_ncname;

assert!(validate_ncname("foobar").is_ok());
assert!(matches!(validate_ncname("foo:bar"), Err(Error::MultiColonName(_))));
assert!(matches!(validate_ncname(""), Err(Error::EmptyNamePart(_))));
*/
pub fn validate_ncname(s: &str) -> Result<(), XmlError> {
	match rxml_validation::validate_ncname(s)
		.map_err(|e| XmlError::from_validation(e, Some(ErrorContext::Name)))
	{
		Err(XmlError::UnexpectedChar(ctx, ':', _)) => Err(XmlError::MultiColonName(ctx)),
		Err(XmlError::InvalidSyntax(_)) => Err(XmlError::EmptyNamePart(None)),
		other => other,
	}
}

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn can_slice_namespace_name() {
		let nsn = Namespace::xml();
		assert_eq!(&nsn[..4], "http");
	}
}
