use std::{
    fmt::{self, Formatter},
    marker::PhantomData,
};

use serde::{
    de::{MapAccess, SeqAccess, Visitor},
    Deserialize, Deserializer,
};

use serde::ser::SerializeStruct;

use bitflags::bitflags;

bitflags! {
    #[repr(transparent)]
    #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
    pub struct BatchFlags: u16 {
        const FILENAME = 1;
        const DESCRIPTION = 1 << 1;
        const AUTHOR = 1 << 2;
        const COMMENT = 1 << 3;
        const URL = 1 << 4;
        const DATE = 1 << 5;
        const PROCESSED = 1 << 6;
        const REDACTED = 1 << 7;
        const COPYRIGHT = 1 << 8;
        const THEME = 1 << 9;
        const KIND = 1 << 10;
        const SOURCE = 1 << 11;
        const RATING = 1 << 12;
    }
}

bitflags! {
    #[repr(transparent)]
    #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
    pub struct QuestionFlags: u16 {
        const NUM = 1;
        const AUTHOR = 1 << 1;
        const COMMENT = 1 << 2;
        const COMMENT1 = 1 << 3;
        const TOUR = 1 << 4;
        const URL = 1 << 5;
        const DATE = 1 << 6;
        const PROCESSED_BY = 1 << 7;
        const REDACTED_BY = 1 << 8;
        const COPYRIGHT = 1 << 9;
        const THEME = 1 << 10;
        const KIND = 1 << 11;
        const SOURCE = 1 << 12;
        const RATING = 1 << 13;
        const BATCH_INFO = 1 << 14;
    }
}

#[derive(Debug, Default, Clone, PartialEq)]
pub struct BatchInfo {
    pub filename: String,
    pub description: String,
    pub author: String,
    pub comment: String,
    pub url: String,
    pub date: String,
    pub processed_by: String,
    pub redacted_by: String,
    pub copyright: String,
    pub theme: String,
    pub kind: String,
    pub source: String,
    pub rating: String,
}

#[derive(Debug, Default, Clone, PartialEq)]
pub struct Question {
    pub num: u32,

    // required fields
    pub id: String,
    pub description: String,
    pub answer: String,

    pub author: String,
    pub comment: String,
    pub comment1: String,
    pub tour: String,
    pub url: String,
    pub date: String,
    pub processed_by: String,
    pub redacted_by: String,
    pub copyright: String,
    pub theme: String,
    pub kind: String,
    pub source: String,
    pub rating: String,
    pub batch_info: BatchInfo,
}

impl BatchInfo {
    pub fn is_default(&self) -> bool {
        *self == BatchInfo::default()
    }
}

macro_rules! flags_from_strings {
    (($self:ident, $flags:ident, $FlagsType:ident) <- {$($field:ident:$flag:ident),+} ) => {$(
        if !$self.$field.is_empty() {
            $flags |= $FlagsType::$flag;
        }
    )+}
}

macro_rules! serialize_fields {
    (($self:ident, $flags:ident, $state:ident, $FlagsType:ident) <- {$($field:ident:$flag:ident),+} ) => {$(
        if $flags.intersects($FlagsType::$flag) {
            $state.serialize_field(std::stringify!($field), &$self.$field)?;
        } else {
            $state.skip_field(std::stringify!($field))?;
        }
    )+}
}

impl serde::Serialize for BatchInfo {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let is_human_readable = serializer.is_human_readable();
        let mut flags: BatchFlags = Default::default();
        let len = 13;

        flags_from_strings!((self, flags, BatchFlags) <- {
            filename: FILENAME, description: DESCRIPTION, author: AUTHOR, comment: COMMENT, url: URL, date: DATE, processed_by: PROCESSED,
            redacted_by: REDACTED, copyright: COPYRIGHT, theme: THEME, kind: KIND, source: SOURCE, rating: RATING
        });

        let mut state = serializer.serialize_struct("BatchInfo", len)?;

        if is_human_readable {
            state.skip_field("_flags")?;
        } else {
            state.serialize_field("_flags", &flags.bits())?;
        }

        serialize_fields!((self, flags, state, BatchFlags) <- {
            filename: FILENAME, description: DESCRIPTION, author: AUTHOR, comment: COMMENT, url: URL, date: DATE, processed_by: PROCESSED,
            redacted_by: REDACTED, copyright: COPYRIGHT, theme: THEME, kind: KIND, source: SOURCE, rating: RATING
        });

