// Take a look at the license at the top of the repository in the LICENSE file.

#![allow(clippy::upper_case_acronyms)]

use std::{cell::RefCell, cmp, fmt, rc::Rc};

use glib::{
    Date,
    translate::{ToGlibPtr, from_glib},
    value::{SendValue, ToValue},
};
use serde::{
    de,
    de::{Deserialize, DeserializeSeed, Deserializer, SeqAccess, Visitor},
    ser,
    ser::{Serialize, SerializeSeq, SerializeStruct, SerializeTuple, Serializer},
};

use crate::{
    DateTime, Sample, TagMergeMode, TagScope, date_time_serde,
    tags::{GenericTagIter, TagList, TagListRef},
    value_serde::{DATE_OTHER_TYPE_ID, DATE_TIME_OTHER_TYPE_ID, SAMPLE_OTHER_TYPE_ID},
};

macro_rules! ser_tag (
    ($value:ident, $seq:ident, $t:ty) => (
        ser_some_value!($value, $t, |_, value| {
            $seq.serialize_element(&value)
        })
    );
);

macro_rules! ser_opt_tag (
    ($value:ident, $seq:ident, $t:ty) => (
        ser_opt_value!($value, $t, |_, value| {
            $seq.serialize_element(&value)
        })
    );
);

// Note: unlike `Value`s, `Tag`s with  optional `Type` `String` & `Date` values are guaranteed
// to be Non-null and non-empty in the C API. See:
// https://gitlab.freedesktop.org/gstreamer/gstreamer/blob/d90d771a9a512381315f7694c3a50b152035f3cb/gst/gststructure.c#L810-853

// serialize trait is only available for `&self`, but we need to mutate the iterator
struct TagValuesSer<'a>(Rc<RefCell<GenericTagIter<'a>>>);
impl<'a> TagValuesSer<'a> {
    fn from(tags_ser: &TagsSer<'a>) -> Self {
        skip_assert_initialized!();
        TagValuesSer(Rc::clone(&tags_ser.1))
    }
}

impl Serialize for TagValuesSer<'_> {
    #[allow(clippy::redundant_closure_call)]
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        use std::ops::DerefMut;

        let mut tag_iter = self.0.borrow_mut();
        let mut seq = serializer.serialize_seq(tag_iter.size_hint().1)?;
        for value in tag_iter.deref_mut() {
            match value.type_() {
                glib::Type::F64 => ser_tag!(value, seq, f64),
                glib::Type::STRING => {
                    // See above comment about `Tag`s with `String` values
                    ser_some_value!(value, String, |_, value: String| {
                        seq.serialize_element(&value)
                    })
                }
                glib::Type::U32 => ser_tag!(value, seq, u32),
                glib::Type::U64 => ser_tag!(value, seq, u64),
                type_id => {
                    if *DATE_OTHER_TYPE_ID == type_id {
                        // See above comment about `Tag`s with `Date` values
                        ser_some_value!(value, Date, |_, value: Date| {
                            // Need to wrap the `glib::Date` in new type `date_time_serde::Date` first
                            // See comment in `date_time_serde.rs`
                            seq.serialize_element(&date_time_serde::Date::from(value))
                        })
                    } else if *DATE_TIME_OTHER_TYPE_ID == type_id {
                        ser_opt_tag!(value, seq, DateTime)
                    } else if *SAMPLE_OTHER_TYPE_ID == type_id {
                        ser_opt_tag!(value, seq, Sample)
                    } else {
                        Err(ser::Error::custom(format!(
                            "unimplemented `Tag` serialization for type {type_id}",
                        )))
                    }
                }
            }?;
        }
        seq.end()
    }
}

struct TagsSer<'a>(&'a str, Rc<RefCell<GenericTagIter<'a>>>);
impl<'a> TagsSer<'a> {
    fn new(name: &'a str, tag_iter: GenericTagIter<'a>) -> Self {
        skip_assert_initialized!();
        TagsSer(name, Rc::new(RefCell::new(tag_iter)))
    }
}

impl Serialize for TagsSer<'_> {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let mut tup = serializer.serialize_tuple(2)?;
        tup.serialize_element(self.0)?;
        tup.serialize_element(&TagValuesSer::from(self))?;
        tup.end()
    }
}

