//! GL API interface definition.

use quick_xml::events::BytesStart;

use crate::deserialization::{Deserialize, Deserializer, DeserializerError};
use crate::DeserializationError;

/// GL API interface definition.
#[derive(Debug, PartialEq, Eq)]
pub struct APIInterfaceDefinition
{
    api_name: String,
    name: String,
    version: String,
    requirements: Vec<Requirement>,
    removals: Vec<Removal>,
}

impl APIInterfaceDefinition
{
    /// Returns a new `APIInterfaceDefinition`.
    #[must_use]
    pub fn new(
        api_name: impl Into<String>,
        name: impl Into<String>,
        version: impl Into<String>,
        requirements: impl IntoIterator<Item = Requirement>,
        removals: impl IntoIterator<Item = Removal>,
    ) -> Self
    {
        Self {
            api_name: api_name.into(),
            name: name.into(),
            version: version.into(),
            requirements: requirements.into_iter().collect(),
            removals: removals.into_iter().collect(),
        }
    }

    /// Returns the API name.
    #[must_use]
    pub fn api_name(&self) -> &str
    {
        &self.api_name
    }

    /// Returns the name.
    #[must_use]
    pub fn name(&self) -> &str
    {
        &self.name
    }

    /// Returns the version.
    #[must_use]
    pub fn version(&self) -> &str
    {
        &self.version
    }

    /// Returns the requirements.
    #[must_use]
    pub fn requirements(&self) -> &[Requirement]
    {
        &self.requirements
    }

    /// Returns the removals.
    #[must_use]
    pub fn removals(&self) -> &[Removal]
    {
        &self.removals
    }
}

impl Deserialize for APIInterfaceDefinition
{
    type Error = Error;

    fn deserialize<TDeserializer: Deserializer>(
        start: &BytesStart,
        deserializer: &mut TDeserializer,
    ) -> Result<Self, Self::Error>
    {
        let api_name = String::from_utf8(
            start
                .try_get_attribute(b"api")
                .map_err(DeserializerError::ReadFailed)?
                .ok_or_else(|| Error::MissingAPIName)?
                .value
                .into_owned(),
        )
        .map_err(|_| Error::APINameNotUTF8)?;

        let name = String::from_utf8(
            start
                .try_get_attribute(b"name")
                .map_err(DeserializerError::ReadFailed)?
                .ok_or_else(|| Error::MissingName)?
                .value
                .into_owned(),
        )
        .map_err(|_| Error::NameNotUTF8)?;

        let version = String::from_utf8(
            start
                .try_get_attribute(b"number")
                .map_err(DeserializerError::ReadFailed)?
                .ok_or_else(|| Error::MissingVersionNumber)?
                .value
                .into_owned(),
        )
        .map_err(|_| Error::VersionNotUTF8)?;

        let requirements = deserializer.de_tag_list::<Requirement>("require")?;

        let removals = deserializer.de_tag_list::<Removal>("remove")?;

        Ok(Self {
            api_name,
            name,
            version,
            requirements,
            removals,
        })
    }
}

/// [`APIInterfaceDefinition`] error.
#[derive(Debug, thiserror::Error)]
pub enum Error
{
    /// Missing API name attribute.
    #[error("Missing API name attribute")]
    MissingAPIName,

    /// Missing name attribute.
    #[error("Missing name attribute")]
    MissingName,

    /// Missing version number attribute.
    #[error("Missing version number attribute")]
    MissingVersionNumber,

    /// API name is not valid UTF-8.
    #[error("API name is not valid UTF-8")]
    APINameNotUTF8,

    /// Name is not valid UTF-8.
    #[error("Name is not valid UTF-8")]
    NameNotUTF8,

    /// Version is not valid UTF-8.
    #[error("Version is not valid UTF-8")]
    VersionNotUTF8,