        state.end()
    }
}

impl serde::Serialize for Question {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let is_human_readable = serializer.is_human_readable();
        let mut flags: QuestionFlags = Default::default();
        let len = 19;

        if self.num != 0 {
            flags |= QuestionFlags::NUM;
        }

        if !self.batch_info.is_default() {
            flags |= QuestionFlags::BATCH_INFO;
        }

        flags_from_strings!((self, flags, QuestionFlags) <- {
            author: AUTHOR, comment: COMMENT, comment1: COMMENT1, tour: TOUR, url: URL, date: DATE,
            processed_by: PROCESSED_BY, redacted_by: REDACTED_BY, copyright: COPYRIGHT, theme: THEME,
            kind: KIND, source: SOURCE, rating: RATING
        });

        let mut state = serializer.serialize_struct("Question", len)?;

        if is_human_readable {
            state.skip_field("_flags")?;
        } else {
            state.serialize_field("_flags", &flags.bits())?;
        }

        if flags.intersects(QuestionFlags::NUM) {
            state.serialize_field("num", &self.num)?;
        } else {
            state.skip_field("num")?;
        }

        state.serialize_field("id", &self.id)?;
        state.serialize_field("description", &self.description)?;
        state.serialize_field("answer", &self.answer)?;

        serialize_fields!((self, flags, state, QuestionFlags) <- {
            author: AUTHOR, comment: COMMENT, comment1: COMMENT1, tour: TOUR, url: URL, date: DATE,
            processed_by: PROCESSED_BY, redacted_by: REDACTED_BY, copyright: COPYRIGHT, theme: THEME,
            kind: KIND, source: SOURCE, rating: RATING, batch_info: BATCH_INFO
        });

        if flags.intersects(QuestionFlags::BATCH_INFO) {
            state.serialize_field("batch_info", &self.batch_info)?;
        } else {
            state.skip_field("batch_info")?;
        }

        state.end()
    }
}

impl<'de> Deserialize<'de> for BatchInfo {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[allow(non_camel_case_types)]
        enum BatchField {
            filename,
            description,
            author,
            comment,
            url,
            date,
            processed_by,
            redacted_by,
            copyright,
            theme,
            kind,
            source,
            rating,
            _flags,
            __ignore,
        }