struct TagListSer<'a>(&'a TagListRef);
impl Serialize for TagListSer<'_> {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let tag_count = self.0.n_tags();
        match tag_count.cmp(&0) {
            cmp::Ordering::Greater => {
                let mut seq = serializer.serialize_seq(Some(tag_count))?;
                let tag_list_iter = self.0.iter_generic();
                for (tag_name, tag_iter) in tag_list_iter {
                    seq.serialize_element(&TagsSer::new(tag_name, tag_iter))?;
                }
                seq.end()
            }
            cmp::Ordering::Equal => {
                let seq = serializer.serialize_seq(None)?;
                seq.end()
            }
            cmp::Ordering::Less => Err(ser::Error::custom("tag count < 0")),
        }
    }
}

impl Serialize for TagListRef {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let mut tag_list = serializer.serialize_struct("TagList", 3)?;
        tag_list.serialize_field("scope", &self.scope())?;
        tag_list.serialize_field("tags", &TagListSer(self))?;
        tag_list.end()
    }
}

impl Serialize for TagList {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        self.as_ref().serialize(serializer)
    }
}

macro_rules! de_tag(
    ($tag_name:expr, $seq:expr, $t:ty) => (
        de_some_send_value!("Tag", $tag_name, $seq, $t)
    );
);
macro_rules! de_opt_tag(
    ($tag_name:expr, $seq:expr, $t:ty) => (
        de_opt_send_value!("Tag", $tag_name, $seq, $t)
    );
);

struct TagValues<'a>(&'a str, &'a mut TagListRef);

struct TagValuesVisitor<'a>(&'a str, &'a mut TagListRef);
impl<'de> Visitor<'de> for TagValuesVisitor<'_> {
    type Value = ();

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a sequence of `Tag` values with the same type")
    }

    fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<(), A::Error> {
        let tag_type: glib::Type = unsafe {
            let tag_name = self.0.to_glib_none();
            from_glib(crate::ffi::gst_tag_get_type(tag_name.0))
        };

        loop {
            let tag_value = match tag_type {
                glib::Type::F64 => de_tag!(self.0, seq, f64),
                glib::Type::STRING => {
                    // See comment above `TagValuesSer` definition about `Tag`s with `String` values
                    de_tag!(self.0, seq, String)
                }
                glib::Type::U32 => de_tag!(self.0, seq, u32),
                glib::Type::U64 => de_tag!(self.0, seq, u64),
                type_id => {
                    if *DATE_OTHER_TYPE_ID == type_id {
                        // See comment above `TagValuesSer` definition about `Tag`s with `Date` values
                        // Need to deserialize as `date_time_serde::Date` new type
                        // See comment in `date_time_serde.rs`
                        de_send_value!("Tag", self.0, seq, date_time_serde::Date, Date)
                    } else if *DATE_TIME_OTHER_TYPE_ID == type_id {
                        de_opt_tag!(self.0, seq, DateTime)
                    } else if *SAMPLE_OTHER_TYPE_ID == type_id {
                        de_opt_tag!(self.0, seq, Sample)
                    } else {
                        return Err(de::Error::custom(format!(
                            "unimplemented deserialization for `Tag` {} with type `{}`",
                            self.0, type_id,
                        )));
                    }
                }
            }?;

            match tag_value {
                Some(tag_value) => self
                    .1
                    .add_value(self.0, &tag_value, TagMergeMode::Append)
                    .map_err(|_| {
                        de::Error::custom(format!("wrong value type for `Tag` {}", self.0))
                    })?,
                None => break,
            }
        }

        Ok(())
    }
}

impl<'de> DeserializeSeed<'de> for TagValues<'_> {
    type Value = ();

    fn deserialize<D: Deserializer<'de>>(self, deserializer: D) -> Result<(), D::Error> {
        skip_assert_initialized!();
        deserializer.deserialize_seq(TagValuesVisitor(self.0, self.1))
    }
}

struct TagValuesTuple<'a>(&'a mut TagListRef);