    /// Deserialization failed.
    #[error("Deserialization failed")]
    DeserializationFailed(#[from] DeserializationError),
}

impl From<DeserializerError> for Error
{
    fn from(err: DeserializerError) -> Self
    {
        DeserializationError(err).into()
    }
}

/// GL API feature requirement.
#[derive(Debug, PartialEq, Eq)]
pub struct Requirement
{
    profile: Option<String>,
    comment: Option<String>,
    features: Vec<Feature>,
}

impl Requirement
{
    /// Returns a new `Requirement`.
    #[must_use]
    pub fn new(
        profile: Option<String>,
        comment: Option<String>,
        features: impl IntoIterator<Item = Feature>,
    ) -> Self
    {
        Self {
            profile,
            comment,
            features: features.into_iter().collect(),
        }
    }

    /// Returns the required features.
    #[must_use]
    pub fn features(&self) -> &[Feature]
    {
        &self.features
    }

    /// Returns the profile.
    #[must_use]
    pub fn profile(&self) -> Option<&str>
    {
        self.profile.as_deref()
    }

    /// Returns the comment.
    #[must_use]
    pub fn comment(&self) -> Option<&str>
    {
        self.comment.as_deref()
    }
}

impl Deserialize for Requirement
{
    type Error = RequirementError;

    fn deserialize<TDeserializer: Deserializer>(
        start: &BytesStart,
        deserializer: &mut TDeserializer,
    ) -> Result<Self, Self::Error>
    {
        let profile = match start
            .try_get_attribute(b"profile")
            .map_err(DeserializerError::ReadFailed)?
        {
            Some(comment_attr) => Some(
                String::from_utf8(comment_attr.value.into_owned())
                    .map_err(|_| RequirementError::ProfileNotUTF8)?,
            ),
            None => None,
        };

        let comment = match start
            .try_get_attribute(b"comment")
            .map_err(DeserializerError::ReadFailed)?
        {
            Some(comment_attr) => Some(
                String::from_utf8(comment_attr.value.into_owned())
                    .map_err(|_| RequirementError::CommentNotUTF8)?,
            ),
            None => None,
        };

        let features = deserializer.de_list::<Feature>()?;

        Ok(Self {
            profile,
            comment,
            features,
        })
    }
}

/// [`Requirement`] error.
#[derive(Debug, thiserror::Error)]
pub enum RequirementError
{
    /// Profile is not valid UTF-8.
    #[error("Profile is not valid UTF-8")]
    ProfileNotUTF8,

    /// Comment is not valid UTF-8.
    #[error("Comment is not valid UTF-8")]
    CommentNotUTF8,

    /// Deserialization failed.
    #[error("Deserialization failed")]
    DeserializationFailed(#[from] DeserializationError),
}

impl From<DeserializerError> for RequirementError
{
    fn from(err: DeserializerError) -> Self
    {
        DeserializationError(err).into()
    }
}

/// GL API feature removal.
#[derive(Debug, PartialEq, Eq)]
pub struct Removal
{
    profile: String,
    comment: String,
    features: Vec<Feature>,
}

impl Removal
{
    /// Returns a new `Removal`.
    #[must_use]
    pub fn new(
        profile: impl Into<String>,
        comment: impl Into<String>,
        features: impl IntoIterator<Item = Feature>,
    ) -> Self
    {
        Self {
            profile: profile.into(),
            comment: comment.into(),
            features: features.into_iter().collect(),
        }
    }

    /// Returns the removed features.
    #[must_use]
    pub fn features(&self) -> &[Feature]
    {
        &self.features
    }

    /// Returns the profile.
    #[must_use]
    pub fn profile(&self) -> &str
    {
        &self.profile
    }

    /// Returns the comment.
    #[must_use]
    pub fn comment(&self) -> &str
    {
        &self.comment
    }
}

impl Deserialize for Removal
{
    type Error = RemovalError;