        struct BatchFieldVisitor;
        impl<'de> Visitor<'de> for BatchFieldVisitor {
            type Value = BatchField;
            fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
                Formatter::write_str(formatter, "field identifier")
            }
            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                match value {
                    0u64 => Ok(BatchField::filename),
                    1u64 => Ok(BatchField::description),
                    2u64 => Ok(BatchField::author),
                    3u64 => Ok(BatchField::comment),
                    4u64 => Ok(BatchField::url),
                    5u64 => Ok(BatchField::date),
                    6u64 => Ok(BatchField::processed_by),
                    7u64 => Ok(BatchField::redacted_by),
                    8u64 => Ok(BatchField::copyright),
                    9u64 => Ok(BatchField::theme),
                    10u64 => Ok(BatchField::kind),
                    11u64 => Ok(BatchField::source),
                    12u64 => Ok(BatchField::rating),
                    13u64 => Ok(BatchField::_flags),
                    _ => Ok(BatchField::__ignore),
                }
            }
            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                match value {
                    "filename" => Ok(BatchField::filename),
                    "description" => Ok(BatchField::description),
                    "author" => Ok(BatchField::author),
                    "comment" => Ok(BatchField::comment),
                    "url" => Ok(BatchField::url),
                    "date" => Ok(BatchField::date),
                    "processed_by" => Ok(BatchField::processed_by),
                    "redacted_by" => Ok(BatchField::redacted_by),
                    "copyright" => Ok(BatchField::copyright),
                    "theme" => Ok(BatchField::theme),
                    "kind" => Ok(BatchField::kind),
                    "source" => Ok(BatchField::source),
                    "rating" => Ok(BatchField::rating),
                    "_flags" => Ok(BatchField::_flags),
                    _ => Ok(BatchField::__ignore),
                }
            }
            fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                match value {
                    b"filename" => Ok(BatchField::filename),
                    b"description" => Ok(BatchField::description),
                    b"author" => Ok(BatchField::author),
                    b"comment" => Ok(BatchField::comment),
                    b"url" => Ok(BatchField::url),
                    b"date" => Ok(BatchField::date),
                    b"processed_by" => Ok(BatchField::processed_by),
                    b"redacted_by" => Ok(BatchField::redacted_by),
                    b"copyright" => Ok(BatchField::copyright),
                    b"theme" => Ok(BatchField::theme),
                    b"kind" => Ok(BatchField::kind),
                    b"source" => Ok(BatchField::source),
                    b"rating" => Ok(BatchField::rating),
                    b"_flags" => Ok(BatchField::_flags),
                    _ => Ok(BatchField::__ignore),
                }
            }
        }
        impl<'de> Deserialize<'de> for BatchField {
            #[inline]
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
            where
                D: Deserializer<'de>,
            {
                Deserializer::deserialize_identifier(deserializer, BatchFieldVisitor)
            }
        }
        struct BatchInfoVisitor<'de> {
            marker: PhantomData<BatchInfo>,
            lifetime: PhantomData<&'de ()>,
        }
        impl<'de> Visitor<'de> for BatchInfoVisitor<'de> {
            type Value = BatchInfo;
            fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
                Formatter::write_str(formatter, "struct BatchInfo")
            }
            #[inline]
            fn visit_seq<V>(self, mut seq: V) -> Result<Self::Value, V::Error>
            where
                V: SeqAccess<'de>,
            {
                let flags = BatchFlags::from_bits(
                    SeqAccess::next_element::<u16>(&mut seq)?.unwrap_or_default(),
                )
                .unwrap_or_default();

                macro_rules! seq_read_strings {
                    (($flags:ident, $seq:ident, $FlagsType:ident) <- {$($field:ident:$flag:ident),+} ) => {$(
                        let $field: String = if $flags.intersects($FlagsType::$flag) {
                            SeqAccess::next_element::<String>(&mut $seq)?.unwrap_or_default()
                        } else {
                            Default::default()
                        };
                    )+}
                }

                seq_read_strings!((flags, seq, BatchFlags) <- {
                    filename: FILENAME, description: DESCRIPTION, author: AUTHOR, comment: COMMENT, url: URL, date: DATE, processed_by: PROCESSED,
                    redacted_by: REDACTED, copyright: COPYRIGHT, theme: THEME, kind: KIND, source: SOURCE, rating: RATING
                });

                Ok(BatchInfo {
                    filename,
                    description,
                    author,
                    comment,
                    url,
                    date,
                    processed_by,
                    redacted_by,
                    copyright,
                    theme,
                    kind,
                    source,
                    rating,
                })
            }
            #[inline]
            fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
            where
                V: MapAccess<'de>,
            {
                let mut filename: Option<String> = None;
                let mut description: Option<String> = None;
                let mut author: Option<String> = None;
                let mut comment: Option<String> = None;
                let mut url: Option<String> = None;
                let mut date: Option<String> = None;
                let mut processed_by: Option<String> = None;
                let mut redacted_by: Option<String> = None;
                let mut copyright: Option<String> = None;
                let mut theme: Option<String> = None;
                let mut kind: Option<String> = None;
                let mut source: Option<String> = None;
                let mut rating: Option<String> = None;

                macro_rules! match_map_fields {
                    (($map:ident, $key:ident, $FieldType:ident) <- {$($field:ident),+} ) => {
                        match $key {
                        $(
                            $FieldType::$field => {
                                if $field.is_some() {
                                    return Err(<V::Error as serde::de::Error>::duplicate_field(
                                        std::stringify!($field),
                                    ));
                                }
                                $field = Some(MapAccess::next_value::<String>(&mut $map)?);
                            },
                        )+
                        _ => {
                            let _ = MapAccess::next_value::<serde::de::IgnoredAny>(&mut $map)?;
                        }
                      }
                  }
                }

                while let Some(key) = MapAccess::next_key::<BatchField>(&mut map)? {
                    match_map_fields!((map, key, BatchField) <- {
                        filename, description, author, comment, url, date, processed_by,
                        redacted_by, copyright, theme, kind, source, rating
                    });
                }

                let filename = filename.unwrap_or_default();
                let description = description.unwrap_or_default();
                let author = author.unwrap_or_default();
                let comment = comment.unwrap_or_default();
                let url = url.unwrap_or_default();
                let date = date.unwrap_or_default();
                let processed_by = processed_by.unwrap_or_default();
                let redacted_by = redacted_by.unwrap_or_default();
                let copyright = copyright.unwrap_or_default();
                let theme = theme.unwrap_or_default();
                let kind = kind.unwrap_or_default();
                let source = source.unwrap_or_default();
                let rating = rating.unwrap_or_default();

                Ok(BatchInfo {
                    filename,
                    description,
                    author,
                    comment,
                    url,
                    date,
                    processed_by,
                    redacted_by,
                    copyright,
                    theme,
                    kind,
                    source,
                    rating,
                })
            }
        }
        const FIELDS: &[&str] = &[
            "filename",
            "description",
            "author",
            "comment",
            "url",
            "date",
            "processed_by",
            "redacted_by",
            "copyright",
            "theme",
            "kind",
            "source",
            "rating",
        ];
        Deserializer::deserialize_struct(
            deserializer,
            "BatchInfo",
            FIELDS,
            BatchInfoVisitor {
                marker: PhantomData::<BatchInfo>,
                lifetime: PhantomData,
            },
        )
    }
}