struct TagValuesTupleVisitor<'a>(&'a mut TagListRef);
impl<'de> Visitor<'de> for TagValuesTupleVisitor<'_> {
    type Value = ();

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter
            .write_str("a tuple (`Tag` name: `String`, seq. of `Tag` values with the same type)")
    }

    fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<(), A::Error> {
        let name = seq
            .next_element::<String>()
            .map_err(|err| de::Error::custom(format!("Error reading Tag name. {err:?}")))?
            .ok_or_else(|| de::Error::custom("Expected a name for the `Tag` name"))?;
        seq.next_element_seed(TagValues(name.as_str(), self.0))?
            .ok_or_else(|| de::Error::custom("Expected a seq of values for the `Tag`"))
    }
}

impl<'de> DeserializeSeed<'de> for TagValuesTuple<'_> {
    type Value = ();

    fn deserialize<D: Deserializer<'de>>(self, deserializer: D) -> Result<(), D::Error> {
        skip_assert_initialized!();
        deserializer.deserialize_tuple(2, TagValuesTupleVisitor(self.0))
    }
}

struct TagsDe(TagList);

struct TagsVisitor;
impl<'de> Visitor<'de> for TagsVisitor {
    type Value = TagsDe;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a sequence of `Tag`s")
    }

    fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
        let mut tag_list = TagList::new();
        {
            let tag_list = tag_list.get_mut().unwrap();
            while seq.next_element_seed(TagValuesTuple(tag_list))?.is_some() {
                // tags are added in the dedicated deserializers
            }
        }
        Ok(TagsDe(tag_list))
    }
}

impl<'de> Deserialize<'de> for TagsDe {
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        skip_assert_initialized!();
        deserializer.deserialize_seq(TagsVisitor)
    }
}

#[derive(serde::Deserialize)]
struct TagListDe {
    scope: TagScope,
    tags: TagsDe,
}

impl From<TagListDe> for TagList {
    fn from(tag_list_de: TagListDe) -> Self {
        skip_assert_initialized!();
        let mut tag_list = tag_list_de.tags.0;
        tag_list.get_mut().unwrap().set_scope(tag_list_de.scope);

        tag_list
    }
}

impl<'de> Deserialize<'de> for TagList {
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        skip_assert_initialized!();
        TagListDe::deserialize(deserializer).map(|tag_list_de| tag_list_de.into())
    }
}

#[cfg(test)]
mod tests {
    use crate::{Buffer, ClockTime, Sample, TagMergeMode, TagScope, tags::*};

    #[test]
    fn test_serialize() {
        crate::init().unwrap();

        let mut tags = TagList::new();
        assert_eq!(tags.to_string(), "taglist;");
        {
            let tags = tags.get_mut().unwrap();
            tags.add::<Title>(&"a title", TagMergeMode::Append); // String
            tags.add::<Title>(&"another title", TagMergeMode::Append); // String
            tags.add::<Duration>(&(ClockTime::SECOND * 120), TagMergeMode::Append); // u64
            tags.add::<Bitrate>(&96_000, TagMergeMode::Append); // u32
            tags.add::<TrackGain>(&1f64, TagMergeMode::Append); // f64
            tags.add::<Date>(
                &glib::Date::from_dmy(28, glib::DateMonth::May, 2018).unwrap(),
                TagMergeMode::Append,
            );
            tags.add::<DateTime>(
                &crate::DateTime::from_ymd(2018, 5, 28).unwrap(),
                TagMergeMode::Append,
            );

            let sample = {
                let mut buffer = Buffer::from_slice(vec![1, 2, 3, 4]);
                {
                    let buffer = buffer.get_mut().unwrap();
                    buffer.set_offset(0);
                    buffer.set_offset_end(0);
                }
                Sample::builder().buffer(&buffer).build()
            };
            tags.add::<Image>(&sample, TagMergeMode::Append); // Sample
        }

        let pretty_config = ron::ser::PrettyConfig::new().new_line("".to_string());

        let res = ron::ser::to_string_pretty(&tags, pretty_config);
        assert_eq!(
            Ok(concat!(
                r#"("#,
                r#"    scope: Stream,"#,
                r#"    tags: ["#,
                r#"        ("title", ["#,
                r#"            "a title","#,
                r#"            "another title","#,
                r#"        ]),"#,
                r#"        ("duration", ["#,
                r#"            120000000000,"#,
                r#"        ]),"#,
                r#"        ("bitrate", ["#,
                r#"            96000,"#,
                r#"        ]),"#,
                r#"        ("replaygain-track-gain", ["#,
                r#"            1.0,"#,
                r#"        ]),"#,
                r#"        ("date", ["#,
                r#"            YMD(2018, 5, 28),"#,
                r#"        ]),"#,
                r#"        ("datetime", ["#,
                r#"            Some(YMD(2018, 5, 28)),"#,
                r#"        ]),"#,
                r#"        ("image", ["#,
                r#"            Some(("#,
                r#"                buffer: Some(("#,
                r#"                    pts: None,"#,
                r#"                    dts: None,"#,
                r#"                    duration: None,"#,
                r#"                    offset: 0,"#,
                r#"                    offset_end: 0,"#,
                r#"                    flags: "","#,
                r#"                    buffer: b"\x01\x02\x03\x04","#,
                r#"                )),"#,
                r#"                buffer_list: None,"#,
                r#"                caps: None,"#,
                r#"                segment: Some(("#,
                r#"                    flags: "","#,
                r#"                    rate: 1.0,"#,
                r#"                    applied_rate: 1.0,"#,
                r#"                    format: Time,"#,
                r#"                    base: 0,"#,
                r#"                    offset: 0,"#,
                r#"                    start: 0,"#,
                r#"                    stop: -1,"#,
                r#"                    time: 0,"#,
                r#"                    position: 0,"#,
                r#"                    duration: -1,"#,
                r#"                )),"#,
                r#"                info: None,"#,
                r#"            )),"#,
                r#"        ]),"#,
                r#"    ],"#,
                r#")"#,
            )
            .to_owned()),
            res,
        );
    }

