use std::borrow::Cow;
use std::convert::TryFrom;
use std::fmt;

use derive_builder::Builder;
use shorthand::ShortHand;

use crate::attribute::AttributePairs;
use crate::types::{Channels, InStreamId, MediaType, ProtocolVersion};
use crate::utils::{parse_yes_or_no, quote, tag, unquote};
use crate::{Error, RequiredVersion};

/// An [`ExtXMedia`] tag is an alternative rendition of a [`VariantStream`].
///
/// For example an [`ExtXMedia`] tag can be used to specify different audio
/// languages (e.g. english is the default and there also exists an
/// [`ExtXMedia`] stream with a german audio).
///
/// [`MediaPlaylist`]: crate::MediaPlaylist
/// [`VariantStream`]: crate::tags::VariantStream
#[derive(ShortHand, Builder, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[shorthand(enable(must_use, into))]
#[builder(setter(into))]
#[builder(build_fn(validate = "Self::validate"))]
pub struct ExtXMedia<'a> {
    /// The [`MediaType`] associated with this tag.
    ///
    /// ### Note
    ///
    /// This field is required.
    #[shorthand(enable(skip))]
    pub media_type: MediaType,
    /// An `URI` to a [`MediaPlaylist`].
    ///
    /// ### Note
    ///
    /// - This field is required, if the [`ExtXMedia::media_type`] is
    ///   [`MediaType::Subtitles`].
    /// - This field is not allowed, if the [`ExtXMedia::media_type`] is
    /// [`MediaType::ClosedCaptions`].
    ///
    /// An absent value indicates that the media data for this rendition is
    /// included in the [`MediaPlaylist`] of any
    /// [`VariantStream::ExtXStreamInf`] tag with the same `group_id` of
    /// this [`ExtXMedia`] instance.
    ///
    /// [`MediaPlaylist`]: crate::MediaPlaylist
    /// [`VariantStream::ExtXStreamInf`]:
    /// crate::tags::VariantStream::ExtXStreamInf
    #[builder(setter(strip_option), default)]
    uri: Option<Cow<'a, str>>,
    /// The identifier that specifies the group to which the rendition
    /// belongs.
    ///
    /// ### Note
    ///
    /// This field is required.
    group_id: Cow<'a, str>,
    /// The name of the primary language used in the rendition.
    /// The value has to conform to [`RFC5646`].
    ///
    /// ### Note
    ///
    /// This field is optional.
    ///
    /// [`RFC5646`]: https://tools.ietf.org/html/rfc5646
    #[builder(setter(strip_option), default)]
    language: Option<Cow<'a, str>>,
    /// The name of a language associated with the rendition.
    /// An associated language is often used in a different role, than the
    /// language specified by the [`language`] field (e.g., written versus
    /// spoken, or a fallback dialect).
    ///
    /// ### Note
    ///
    /// This field is optional.
    ///
    /// [`language`]: #method.language
    #[builder(setter(strip_option), default)]
    assoc_language: Option<Cow<'a, str>>,
    /// A human-readable description of the rendition.
    ///
    /// ### Note
    ///
    /// This field is required.
    ///
    /// If the [`language`] field is present, this field should be in
    /// that language.
    ///
    /// [`language`]: #method.language
    name: Cow<'a, str>,
    /// The value of the `default` flag.
    /// A value of `true` indicates, that the client should play
    /// this rendition of the content in the absence of information
    /// from the user indicating a different choice.
    ///
    /// ### Note
    ///
    /// This field is optional, its absence indicates an implicit value
    /// of `false`.
    #[builder(default)]
    #[shorthand(enable(skip))]
    pub is_default: bool,
    /// Whether the client may choose to play this rendition in the absence of
    /// explicit user preference.
    ///
    /// ### Note
    ///
    /// This field is optional, its absence indicates an implicit value
    /// of `false`.
    #[builder(default)]
    #[shorthand(enable(skip))]
    pub is_autoselect: bool,
    /// Whether the rendition contains content that is considered
    /// essential to play.
    #[builder(default)]
    #[shorthand(enable(skip))]
    pub is_forced: bool,
    /// An [`InStreamId`] identifies a rendition within the
    /// [`MediaSegment`]s in a [`MediaPlaylist`].
    ///
    /// ### Note
    ///
    /// This field is required, if the media type is
    /// [`MediaType::ClosedCaptions`]. For all other media types the
    /// [`InStreamId`] must not be specified!
    ///
    /// [`MediaPlaylist`]: crate::MediaPlaylist
    /// [`MediaSegment`]: crate::MediaSegment
    #[builder(setter(strip_option), default)]
    #[shorthand(enable(skip))]
    pub instream_id: Option<InStreamId>,
    /// The characteristics field contains one or more Uniform Type
    /// Identifiers ([`UTI`]) separated by a comma.
    /// Each [`UTI`] indicates an individual characteristic of the Rendition.
    ///
    /// An `ExtXMedia` instance with [`MediaType::Subtitles`] may include the
    /// following characteristics:
    /// - `"public.accessibility.transcribes-spoken-dialog"`,
    /// - `"public.accessibility.describes-music-and-sound"`, and
    /// - `"public.easy-to-read"` (which indicates that the subtitles have
    /// been edited for ease of reading).
    ///
    /// An `ExtXMedia` instance with [`MediaType::Audio`] may include the
    /// following characteristic:
    /// - `"public.accessibility.describes-video"`
    ///
    /// The characteristics field may include private UTIs.
    ///
    /// ### Note
    ///
    /// This field is optional.
    ///
    /// [`UTI`]: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#ref-UTI
    #[builder(setter(strip_option), default)]
    characteristics: Option<Cow<'a, str>>,
    /// A count of audio channels indicating the maximum number of independent,
    /// simultaneous audio channels present in any [`MediaSegment`] in the
    /// rendition.
    ///
    /// ### Note
    ///
    /// This field is optional, but every instance of [`ExtXMedia`] with
    /// [`MediaType::Audio`] should have this field. If the [`MasterPlaylist`]
    /// contains two renditions with the same codec, but a different number of
    /// channels, then the channels field is required.
    ///
    /// [`MediaSegment`]: crate::MediaSegment
    /// [`MasterPlaylist`]: crate::MasterPlaylist
    #[builder(setter(strip_option), default)]
    #[shorthand(enable(skip))]
    pub channels: Option<Channels>,
}