impl<'de> Deserialize<'de> for Question {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[allow(non_camel_case_types)]
        enum QuestionField {
            num,
            id,
            description,
            answer,
            author,
            comment,
            comment1,
            tour,
            url,
            date,
            processed_by,
            redacted_by,
            copyright,
            theme,
            kind,
            source,
            rating,
            batch_info,
            _flags,
            __ignore,
        }

        struct QuestionFieldVisitor;
        impl<'de> Visitor<'de> for QuestionFieldVisitor {
            type Value = QuestionField;
            fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
                Formatter::write_str(formatter, "field identifier")
            }
            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                match value {
                    0u64 => Ok(QuestionField::num),
                    1u64 => Ok(QuestionField::id),
                    2u64 => Ok(QuestionField::description),
                    3u64 => Ok(QuestionField::answer),
                    4u64 => Ok(QuestionField::author),
                    5u64 => Ok(QuestionField::comment),
                    6u64 => Ok(QuestionField::comment1),
                    7u64 => Ok(QuestionField::tour),
                    8u64 => Ok(QuestionField::url),
                    9u64 => Ok(QuestionField::date),
                    10u64 => Ok(QuestionField::processed_by),
                    11u64 => Ok(QuestionField::redacted_by),
                    12u64 => Ok(QuestionField::copyright),
                    13u64 => Ok(QuestionField::theme),
                    14u64 => Ok(QuestionField::kind),
                    15u64 => Ok(QuestionField::source),
                    16u64 => Ok(QuestionField::rating),
                    17u64 => Ok(QuestionField::batch_info),
                    18u64 => Ok(QuestionField::_flags),
                    _ => Ok(QuestionField::__ignore),
                }
            }
            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                match value {
                    "num" => Ok(QuestionField::num),
                    "id" => Ok(QuestionField::id),
                    "description" => Ok(QuestionField::description),
                    "answer" => Ok(QuestionField::answer),
                    "author" => Ok(QuestionField::author),
                    "comment" => Ok(QuestionField::comment),
                    "comment1" => Ok(QuestionField::comment1),
                    "tour" => Ok(QuestionField::tour),
                    "url" => Ok(QuestionField::url),
                    "date" => Ok(QuestionField::date),
                    "processed_by" => Ok(QuestionField::processed_by),
                    "redacted_by" => Ok(QuestionField::redacted_by),
                    "copyright" => Ok(QuestionField::copyright),
                    "theme" => Ok(QuestionField::theme),
                    "kind" => Ok(QuestionField::kind),
                    "source" => Ok(QuestionField::source),
                    "rating" => Ok(QuestionField::rating),
                    "batch_info" => Ok(QuestionField::batch_info),
                    "_flags" => Ok(QuestionField::_flags),
                    _ => Ok(QuestionField::__ignore),
                }
            }
            fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                match value {
                    b"num" => Ok(QuestionField::num),
                    b"id" => Ok(QuestionField::id),
                    b"description" => Ok(QuestionField::description),
                    b"answer" => Ok(QuestionField::answer),
                    b"author" => Ok(QuestionField::author),
                    b"comment" => Ok(QuestionField::comment),
                    b"comment1" => Ok(QuestionField::comment1),
                    b"tour" => Ok(QuestionField::tour),
                    b"url" => Ok(QuestionField::url),
                    b"date" => Ok(QuestionField::date),
                    b"processed_by" => Ok(QuestionField::processed_by),
                    b"redacted_by" => Ok(QuestionField::redacted_by),
                    b"copyright" => Ok(QuestionField::copyright),
                    b"theme" => Ok(QuestionField::theme),
                    b"kind" => Ok(QuestionField::kind),
                    b"source" => Ok(QuestionField::source),
                    b"rating" => Ok(QuestionField::rating),
                    b"batch_info" => Ok(QuestionField::batch_info),
                    b"_flags" => Ok(QuestionField::_flags),
                    _ => Ok(QuestionField::__ignore),
                }
            }
        }
        impl<'de> Deserialize<'de> for QuestionField {
            #[inline]
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
            where
                D: Deserializer<'de>,
            {
                Deserializer::deserialize_identifier(deserializer, QuestionFieldVisitor)
            }
        }
        #[doc(hidden)]
        struct QuestionVisitor<'de> {
            marker: PhantomData<Question>,
            lifetime: PhantomData<&'de ()>,
        }
        impl<'de> Visitor<'de> for QuestionVisitor<'de> {
            type Value = Question;
            fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
                Formatter::write_str(formatter, "struct Question")
            }
            #[inline]
            fn visit_seq<V>(self, mut seq: V) -> Result<Self::Value, V::Error>
            where
                V: SeqAccess<'de>,
            {
                let _flags = QuestionFlags::from_bits(
                    SeqAccess::next_element::<u16>(&mut seq)?.unwrap_or_default(),
                )
                .unwrap_or_default();

                macro_rules! seq_read_strings {
                    (($flags:ident, $seq:ident, $FlagsType:ident) <- {$($field:ident:$flag:ident),+} ) => {$(
                        let $field: String = if $flags.intersects($FlagsType::$flag) {
                            SeqAccess::next_element::<String>(&mut $seq)?.unwrap_or_default()
                        } else {
                            Default::default()
                        };
                    )+}
                }

                let num: u32 = if _flags.intersects(QuestionFlags::NUM) {
                    SeqAccess::next_element::<u32>(&mut seq)?.unwrap_or_default()
                } else {
                    Default::default()
                };

                let id = match SeqAccess::next_element::<String>(&mut seq)? {
                    Some(value) => value,
                    _ => return Err(<V::Error as serde::de::Error>::missing_field("id")),
                };
                let description = match SeqAccess::next_element::<String>(&mut seq)? {
                    Some(value) => value,
                    _ => return Err(<V::Error as serde::de::Error>::missing_field("description")),
                };
                let answer = match SeqAccess::next_element::<String>(&mut seq)? {
                    Some(value) => value,
                    _ => return Err(<V::Error as serde::de::Error>::missing_field("answer")),
                };

                seq_read_strings!((_flags, seq, QuestionFlags) <- {
                    author: AUTHOR, comment: COMMENT, comment1: COMMENT1, tour: TOUR, url: URL, date: DATE,
                    processed_by: PROCESSED_BY, redacted_by: REDACTED_BY, copyright: COPYRIGHT, theme: THEME,
                    kind: KIND, source: SOURCE, rating: RATING
                });

                let batch_info: BatchInfo = if _flags.intersects(QuestionFlags::BATCH_INFO) {
                    SeqAccess::next_element::<BatchInfo>(&mut seq)?.unwrap_or_default()
                } else {
                    Default::default()
                };

                Ok(Question {
                    num,
                    id,
                    description,
                    answer,
                    author,
                    comment,
                    comment1,
                    tour,
                    url,
                    date,
                    processed_by,
                    redacted_by,
                    copyright,
                    theme,
                    kind,
                    source,
                    rating,
                    batch_info,
                })
            }
            #[inline]
            fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
            where
                V: MapAccess<'de>,
            {
                let mut num: Option<u32> = None;
                let mut id: Option<String> = None;
                let mut description: Option<String> = None;
                let mut answer: Option<String> = None;
                let mut author: Option<String> = None;
                let mut comment: Option<String> = None;
                let mut comment1: Option<String> = None;
                let mut tour: Option<String> = None;
                let mut url: Option<String> = None;
                let mut date: Option<String> = None;
                let mut processed_by: Option<String> = None;
                let mut redacted_by: Option<String> = None;
                let mut copyright: Option<String> = None;
                let mut theme: Option<String> = None;
                let mut kind: Option<String> = None;
                let mut source: Option<String> = None;
                let mut rating: Option<String> = None;
                let mut batch_info: Option<BatchInfo> = None;

                macro_rules! match_map_fields {
                    (($map:ident, $key:ident, $FieldType:ident) <- {$($field:ident:$FieldValueType:ident),+} ) => {
                        match $key {
                        $(
                            $FieldType::$field => {
                                if $field.is_some() {
                                    return Err(<V::Error as serde::de::Error>::duplicate_field(
                                        std::stringify!($field),
                                    ));
                                }
                                $field = Some(MapAccess::next_value::<$FieldValueType>(&mut $map)?);
                            },
                        )+
                        _ => {
                            let _ = MapAccess::next_value::<serde::de::IgnoredAny>(&mut $map)?;
                        }
                      }
                  }
                }

                while let Some(key) = MapAccess::next_key::<QuestionField>(&mut map)? {
                    match_map_fields!((map, key, QuestionField) <- {
                        num: u32, id: String, description: String, answer: String, author: String, comment: String,
                        comment1: String, tour: String, url: String, date: String, processed_by: String, redacted_by: String,
                        copyright: String, theme: String, kind: String, source: String, rating: String, batch_info: BatchInfo
                    });
                }

                let id = match id {
                    Some(value) => value,
                    _ => return Err(<V::Error as serde::de::Error>::missing_field("id")),
                };
                let description = match description {
                    Some(value) => value,
                    _ => return Err(<V::Error as serde::de::Error>::missing_field("description")),
                };
                let answer = match answer {
                    Some(value) => value,
                    _ => return Err(<V::Error as serde::de::Error>::missing_field("answer")),
                };

                let num = num.unwrap_or_default();
                let author = author.unwrap_or_default();
                let comment = comment.unwrap_or_default();
                let comment1 = comment1.unwrap_or_default();
                let tour = tour.unwrap_or_default();
                let url = url.unwrap_or_default();
                let date = date.unwrap_or_default();
                let processed_by = processed_by.unwrap_or_default();
                let redacted_by = redacted_by.unwrap_or_default();
                let copyright = copyright.unwrap_or_default();
                let theme = theme.unwrap_or_default();
                let kind = kind.unwrap_or_default();
                let source = source.unwrap_or_default();
                let rating = rating.unwrap_or_default();
                let batch_info = batch_info.unwrap_or_default();

                Ok(Question {
                    num,
                    id,
                    description,
                    answer,
                    author,
                    comment,
                    comment1,
                    tour,
                    url,
                    date,
                    processed_by,
                    redacted_by,
                    copyright,
                    theme,
                    kind,
                    source,
                    rating,
                    batch_info,
                })
            }
        }
        #[doc(hidden)]
        const FIELDS: &[&str] = &[
            "num",
            "id",
            "description",
            "answer",
            "author",
            "comment",
            "comment1",
            "tour",
            "url",
            "date",
            "processed_by",
            "redacted_by",
            "copyright",
            "theme",
            "kind",
            "source",
            "rating",
            "batch_info",
        ];
        Deserializer::deserialize_struct(
            deserializer,
            "Question",
            FIELDS,
            QuestionVisitor {
                marker: PhantomData::<Question>,
                lifetime: PhantomData,
            },
        )
    }
}