    fn deserialize<TDeserializer: Deserializer>(
        start: &BytesStart,
        deserializer: &mut TDeserializer,
    ) -> Result<Self, Self::Error>
    {
        let profile = String::from_utf8(
            start
                .try_get_attribute(b"profile")
                .map_err(DeserializerError::ReadFailed)?
                .ok_or_else(|| RemovalError::MissingProfile)?
                .value
                .into_owned(),
        )
        .map_err(|_| RemovalError::ProfileNotUTF8)?;

        let comment = String::from_utf8(
            start
                .try_get_attribute(b"comment")
                .map_err(DeserializerError::ReadFailed)?
                .ok_or_else(|| RemovalError::MissingComment)?
                .value
                .into_owned(),
        )
        .map_err(|_| RemovalError::CommentNotUTF8)?;

        let features = deserializer.de_list::<Feature>()?;

        Ok(Self {
            profile,
            comment,
            features,
        })
    }
}

/// [`Removal`] error.
#[derive(Debug, thiserror::Error)]
pub enum RemovalError
{
    /// Missing profile attribute.
    #[error("Missing profile attribute")]
    MissingProfile,

    /// Missing comment attribute.
    #[error("Missing comment attribute")]
    MissingComment,

    /// Profile is not valid UTF-8.
    #[error("Profile is not valid UTF-8")]
    ProfileNotUTF8,

    /// Comment is not valid UTF-8.
    #[error("Comment is not valid UTF-8")]
    CommentNotUTF8,

    /// Deserialization failed.
    #[error("Deserialization failed")]
    DeserializationFailed(#[from] DeserializationError),
}

impl From<DeserializerError> for RemovalError
{
    fn from(err: DeserializerError) -> Self
    {
        DeserializationError(err).into()
    }
}

/// GL API feature.
#[derive(Debug, PartialEq, Eq)]
pub struct Feature
{
    kind: FeatureKind,
    name: String,
    comment: Option<String>,
}

impl Feature
{
    /// Returns a new `Feature`.
    #[must_use]
    pub fn new(
        kind: FeatureKind,
        name: impl Into<String>,
        comment: Option<String>,
    ) -> Self
    {
        Self {
            kind,
            name: name.into(),
            comment,
        }
    }

    /// Returns the feature kind.
    #[must_use]
    pub fn kind(&self) -> FeatureKind
    {
        self.kind
    }

    /// Returns the feature name.
    #[must_use]
    pub fn name(&self) -> &str
    {
        &self.name
    }

    /// Returns the feature comment.
    #[must_use]
    pub fn comment(&self) -> Option<&str>
    {
        self.comment.as_deref()
    }
}

impl Deserialize for Feature
{
    type Error = FeatureError;

    fn deserialize<TDeserializer: Deserializer>(
        start: &BytesStart,
        _deserializer: &mut TDeserializer,
    ) -> Result<Self, Self::Error>
    {
        let kind = match start.name().as_ref() {
            b"enum" => Ok(FeatureKind::Enum),
            b"command" => Ok(FeatureKind::Command),
            b"type" => Ok(FeatureKind::Type),
            unknown_kind => Err(FeatureError::UnknownFeatureKind(
                String::from_utf8_lossy(unknown_kind).into_owned(),
            )),
        }?;

        let name = String::from_utf8(
            start
                .try_get_attribute(b"name")
                .map_err(DeserializerError::ReadFailed)?
                .ok_or_else(|| FeatureError::MissingName)?
                .value
                .into_owned(),
        )
        .map_err(|_| FeatureError::NameNotUTF8)?;

        let comment = match start
            .try_get_attribute(b"comment")
            .map_err(DeserializerError::ReadFailed)?
        {
            Some(comment_attr) => Some(
                String::from_utf8(comment_attr.value.into_owned())
                    .map_err(|_| FeatureError::CommentNotUTF8)?,
            ),
            None => None,
        };

        Ok(Self {
            kind,
            name,
            comment,
        })
    }
}

/// [`Feature`] error.
#[derive(Debug, thiserror::Error)]
pub enum FeatureError
{
    /// Unknown feature kind.
    #[error("Unknown feature kind {0}")]
    UnknownFeatureKind(String),

    /// Missing name attribute.
    #[error("Missing name attribute")]
    MissingName,

    /// Name is not valid UTF-8.
    #[error("Name is not valid UTF-8")]
    NameNotUTF8,

    /// Comment is not valid UTF-8.
    #[error("Comment is not valid UTF-8")]
    CommentNotUTF8,