    #[test]
    fn test_deserialize() {
        crate::init().unwrap();

        let tag_list_ron = r#"
            (
                scope: Global,
                tags: [
                    ("title", [
                        "a title",
                        "another title",
                    ]),
                    ("duration", [120000000000]),
                    ("bitrate", [96000]),
                    ("replaygain-track-gain", [1.0]),
                    ("date", [
                        YMD(2018, 5, 28),
                    ]),
                    ("datetime", [
                        Some(YMD(2018, 5, 28)),
                    ]),
                    ("image", [
                        Some((
                            buffer: Some((
                                pts: None,
                                dts: None,
                                duration: None,
                                offset: 0,
                                offset_end: 0,
                                flags: "",
                                buffer: b"\x01\x02\x03\x04",
                            )),
                            buffer_list: None,
                            caps: None,
                            segment: None,
                            info: None,
                        )),
                    ])
                ],
            )
        "#;
        let tags: TagList = ron::de::from_str(tag_list_ron).unwrap();
        assert_eq!(tags.scope(), TagScope::Global);

        assert_eq!(tags.index::<Title>(0).unwrap().get(), "a title");
        assert_eq!(tags.index::<Title>(1).unwrap().get(), "another title");
        assert_eq!(tags.index::<Title>(1).unwrap().get(), "another title");
        assert_eq!(
            tags.index::<Duration>(0).unwrap().get(),
            120 * ClockTime::SECOND,
        );
        assert_eq!(tags.index::<Bitrate>(0).unwrap().get(), 96_000);
        assert!((tags.index::<TrackGain>(0).unwrap().get() - 1f64).abs() < f64::EPSILON);
        assert_eq!(
            tags.index::<Date>(0).unwrap().get(),
            glib::Date::from_dmy(28, glib::DateMonth::May, 2018).unwrap()
        );
        assert_eq!(
            tags.index::<DateTime>(0).unwrap().get(),
            crate::DateTime::from_ymd(2018, 5, 28).unwrap()
        );
        let sample = tags.index::<Image>(0).unwrap().get();
        let buffer = sample.buffer().unwrap();
        {
            let data = buffer.map_readable().unwrap();
            assert_eq!(data.as_slice(), vec![1, 2, 3, 4].as_slice());
        }

        let tag_json = r#"
            {
                "scope":"Global",
                "tags":[
                    ["title", ["a title", "another title"]],
                    ["duration", [120000000000]],
                    ["bitrate", [96000]],
                    ["replaygain-track-gain", [1]],
                    ["date",[{"YMD":[2018,5,28]}]],
                    ["datetime",[{"YMD":[2018,5,28]}]],
                    ["image",[{"buffer":{"pts":null,"dts":null,"duration":null,"offset":0,"offset_end":0,"flags":"","buffer":[1,2,3,4]},"buffer_list":null,"caps":null,"segment":null,"info":null}]]
                ]
            }
        "#;
        let tags: TagList = serde_json::from_str(tag_json).unwrap();
        assert_eq!(tags.scope(), TagScope::Global);

        assert_eq!(tags.index::<Title>(0).unwrap().get(), "a title");
        assert_eq!(tags.index::<Title>(1).unwrap().get(), "another title");
        assert_eq!(tags.index::<Bitrate>(0).unwrap().get(), 96_000);
        assert!((tags.index::<TrackGain>(0).unwrap().get() - 1f64).abs() < f64::EPSILON);
        assert_eq!(
            tags.index::<Date>(0).unwrap().get(),
            glib::Date::from_dmy(28, glib::DateMonth::May, 2018).unwrap()
        );
        assert_eq!(
            tags.index::<DateTime>(0).unwrap().get(),
            crate::DateTime::from_ymd(2018, 5, 28).unwrap()
        );
        let sample = tags.index::<Image>(0).unwrap().get();
        let buffer = sample.buffer().unwrap();
        {
            let data = buffer.map_readable().unwrap();
            assert_eq!(data.as_slice(), vec![1, 2, 3, 4].as_slice());
        }
    }