#[cfg(any(feature = "convert", feature = "convert_async"))]
pub mod convert_common {
    use super::{BatchInfo, Question};
    use crate::source::{SourceQuestion, SourceQuestionsBatch};

    macro_rules! make {
        ($Target:ident; by {$($field:ident),+}; from $src:expr) => {$Target {$(
            $field: $src.$field
        ),+}};
        ($Target:ident; with defaults and by {$($field:ident),+}; from $src:expr) => {$Target {$(
            $field: $src.$field
        ),+ ,..$Target::default()}}
    }

    impl From<SourceQuestion> for Question {
        fn from(src: SourceQuestion) -> Self {
            make! {Self; with defaults and by {
                num, id, description, answer, author, comment, comment1, tour, url,
                date, processed_by, redacted_by, copyright, theme, kind, source, rating
            }; from src}
        }
    }

    impl From<SourceQuestionsBatch> for BatchInfo {
        fn from(src: SourceQuestionsBatch) -> Self {
            make! {Self; by {
                filename, description, author, comment, url, date,
                processed_by, redacted_by, copyright, theme, kind, source, rating
            }; from src}
        }
    }

    impl From<SourceQuestionsBatch> for Vec<Question> {
        fn from(src: SourceQuestionsBatch) -> Self {
            let mut src = src;
            let mut questions: Vec<SourceQuestion> = vec![];
            std::mem::swap(&mut src.questions, &mut questions);
            let mut result: Vec<Question> = questions.into_iter().map(|item| item.into()).collect();
            let batch_info = BatchInfo::from(src);
            result.iter_mut().for_each(|question| {
                question.batch_info = batch_info.clone();
            });

            result
        }
    }
}