    /// Deserialization failed.
    #[error("Deserialization failed")]
    DeserializationFailed(#[from] DeserializationError),
}

impl From<DeserializerError> for FeatureError
{
    fn from(err: DeserializerError) -> Self
    {
        DeserializationError(err).into()
    }
}

/// GL API feature kind.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FeatureKind
{
    /// A enum.
    Enum,

    /// A command.
    Command,

    /// A type.
    Type,
}

#[cfg(test)]
mod tests
{
    use pretty_assertions::{assert_eq, assert_str_eq};

    use super::*;
    use crate::deserialization::MockDeserializer;

    #[test]
    fn deserialize_feature_works()
    {
        let mut mock_deserializer = MockDeserializer::new();

        let commentless_enum_feature = Feature::deserialize(
            &BytesStart::new("enum").with_attributes([("name", "GL_BLEND")]),
            &mut mock_deserializer,
        )
        .expect("Expected Ok");

        assert_eq!(commentless_enum_feature.kind, FeatureKind::Enum);
        assert_str_eq!(commentless_enum_feature.name, "GL_BLEND");
        assert!(commentless_enum_feature.comment.is_none());

        let enum_feature = Feature::deserialize(
            &BytesStart::new("enum").with_attributes([
                ("name", "GL_VERTEX_BINDING_BUFFER"),
                ("comment", "Added in 2013/10/22 update to the spec"),
            ]),
            &mut mock_deserializer,
        )
        .expect("Expected Ok");

        assert_eq!(enum_feature.kind, FeatureKind::Enum);
        assert_str_eq!(enum_feature.name, "GL_VERTEX_BINDING_BUFFER");

        assert_str_eq!(
            enum_feature.comment.expect("Expected Some"),
            "Added in 2013/10/22 update to the spec"
        );

        let command_feature = Feature::deserialize(
            &BytesStart::new("command").with_attributes([("name", "glCreateBuffers")]),
            &mut mock_deserializer,
        )
        .expect("Expected Ok");

        assert_eq!(command_feature.kind, FeatureKind::Command);
        assert_str_eq!(command_feature.name, "glCreateBuffers");
        assert!(command_feature.comment.is_none());

        let type_feature = Feature::deserialize(
            &BytesStart::new("type").with_attributes([("name", "GLbyte")]),
            &mut mock_deserializer,
        )
        .expect("Expected Ok");

        assert_eq!(type_feature.kind, FeatureKind::Type);
        assert_str_eq!(type_feature.name, "GLbyte");
        assert!(type_feature.comment.is_none());

        assert!(matches!(
            Feature::deserialize(&BytesStart::new("foo"), &mut mock_deserializer),
            Err(FeatureError::UnknownFeatureKind(_))
        ));
    }

    #[test]
    fn deserialize_removal_works()
    {
        let mut mock_deserializer = MockDeserializer::new();

        // SAFETY: No argument lifetime is mistreated
        unsafe {
            mock_deserializer.expect_de_list().times(1).returning(|_| {
                Ok(vec![
                    Feature::new(FeatureKind::Command, "glNewList", None),
                    Feature::new(FeatureKind::Command, "glEndList", None),
                ])
            });
        }

        let removal = Removal::deserialize(
            &BytesStart::new("remove").with_attributes([
                ("profile", "core"),
                (
                    "comment",
                    "Compatibility-only GL 1.0 features removed from GL 3.2",
                ),
            ]),
            &mut mock_deserializer,
        )
        .expect("Expected Ok");

        assert_str_eq!(removal.profile, "core");

        assert_str_eq!(
            removal.comment,
            "Compatibility-only GL 1.0 features removed from GL 3.2"
        );

        assert_eq!(removal.features.len(), 2);
    }