impl<'a> ExtXMediaBuilder<'a> {
    fn validate(&self) -> Result<(), String> {
        // A MediaType is always required!
        let media_type = self
            .media_type
            .ok_or_else(|| Error::missing_attribute("MEDIA-TYPE").to_string())?;

        if media_type == MediaType::Subtitles && self.uri.is_none() {
            return Err(Error::missing_attribute("URI").to_string());
        }

        if media_type == MediaType::ClosedCaptions {
            if self.uri.is_some() {
                return Err(Error::unexpected_attribute("URI").to_string());
            }
            if self.instream_id.is_none() {
                return Err(Error::missing_attribute("INSTREAM-ID").to_string());
            }
        } else if self.instream_id.is_some() {
            return Err(Error::custom(
                "InStreamId should only be specified for an ExtXMedia tag with `MediaType::ClosedCaptions`"
            ).to_string());
        }

        if self.is_default.unwrap_or(false) && self.is_autoselect.map_or(false, |b| !b) {
            return Err(Error::custom(format!(
                "If `DEFAULT` is true, `AUTOSELECT` has to be true too, if present. Default: {:?}, Autoselect: {:?}!",
                self.is_default, self.is_autoselect
            ))
            .to_string());
        }

        if media_type != MediaType::Subtitles && self.is_forced.unwrap_or(false) {
            return Err(Error::custom(format!(
                concat!(
                    "the forced attribute must not be present, ",
                    "unless the media_type is `MediaType::Subtitles`: ",
                    "media_type: {:?}, is_forced: {:?}"
                ),
                media_type, self.is_forced
            ))
            .to_string());
        }

        Ok(())
    }
}

impl<'a> ExtXMedia<'a> {
    pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA:";