#[cfg(feature = "convert")]
pub mod convert {
    use super::Question;
    use crate::source::SourceQuestionsBatch;

    pub trait QuestionsConverter {
        fn convert<'a>(&'a mut self) -> Box<dyn Iterator<Item = Question> + 'a>;
    }

    impl<T> QuestionsConverter for T
    where
        T: Iterator<Item = (String, Result<SourceQuestionsBatch, serde_json::Error>)>,
    {
        fn convert<'a>(&'a mut self) -> Box<dyn Iterator<Item = Question> + 'a> {
            let iter = self
                .filter(|(_, data)| data.is_ok())
                .flat_map(|(filename, data)| {
                    let mut batch = data.unwrap();
                    batch.filename = filename;
                    let questions: Vec<Question> = batch.into();
                    questions
                });
            Box::new(iter)
        }
    }

    #[cfg(test)]
    mod test {
        use crate::questions::test::convert_common::sample_batch;

        use super::*;
        use insta::assert_yaml_snapshot;
        use std::iter;

        #[test]
        fn test_convert() {
            let mut source = iter::once((
                String::from("test.json"),
                Ok::<SourceQuestionsBatch, serde_json::Error>(sample_batch()),
            ));
            let converted: Vec<_> = source.convert().collect();
            assert_yaml_snapshot!(converted, @r#"
            ---
            - id: Вопрос 1
              description: Сколько будет (2 * 2 * 2 + 2) * 2 * 2 + 2
              answer: "42"
              batch_info:
                filename: test.json
                description: Тестовый
                date: 00-000-2000
            - id: Вопрос 2
              description: Зимой и летом одним цветом
              answer: ёлка
              batch_info:
                filename: test.json
                description: Тестовый
                date: 00-000-2000
              
            "#);
        }
    }
}
#[cfg(feature = "convert")]
pub use convert::QuestionsConverter;