    #[test]
    fn deserialize_requirement_works()
    {
        let mut mock_deserializer = MockDeserializer::new();

        // SAFETY: No argument lifetime is mistreated
        unsafe {
            mock_deserializer.expect_de_list().times(1).returning(|_| {
                Ok(vec![
                    Feature::new(FeatureKind::Command, "glNewList", None),
                    Feature::new(FeatureKind::Command, "glEndList", None),
                ])
            });
        }

        // SAFETY: No argument lifetime is mistreated
        unsafe {
            mock_deserializer.expect_de_list().times(1).returning(|_| {
                Ok(vec![
                    Feature::new(FeatureKind::Enum, "GL_COLOR_TABLE", None),
                    Feature::new(FeatureKind::Enum, "GL_MINMAX", None),
                    Feature::new(FeatureKind::Enum, "GL_HISTOGRAM", None),
                ])
            });
        }

        // SAFETY: No argument lifetime is mistreated
        unsafe {
            mock_deserializer.expect_de_list().times(1).returning(|_| {
                Ok(vec![
                    Feature::new(FeatureKind::Enum, "GL_STACK_UNDERFLOW", None),
                    Feature::new(FeatureKind::Enum, "GL_STACK_OVERFLOW", None),
                ])
            });
        }

        let requirement =
            Requirement::deserialize(&BytesStart::new("require"), &mut mock_deserializer)
                .expect("Expected Ok");

        assert!(requirement.profile.is_none());
        assert!(requirement.comment.is_none());

        assert_eq!(requirement.features.len(), 2);

        let requirement = Requirement::deserialize(
            &BytesStart::new("require").with_attributes([("profile", "compatibility")]),
            &mut mock_deserializer,
        )
        .expect("Expected Ok");

        assert_str_eq!(requirement.profile.expect("Expected Some"), "compatibility");
        assert!(requirement.comment.is_none());

        assert_eq!(requirement.features.len(), 3);

        let requirement = Requirement::deserialize(
            &BytesStart::new("require").with_attributes([
                ("profile", "core"),
                (
                    "comment",
                    concat!(
                        "Restore functionality removed in GL 3.2 core ",
                        "to GL 4.3. Needed for debug interface."
                    ),
                ),
            ]),
            &mut mock_deserializer,
        )
        .expect("Expected Ok");

        assert_str_eq!(requirement.profile.expect("Expected Some"), "core");
        assert_str_eq!(
            requirement.comment.expect("Expected Some"),
            concat!(
                "Restore functionality removed in GL 3.2 core to ",
                "GL 4.3. Needed for debug interface."
            )
        );

        assert_eq!(requirement.features.len(), 2);
    }

    #[test]
    fn deserialize_api_interface_definition_works()
    {
        let mut mock_deserializer = MockDeserializer::new();

        // SAFETY: No argument lifetime is mistreated
        unsafe {
            mock_deserializer
                .expect_de_tag_list()
                .times(1)
                .returning(|_, _| {
                    Ok(vec![
                        Requirement::new(
                            None,
                            None,
                            vec![
                                Feature::new(FeatureKind::Command, "glFenceSync", None),
                                Feature::new(FeatureKind::Enum, "GL_WAIT_FAILED", None),
                            ],
                        ),
                        Requirement::new(
                            None,
                            Some("Reuse ARB_sync".to_string()),
                            [Feature::new(FeatureKind::Enum, "GL_OBJECT_TYPE", None)],
                        ),
                    ])
                });
        }

        // SAFETY: No argument lifetime is mistreated
        unsafe {
            mock_deserializer
                .expect_de_tag_list()
                .times(1)
                .returning(|_, _| {
                    Ok(vec![Removal::new(
                        "core",
                        "Compatibility-only GL 1.0 features removed from GL 3.2",
                        vec![Feature::new(FeatureKind::Command, "glBegin", None)],
                    )])
                });
        }

        let api_interface_definition = APIInterfaceDefinition::deserialize(
            &BytesStart::new("feature").with_attributes([
                ("api", "gl"),
                ("name", "GL_VERSION_3_2"),
                ("number", "3.2"),
            ]),
            &mut mock_deserializer,
        )
        .expect("Expected Ok");

        assert_str_eq!(api_interface_definition.api_name, "gl");
        assert_str_eq!(api_interface_definition.name, "GL_VERSION_3_2");
        assert_str_eq!(api_interface_definition.version, "3.2");
        assert_eq!(api_interface_definition.requirements.len(), 2);
        assert_eq!(api_interface_definition.removals.len(), 1);
    }
}