    /// Makes a new [`ExtXMedia`] tag with the associated [`MediaType`], the
    /// identifier that specifies the group to which the rendition belongs
    /// (group id) and a human-readable description of the rendition. If the
    /// [`language`] is specified it should be in that language.
    ///
    /// # Example
    ///
    /// ```
    /// # use hls_m3u8::tags::ExtXMedia;
    /// use hls_m3u8::types::MediaType;
    ///
    /// let media = ExtXMedia::new(MediaType::Video, "vg1", "1080p video stream");
    /// ```
    ///
    /// [`language`]: #method.language
    #[must_use]
    pub fn new<T, K>(media_type: MediaType, group_id: T, name: K) -> Self
    where
        T: Into<Cow<'a, str>>,
        K: Into<Cow<'a, str>>,
    {
        Self {
            media_type,
            uri: None,
            group_id: group_id.into(),
            language: None,
            assoc_language: None,
            name: name.into(),
            is_default: false,
            is_autoselect: false,
            is_forced: false,
            instream_id: None,
            characteristics: None,
            channels: None,
        }
    }

    /// Returns a builder for [`ExtXMedia`].
    ///
    /// # Example
    ///
    /// ```
    /// # use hls_m3u8::tags::ExtXMedia;
    /// use hls_m3u8::types::MediaType;
    ///
    /// let media = ExtXMedia::builder()
    ///     .media_type(MediaType::Subtitles)
    ///     .uri("french/ed.ttml")
    ///     .group_id("subs")
    ///     .language("fra")
    ///     .assoc_language("fra")
    ///     .name("French")
    ///     .is_autoselect(true)
    ///     .is_forced(true)
    ///     // concat! joins multiple `&'static str`
    ///     .characteristics(concat!(
    ///         "public.accessibility.transcribes-spoken-dialog,",
    ///         "public.accessibility.describes-music-and-sound"
    ///     ))
    ///     .build()?;
    /// # Ok::<(), String>(())
    /// ```
    #[must_use]
    #[inline]
    pub fn builder() -> ExtXMediaBuilder<'a> { ExtXMediaBuilder::default() }

    /// Makes the struct independent of its lifetime, by taking ownership of all
    /// internal [`Cow`]s.
    ///
    /// # Note
    ///
    /// This is a relatively expensive operation.
    #[must_use]
    pub fn into_owned(self) -> ExtXMedia<'static> {
        ExtXMedia {
            media_type: self.media_type,
            uri: self.uri.map(|v| Cow::Owned(v.into_owned())),
            group_id: Cow::Owned(self.group_id.into_owned()),
            language: self.language.map(|v| Cow::Owned(v.into_owned())),
            assoc_language: self.assoc_language.map(|v| Cow::Owned(v.into_owned())),
            name: Cow::Owned(self.name.into_owned()),
            is_default: self.is_default,
            is_autoselect: self.is_autoselect,
            is_forced: self.is_forced,
            instream_id: self.instream_id,
            characteristics: self.characteristics.map(|v| Cow::Owned(v.into_owned())),
            channels: self.channels,
        }
    }
}

/// This tag requires either `ProtocolVersion::V1` or if there is an
/// `instream_id` it requires it's version.
impl<'a> RequiredVersion for ExtXMedia<'a> {
    fn required_version(&self) -> ProtocolVersion {
        self.instream_id
            .map_or(ProtocolVersion::V1, |i| i.required_version())
    }
}

impl<'a> fmt::Display for ExtXMedia<'a> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", Self::PREFIX)?;
        write!(f, "TYPE={}", self.media_type)?;

        if let Some(value) = &self.uri {
            write!(f, ",URI={}", quote(value))?;
        }

        write!(f, ",GROUP-ID={}", quote(&self.group_id))?;

        if let Some(value) = &self.language {
            write!(f, ",LANGUAGE={}", quote(value))?;
        }

        if let Some(value) = &self.assoc_language {
            write!(f, ",ASSOC-LANGUAGE={}", quote(value))?;
        }

        write!(f, ",NAME={}", quote(&self.name))?;

        if self.is_default {
            write!(f, ",DEFAULT=YES")?;
        }

        if self.is_autoselect {
            write!(f, ",AUTOSELECT=YES")?;
        }

        if self.is_forced {
            write!(f, ",FORCED=YES")?;
        }

        if let Some(value) = &self.instream_id {
            write!(f, ",INSTREAM-ID={}", quote(value))?;
        }

        if let Some(value) = &self.characteristics {
            write!(f, ",CHARACTERISTICS={}", quote(value))?;
        }

        if let Some(value) = &self.channels {
            write!(f, ",CHANNELS={}", quote(value))?;
        }
        Ok(())
    }
}