#[cfg(feature = "convert_async")]
pub mod convert_async {
    use futures::stream;
    use futures_core::stream::Stream;
    use futures_util::StreamExt;

    use super::Question;
    use crate::source::SourceQuestionsBatch;

    pub struct QuestionsConverterAsync<T>
    where
        T: Stream<Item = (String, Result<SourceQuestionsBatch, serde_json::Error>)>
            + std::marker::Unpin,
    {
        inner: T,
    }

    impl<T> From<T> for QuestionsConverterAsync<T>
    where
        T: Stream<Item = (String, Result<SourceQuestionsBatch, serde_json::Error>)>
            + std::marker::Unpin,
    {
        fn from(inner: T) -> Self {
            Self { inner }
        }
    }

    pub trait QuestionsConverterAsyncForStream<T>
    where
        T: Stream<Item = (String, Result<SourceQuestionsBatch, serde_json::Error>)>
            + std::marker::Unpin,
    {
        fn converter(&mut self) -> QuestionsConverterAsync<&mut T>;
    }

    impl<T> QuestionsConverterAsyncForStream<T> for T
    where
        T: Stream<Item = (String, Result<SourceQuestionsBatch, serde_json::Error>)>
            + std::marker::Unpin,
    {
        fn converter(&mut self) -> QuestionsConverterAsync<&mut T> {
            QuestionsConverterAsync::from(self)
        }
    }

    impl<T> QuestionsConverterAsync<T>
    where
        T: Stream<Item = (String, Result<SourceQuestionsBatch, serde_json::Error>)>
            + std::marker::Unpin,
    {
        pub fn convert(self) -> impl Stream<Item = Question> {
            self.inner
                .filter_map(|(name, res)| async move {
                    if let Ok(item) = res {
                        Some((name, item))
                    } else {
                        None
                    }
                })
                .flat_map(|(filename, batch)| {
                    stream::iter({
                        let mut batch = batch;
                        batch.filename = filename;
                        let questions: Vec<Question> = batch.into();
                        questions
                    })
                })
        }
    }

