use std::collections::HashMap;
use std::fmt;

use serde::Deserialize;
use serde::de::{self, DeserializeSeed, MapAccess, Visitor};
use serde_json::value::RawValue;

use crate::de::attributes::RawAttribute;
use crate::de::sections::{
    RawAppearanceSection, RawExtension, RawGeometryTemplatesSection, RawMetadataSection,
};
use crate::errors::{Error, Result};

pub(crate) struct PreparedRoot<'a> {
    pub(crate) type_name: &'a str,
    pub(crate) version: Option<&'a str>,
    pub(crate) transform: Option<RawTransform>,
    pub(crate) vertices: Vec<[f64; 3]>,
    pub(crate) metadata: Option<RawMetadataSection<'a>>,
    pub(crate) extensions: Option<HashMap<&'a str, RawExtension<'a>>>,
    pub(crate) cityobjects: &'a RawValue,
    pub(crate) appearance: Option<RawAppearanceSection<'a>>,
    pub(crate) geometry_templates: Option<RawGeometryTemplatesSection<'a>>,
    pub(crate) id: Option<RawAttribute<'a>>,
    pub(crate) extra: HashMap<&'a str, RawAttribute<'a>>,
}

#[derive(Deserialize)]
pub(crate) struct RawTransform {
    pub(crate) scale: [f64; 3],
    pub(crate) translate: [f64; 3],
}

pub(crate) fn parse_root(input: &str) -> Result<PreparedRoot<'_>> {
    let mut deserializer = serde_json::Deserializer::from_str(input);
    let root = RootSeed
        .deserialize(&mut deserializer)
        .map_err(Error::from)?;
    deserializer.end().map_err(Error::from)?;
    Ok(root)
}

struct RootSeed;

impl<'de> DeserializeSeed<'de> for RootSeed {
    type Value = PreparedRoot<'de>;

    fn deserialize<D>(self, deserializer: D) -> std::result::Result<Self::Value, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserializer.deserialize_map(RootVisitor)
    }
}

struct RootVisitor;

impl<'de> Visitor<'de> for RootVisitor {
    type Value = PreparedRoot<'de>;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a CityJSON root object")
    }

    fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
    where
        A: MapAccess<'de>,
    {
        let mut type_name = None;
        let mut version = None;
        let mut transform = None;
        let mut vertices = None;
        let mut metadata = None;
        let mut extensions = None;
        let mut cityobjects = None;
        let mut appearance = None;
        let mut geometry_templates = None;
        let mut id = None;
        let mut extra = HashMap::with_capacity(map.size_hint().unwrap_or(0));

        while let Some(key) = map.next_key::<&'de str>()? {
            match key {
                "type" => set_once(&mut type_name, "type", map.next_value()?)?,
                "version" => set_once(&mut version, "version", map.next_value()?)?,
                "transform" => set_once(&mut transform, "transform", map.next_value()?)?,
                "vertices" => set_once(&mut vertices, "vertices", map.next_value()?)?,
                "metadata" => set_once(&mut metadata, "metadata", map.next_value()?)?,
                "extensions" => set_once(&mut extensions, "extensions", map.next_value()?)?,
                "CityObjects" => set_once(&mut cityobjects, "CityObjects", map.next_value()?)?,
                "appearance" => set_once(&mut appearance, "appearance", map.next_value()?)?,
                "id" => set_once(&mut id, "id", map.next_value()?)?,
                "geometry-templates" => set_once(
                    &mut geometry_templates,
                    "geometry-templates",
                    map.next_value()?,
                )?,
                _ => {
                    extra.insert(key, map.next_value()?);
                }
            }
        }

        Ok(PreparedRoot {
            type_name: type_name.ok_or_else(|| de::Error::missing_field("type"))?,
            version,
            transform,
            vertices: vertices.ok_or_else(|| de::Error::missing_field("vertices"))?,
            metadata,
            extensions,
            cityobjects: cityobjects.ok_or_else(|| de::Error::missing_field("CityObjects"))?,
            appearance,
            geometry_templates,
            id,
            extra,
        })
    }
}

fn set_once<T, E>(slot: &mut Option<T>, field: &'static str, value: T) -> std::result::Result<(), E>
where
    E: de::Error,
{
    if slot.is_some() {
        return Err(de::Error::duplicate_field(field));
    }
    *slot = Some(value);
    Ok(())
}