    #[test]
    fn test_serde_roundtrip() {
        crate::init().unwrap();

        let mut tags = TagList::new();
        assert_eq!(tags.to_string(), "taglist;");
        {
            let tags = tags.get_mut().unwrap();
            tags.set_scope(TagScope::Global);
            tags.add::<Title>(&"a title", TagMergeMode::Append); // String
            tags.add::<Title>(&"another title", TagMergeMode::Append); // String
            tags.add::<Duration>(&(ClockTime::SECOND * 120), TagMergeMode::Append); // u64
            tags.add::<Bitrate>(&96_000, TagMergeMode::Append); // u32
            tags.add::<TrackGain>(&1f64, TagMergeMode::Append); // f64
            tags.add::<Date>(
                &glib::Date::from_dmy(28, glib::DateMonth::May, 2018).unwrap(),
                TagMergeMode::Append,
            );
            tags.add::<DateTime>(
                &crate::DateTime::from_ymd(2018, 5, 28).unwrap(),
                TagMergeMode::Append,
            );

            let sample = {
                let mut buffer = Buffer::from_slice(vec![1, 2, 3, 4]);
                {
                    let buffer = buffer.get_mut().unwrap();
                    buffer.set_offset(0);
                    buffer.set_offset_end(0);
                }
                Sample::builder().buffer(&buffer).build()
            };
            tags.add::<Image>(&sample, TagMergeMode::Append); // Sample
        }
        let tags_ser = ron::ser::to_string(&tags).unwrap();

        let tags_de: TagList = ron::de::from_str(tags_ser.as_str()).unwrap();
        assert_eq!(tags_de.scope(), TagScope::Global);

        assert_eq!(
            tags_de.index::<Title>(0).unwrap().get(),
            tags.index::<Title>(0).unwrap().get(),
        );
        assert_eq!(
            tags_de.index::<Title>(1).unwrap().get(),
            tags.index::<Title>(1).unwrap().get(),
        );
        assert_eq!(
            tags_de.index::<Duration>(0).unwrap().get(),
            tags.index::<Duration>(0).unwrap().get(),
        );
        assert_eq!(
            tags_de.index::<Bitrate>(0).unwrap().get(),
            tags.index::<Bitrate>(0).unwrap().get(),
        );
        assert!(
            (tags_de.index::<TrackGain>(0).unwrap().get()
                - tags.index::<TrackGain>(0).unwrap().get())
            .abs()
                < f64::EPSILON
        );
        assert_eq!(
            tags_de.index::<Date>(0).unwrap().get(),
            tags.index::<Date>(0).unwrap().get(),
        );
        assert_eq!(
            tags.index::<DateTime>(0).unwrap().get(),
            crate::DateTime::from_ymd(2018, 5, 28).unwrap()
        );
        let sample = tags.index::<Image>(0).unwrap().get();
        let buffer = sample.buffer().unwrap();
        {
            let data = buffer.map_readable().unwrap();
            assert_eq!(data.as_slice(), vec![1, 2, 3, 4].as_slice());
        }
    }
}