    #[cfg(test)]
    mod test {
        use crate::questions::test::convert_common::sample_batch;

        use super::*;
        use futures_util::{pin_mut, StreamExt};
        use insta::assert_yaml_snapshot;

        #[tokio::test]
        async fn test_convert_stream() {
            let source = futures::stream::once(async {
                (
                    String::from("test.json"),
                    Ok::<SourceQuestionsBatch, serde_json::Error>(sample_batch()),
                )
            });

            pin_mut!(source);
            let converter = source.converter();
            let converter = converter.convert();
            let converted: Vec<_> = converter.collect().await;
            assert_yaml_snapshot!(converted, @r#"
            ---
            - id: Вопрос 1
              description: Сколько будет (2 * 2 * 2 + 2) * 2 * 2 + 2
              answer: "42"
              batch_info:
                filename: test.json
                description: Тестовый
                date: 00-000-2000
            - id: Вопрос 2
              description: Зимой и летом одним цветом
              answer: ёлка
              batch_info:
                filename: test.json
                description: Тестовый
                date: 00-000-2000
              
            "#);
        }
    }
}
#[cfg(feature = "convert_async")]
pub use convert_async::{QuestionsConverterAsync, QuestionsConverterAsyncForStream};

#[cfg(test)]
mod test {
    use super::*;
    use insta::assert_yaml_snapshot;
    use serde_json::json;

    #[cfg(any(feature = "convert", feature = "convert_async"))]
    pub mod convert_common {
        use crate::source::{SourceQuestion, SourceQuestionsBatch};

        pub fn sample_batch() -> SourceQuestionsBatch {
            SourceQuestionsBatch {
                description: "Тестовый".into(),
                date: "00-000-2000".into(),
                questions: vec![
                    SourceQuestion {
                        id: "Вопрос 1".into(),
                        description: "Сколько будет (2 * 2 * 2 + 2) * 2 * 2 + 2".into(),
                        answer: "42".into(),
                        ..Default::default()
                    },
                    SourceQuestion {
                        id: "Вопрос 2".into(),
                        description: "Зимой и летом одним цветом".into(),
                        answer: "ёлка".into(),
                        ..Default::default()
                    },
                ],
                ..Default::default()
            }
        }
    }

    pub fn sample_question() -> Question {
        Question {
            id: "Вопрос 1".into(),
            description: "Сколько будет (2 * 2 * 2 + 2) * 2 * 2 + 2".into(),
            answer: "42".into(),
            batch_info: BatchInfo {
                description: "Тестовый".into(),
                date: "00-000-2000".into(),
                ..Default::default()
            },
            ..Default::default()
        }
    }

    #[test]
    fn test_question_ser() {
        assert_yaml_snapshot!(sample_question(), @r#"
            ---
            id: Вопрос 1
            description: Сколько будет (2 * 2 * 2 + 2) * 2 * 2 + 2
            answer: "42"
            batch_info:
              description: Тестовый
              date: 00-000-2000
              
            "#);
    }
    #[test]
    fn test_question_serde_postcard() {
        use postcard::{from_bytes, to_slice};

        let original = sample_question();
        let mut buff: Vec<u8> = vec![0; 135];

        to_slice(&original, buff.as_mut_slice()).expect("to_slice");
        let actual: Question = from_bytes(buff.as_slice()).expect("from_bytes");

        assert_eq!(actual, original);
    }
    #[test]
    fn test_question_de() {
        let question_from_json: Result<Question, _> = serde_json::from_value(json!({
            "id": "Вопрос 1",
            "description": "Сколько будет (2 * 2 * 2 + 2) * 2 * 2 + 2",
            "answer": "42",
            "batch_info": {
                "description": "Тестовый",
                "date": "00-000-2000"
            }
        }));
        assert!(question_from_json.is_ok());

        assert_yaml_snapshot!(question_from_json.unwrap(), @r#"
        ---
        id: Вопрос 1
        description: Сколько будет (2 * 2 * 2 + 2) * 2 * 2 + 2
        answer: "42"
        batch_info:
          description: Тестовый
          date: 00-000-2000
          
        "#);
    }
}