impl<'a> TryFrom<&'a str> for ExtXMedia<'a> {
    type Error = Error;

    fn try_from(input: &'a str) -> Result<Self, Self::Error> {
        let input = tag(input, Self::PREFIX)?;

        let mut builder = Self::builder();

        for (key, value) in AttributePairs::new(input) {
            match key {
                "TYPE" => {
                    builder.media_type(value.parse::<MediaType>()?);
                }
                "URI" => {
                    builder.uri(unquote(value));
                }
                "GROUP-ID" => {
                    builder.group_id(unquote(value));
                }
                "LANGUAGE" => {
                    builder.language(unquote(value));
                }
                "ASSOC-LANGUAGE" => {
                    builder.assoc_language(unquote(value));
                }
                "NAME" => {
                    builder.name(unquote(value));
                }
                "DEFAULT" => {
                    builder.is_default(parse_yes_or_no(value)?);
                }
                "AUTOSELECT" => {
                    builder.is_autoselect(parse_yes_or_no(value)?);
                }
                "FORCED" => {
                    builder.is_forced(parse_yes_or_no(value)?);
                }
                "INSTREAM-ID" => {
                    builder.instream_id(unquote(value).parse::<InStreamId>()?);
                }
                "CHARACTERISTICS" => {
                    builder.characteristics(unquote(value));
                }
                "CHANNELS" => {
                    builder.channels(unquote(value).parse::<Channels>()?);
                }
                _ => {
                    // [6.3.1. General Client Responsibilities]
                    // > ignore any attribute/value pair with an unrecognized
                    // AttributeName.
                }
            }
        }

        builder.build().map_err(Error::builder)
    }
}

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

    macro_rules! generate_tests {
        ( $( { $struct:expr, $str:expr } ),+ $(,)* ) => {
            #[test]
            fn test_display() {
                $(
                    assert_eq!($struct.to_string(), $str.to_string());
                )+
            }

            #[test]
            fn test_parser() {
                $(
                    assert_eq!($struct, TryFrom::try_from($str).unwrap());
                )+
            }
        }
    }

    generate_tests! {
        {
            ExtXMedia::builder()
                .media_type(MediaType::Audio)
                .group_id("audio")
                .language("eng")
                .name("English")
                .is_default(true)
                .uri("eng/prog_index.m3u8")
                .build()
                .unwrap(),
            concat!(
                "#EXT-X-MEDIA:",
                "TYPE=AUDIO,",
                "URI=\"eng/prog_index.m3u8\",",
                "GROUP-ID=\"audio\",",
                "LANGUAGE=\"eng\",",
                "NAME=\"English\",",
                "DEFAULT=YES",
            )
        },
        {
            ExtXMedia::builder()
                .media_type(MediaType::Audio)
                .group_id("audio")
                .language("eng")
                .name("English")
                .is_autoselect(true)
                .is_default(true)
                .uri("eng/prog_index.m3u8")
                .build()
                .unwrap(),
            concat!(
                "#EXT-X-MEDIA:",
                "TYPE=AUDIO,",
                "URI=\"eng/prog_index.m3u8\",",
                "GROUP-ID=\"audio\",",
                "LANGUAGE=\"eng\",",
                "NAME=\"English\",",
                "DEFAULT=YES,",
                "AUTOSELECT=YES"
            )
        },
        {
            ExtXMedia::builder()
                .media_type(MediaType::Audio)
                .uri("fre/prog_index.m3u8")
                .group_id("audio")
                .language("fre")
                .name("Français")
                .is_default(false)
                .is_autoselect(true)
                .build()
                .unwrap(),
            concat!(
                "#EXT-X-MEDIA:",
                "TYPE=AUDIO,",
                "URI=\"fre/prog_index.m3u8\",",
                "GROUP-ID=\"audio\",",
                "LANGUAGE=\"fre\",",
                "NAME=\"Français\",",
                "AUTOSELECT=YES"
            )
        },
        {
            ExtXMedia::builder()
                .media_type(MediaType::Audio)
                .group_id("audio")
                .language("sp")
                .name("Espanol")
                .is_autoselect(true)
                .is_default(false)
                .uri("sp/prog_index.m3u8")
                .build()
                .unwrap(),
            concat!(
                "#EXT-X-MEDIA:",
                "TYPE=AUDIO,",
                "URI=\"sp/prog_index.m3u8\",",
                "GROUP-ID=\"audio\",",
                "LANGUAGE=\"sp\",",
                "NAME=\"Espanol\",",
                "AUTOSELECT=YES"
            )
        },
        {
            ExtXMedia::builder()
                .media_type(MediaType::Audio)
                .group_id("audio-lo")
                .language("eng")
                .name("English")
                .is_autoselect(true)
                .is_default(true)
                .uri("englo/prog_index.m3u8")
                .build()
                .unwrap(),
            concat!(
                "#EXT-X-MEDIA:",
                "TYPE=AUDIO,",
                "URI=\"englo/prog_index.m3u8\",",
                "GROUP-ID=\"audio-lo\",",
                "LANGUAGE=\"eng\",",
                "NAME=\"English\",",
                "DEFAULT=YES,",
                "AUTOSELECT=YES"
            )
        },
        {
            ExtXMedia::builder()
                .media_type(MediaType::Audio)
                .group_id("audio-lo")
                .language("fre")
                .name("Français")
                .is_autoselect(true)
                .is_default(false)
                .uri("frelo/prog_index.m3u8")
                .build()
                .unwrap(),
            concat!(
                "#EXT-X-MEDIA:",
                "TYPE=AUDIO,",
                "URI=\"frelo/prog_index.m3u8\",",
                "GROUP-ID=\"audio-lo\",",
                "LANGUAGE=\"fre\",",
                "NAME=\"Français\",",
                "AUTOSELECT=YES"
            )
        },
        {
            ExtXMedia::builder()
                .media_type(MediaType::Audio)
                .group_id("audio-lo")
                .language("es")
                .name("Espanol")
                .is_autoselect(true)
                .is_default(false)
                .uri("splo/prog_index.m3u8")
                .build()
                .unwrap(),
            concat!(
                "#EXT-X-MEDIA:",
                "TYPE=AUDIO,",
                "URI=\"splo/prog_index.m3u8\",",
                "GROUP-ID=\"audio-lo\",",
                "LANGUAGE=\"es\",",
                "NAME=\"Espanol\",",
                "AUTOSELECT=YES"
            )
        },
        {
            ExtXMedia::builder()
                .media_type(MediaType::Audio)
                .group_id("audio-hi")
                .language("eng")
                .name("English")
                .is_autoselect(true)
                .is_default(true)
                .uri("eng/prog_index.m3u8")
                .build()
                .unwrap(),
            concat!(
                "#EXT-X-MEDIA:",
                "TYPE=AUDIO,",
                "URI=\"eng/prog_index.m3u8\",",
                "GROUP-ID=\"audio-hi\",",
                "LANGUAGE=\"eng\",",
                "NAME=\"English\",",
                "DEFAULT=YES,",
                "AUTOSELECT=YES"
            )
        },
        {
            ExtXMedia::builder()
                .media_type(MediaType::Audio)
                .group_id("audio-hi")
                .language("fre")
                .name("Français")
                .is_autoselect(true)
                .is_default(false)
                .uri("fre/prog_index.m3u8")
                .build()
                .unwrap(),
            concat!(
                "#EXT-X-MEDIA:",
                "TYPE=AUDIO,",
                "URI=\"fre/prog_index.m3u8\",",
                "GROUP-ID=\"audio-hi\",",
                "LANGUAGE=\"fre\",",
                "NAME=\"Français\",",
                "AUTOSELECT=YES"
            )
        },
        {
            ExtXMedia::builder()
                .media_type(MediaType::Audio)
                .group_id("audio-hi")
                .language("es")
                .name("Espanol")
                .is_autoselect(true)
                .is_default(false)
                .uri("sp/prog_index.m3u8")
                .build()
                .unwrap(),
            concat!(
                "#EXT-X-MEDIA:",
                "TYPE=AUDIO,",
                "URI=\"sp/prog_index.m3u8\",",
                "GROUP-ID=\"audio-hi\",",
                "LANGUAGE=\"es\",",
                "NAME=\"Espanol\",",
                "AUTOSELECT=YES"
            )
        },
        {
            ExtXMedia::builder()
                .media_type(MediaType::Audio)
                .group_id("audio-aacl-312")
                .language("en")
                .name("English")
                .is_autoselect(true)
                .is_default(true)
                .channels(Channels::new(2))
                .build()
                .unwrap(),
            concat!(
                "#EXT-X-MEDIA:",
                "TYPE=AUDIO,",
                "GROUP-ID=\"audio-aacl-312\",",
                "LANGUAGE=\"en\",",
                "NAME=\"English\",",
                "DEFAULT=YES,",
                "AUTOSELECT=YES,",
                "CHANNELS=\"2\""
            )
        },
        {
            ExtXMedia::builder()
                .media_type(MediaType::Subtitles)
                .uri("french/ed.ttml")
                .group_id("subs")
                .language("fra")
                .assoc_language("fra")
                .name("French")
                .is_autoselect(true)
                .is_forced(true)
                .characteristics("public.accessibility.transcribes-spoken\
                -dialog,public.accessibility.describes-music-and-sound")
                .build()
                .unwrap(),
            concat!(
                "#EXT-X-MEDIA:",
                "TYPE=SUBTITLES,",
                "URI=\"french/ed.ttml\",",
                "GROUP-ID=\"subs\",",
                "LANGUAGE=\"fra\",",
                "ASSOC-LANGUAGE=\"fra\",",
                "NAME=\"French\",",
                "AUTOSELECT=YES,",
                "FORCED=YES,",
                "CHARACTERISTICS=\"",
                "public.accessibility.transcribes-spoken-dialog,",
                "public.accessibility.describes-music-and-sound",
                "\""
            )
        },
        {
            ExtXMedia::builder()
                .media_type(MediaType::ClosedCaptions)
                .group_id("cc")
                .language("sp")
                .name("CC2")
                .instream_id(InStreamId::Cc2)
                .is_autoselect(true)
                .build()
                .unwrap(),
            concat!(
                "#EXT-X-MEDIA:",
                "TYPE=CLOSED-CAPTIONS,",
                "GROUP-ID=\"cc\",",
                "LANGUAGE=\"sp\",",
                "NAME=\"CC2\",",
                "AUTOSELECT=YES,",
                "INSTREAM-ID=\"CC2\""
            )
        },
        {
            ExtXMedia::new(MediaType::Audio, "foo", "bar"),
            "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"foo\",NAME=\"bar\""
        },
    }

    #[test]
    fn test_parser_error() {
        assert_eq!(ExtXMedia::try_from("").is_err(), true);
        assert_eq!(ExtXMedia::try_from("garbage").is_err(), true);

        assert_eq!(
            ExtXMedia::try_from("#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,URI=\"http://www.example.com\"")
                .is_err(),
            true
        );
        assert_eq!(
            ExtXMedia::try_from("#EXT-X-MEDIA:TYPE=AUDIO,INSTREAM-ID=CC1").is_err(),
            true
        );

        assert_eq!(
            ExtXMedia::try_from("#EXT-X-MEDIA:TYPE=AUDIO,DEFAULT=YES,AUTOSELECT=NO").is_err(),
            true
        );

        assert_eq!(
            ExtXMedia::try_from("#EXT-X-MEDIA:TYPE=AUDIO,FORCED=YES").is_err(),
            true
        );
    }

    #[test]
    fn test_required_version() {
        macro_rules! gen_required_version {
            ( $( $id:expr => $output:expr, )* ) => {
                $(
                    assert_eq!(
                        ExtXMedia::builder()
                            .media_type(MediaType::ClosedCaptions)
                            .group_id("audio")
                            .name("English")
                            .instream_id($id)
                            .build()
                            .unwrap()
                            .required_version(),
                        $output
                    );
                )*
            }
        }

        gen_required_version![
            InStreamId::Cc1 => ProtocolVersion::V1,
            InStreamId::Cc2 => ProtocolVersion::V1,
            InStreamId::Cc3 => ProtocolVersion::V1,
            InStreamId::Cc4 => ProtocolVersion::V1,
            InStreamId::Service1 => ProtocolVersion::V7,
        ];

        assert_eq!(
            ExtXMedia::builder()
                .media_type(MediaType::Audio)
                .group_id("audio")
                .name("English")
                .build()
                .unwrap()
                .required_version(),
            ProtocolVersion::V1
        );
    }
}
