use std::collections::HashMap;
use std::fmt;
use std::marker::PhantomData;

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

use cityjson_types::resources::handles::CityObjectHandle;
use cityjson_types::resources::storage::StringStorage;
use cityjson_types::v2_0::{Attributes, BBox, CityModel, CityObject, CityObjectIdentifier};

use crate::de::attributes::{AttributeValueSeed, OptionalAttributesSeed};
use crate::de::geometry::{GeometryResources, StreamingGeometry, import_stream_geometry};
use crate::de::parse::ParseStringStorage;
use crate::de::profiling::timed;
use crate::de::validation::parse_cityobject_type;
use crate::errors::{Error, Result};

pub(crate) struct StreamingCityObject<'de, SS: StringStorage> {
    pub(crate) type_name: &'de str,
    pub(crate) geographical_extent: Option<[f64; 6]>,
    pub(crate) attributes: Option<Attributes<SS>>,
    pub(crate) parents: Vec<&'de str>,
    pub(crate) children: Vec<&'de str>,
    pub(crate) geometry: Option<Vec<StreamingGeometry<'de>>>,
    pub(crate) extra: Attributes<SS>,
}

impl<'de, SS> Deserialize<'de> for StreamingCityObject<'de, SS>
where
    SS: ParseStringStorage<'de>,
{
    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserializer.deserialize_map(StreamingCityObjectVisitor::<SS>(PhantomData))
    }
}

struct StreamingCityObjectVisitor<SS>(PhantomData<SS>);

impl<'de, SS> Visitor<'de> for StreamingCityObjectVisitor<SS>
where
    SS: ParseStringStorage<'de>,
{
    type Value = StreamingCityObject<'de, SS>;

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

    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 geographical_extent = None;
        let mut attributes = None;
        let mut parents = None;
        let mut children = None;
        let mut geometry = None;
        let mut extra = Attributes::<SS>::with_capacity(map.size_hint().unwrap_or(0));

        while let Some(key) = map.next_key::<&'de str>()? {
            match key {
                "type" => {
                    if type_name.is_some() {
                        return Err(de::Error::duplicate_field("type"));
                    }
                    type_name = Some(map.next_value()?);
                }
                "geographicalExtent" => {
                    if geographical_extent.is_some() {
                        return Err(de::Error::duplicate_field("geographicalExtent"));
                    }
                    geographical_extent = Some(map.next_value()?);
                }
                "attributes" => {
                    if attributes.is_some() {
                        return Err(de::Error::duplicate_field("attributes"));
                    }
                    attributes = Some(timed("cityobjects.attributes", || {
                        map.next_value_seed(OptionalAttributesSeed::<SS>::new())
                            .map_err(de::Error::custom)
                    })?);
                }
                "parents" => {
                    if parents.is_some() {
                        return Err(de::Error::duplicate_field("parents"));
                    }
                    parents = Some(map.next_value()?);
                }
                "children" => {
                    if children.is_some() {
                        return Err(de::Error::duplicate_field("children"));
                    }
                    children = Some(map.next_value()?);
                }
                "geometry" => {
                    if geometry.is_some() {
                        return Err(de::Error::duplicate_field("geometry"));
                    }
                    geometry = Some(map.next_value()?);
                }
                _ => {
                    let value = timed("cityobjects.extra", || {
                        map.next_value_seed(AttributeValueSeed::<SS>::new())
                            .map_err(de::Error::custom)
                    })?;
                    extra.insert(SS::store(key), value);
                }
            }
        }

        Ok(StreamingCityObject {
            type_name: type_name.ok_or_else(|| de::Error::missing_field("type"))?,
            geographical_extent: geographical_extent.flatten(),
            attributes: attributes.flatten(),
            parents: parents.unwrap_or_default(),
            children: children.unwrap_or_default(),
            geometry: geometry.flatten(),
            extra,
        })
    }
}

struct PendingRelations<'de> {
    source_id: &'de str,
    source_handle: CityObjectHandle,
    parents: Vec<&'de str>,
    children: Vec<&'de str>,
}

pub(crate) fn import_cityobjects<'de, SS>(
    cityobjects: &'de RawValue,
    model: &mut CityModel<u32, SS>,
    resources: &GeometryResources,
) -> Result<()>
where
    SS: ParseStringStorage<'de>,
    SS::String: From<&'de str>,
{
    let mut deserializer = serde_json::Deserializer::from_str(cityobjects.get());
    timed("cityobjects.deserialize", || {
        CityObjectsImportSeed { model, resources }
            .deserialize(&mut deserializer)
            .map_err(Error::from)
    })?;
    deserializer.end().map_err(Error::from)?;
    Ok(())
}

struct CityObjectsImportSeed<'a, SS: StringStorage> {
    model: &'a mut CityModel<u32, SS>,
    resources: &'a GeometryResources,
}

impl<'de, SS> DeserializeSeed<'de> for CityObjectsImportSeed<'_, SS>
where
    SS: ParseStringStorage<'de>,
    SS::String: From<&'de str>,
{
    type Value = ();

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

struct CityObjectsImportVisitor<'a, SS: StringStorage> {
    model: &'a mut CityModel<u32, SS>,
    resources: &'a GeometryResources,
}

impl<'de, SS> Visitor<'de> for CityObjectsImportVisitor<'_, SS>
where
    SS: ParseStringStorage<'de>,
    SS::String: From<&'de str>,
{
    type Value = ();

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

    fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
    where
        A: MapAccess<'de>,
    {
        let capacity = map.size_hint().unwrap_or(0);
        if capacity != 0 {
            self.model
                .cityobjects_mut()
                .reserve(capacity)
                .map_err(de::Error::custom)?;
        }

        let mut handle_by_id = HashMap::with_capacity(capacity);
        let mut pending = Vec::with_capacity(capacity);

        while let Some(id) = map.next_key::<&'de str>()? {
            let imported = timed("cityobjects.import_object", || {
                import_cityobject::<SS>(id, map.next_value()?, self.model, self.resources)
                    .map_err(de::Error::custom)
            })?;
            handle_by_id.insert(id, imported.source_handle);
            pending.push(imported);
        }

        timed("cityobjects.resolve_relations", || {
            resolve_relations(pending, &handle_by_id, self.model).map_err(de::Error::custom)
        })
    }
}

fn import_cityobject<'de, SS>(
    id: &'de str,
    raw_object: StreamingCityObject<'de, SS>,
    model: &mut CityModel<u32, SS>,
    resources: &GeometryResources,
) -> Result<PendingRelations<'de>>
where
    SS: ParseStringStorage<'de>,
    SS::String: From<&'de str>,
{
    let type_cityobject = timed("cityobjects.parse_type", || {
        parse_cityobject_type::<SS>(raw_object.type_name)
    })?;
    let mut cityobject = CityObject::new(CityObjectIdentifier::new(SS::store(id)), type_cityobject);

    if let Some(extent) = raw_object.geographical_extent {
        cityobject.set_geographical_extent(Some(BBox::from(extent)));
    }
    if let Some(attributes) = raw_object.attributes {
        *cityobject.attributes_mut() = attributes;
    }
    if !raw_object.extra.is_empty() {
        *cityobject.extra_mut() = raw_object.extra;
    }
    if let Some(geometries) = raw_object.geometry {
        if geometries.is_empty() {
            cityobject.clear_geometry();
        } else {
            for geometry in geometries {
                let handle = timed("cityobjects.geometry", || {
                    import_stream_geometry::<SS>(geometry, model, resources)
                })?;
                cityobject.add_geometry(handle);
            }
        }
    }

    let handle = timed("cityobjects.add_object", || {
        model.cityobjects_mut().add(cityobject)
    })?;
    Ok(PendingRelations {
        source_id: id,
        source_handle: handle,
        parents: raw_object.parents,
        children: raw_object.children,
    })
}

fn resolve_relations<'de, SS>(
    pending: Vec<PendingRelations<'de>>,
    handle_by_id: &HashMap<&'de str, CityObjectHandle>,
    model: &mut CityModel<u32, SS>,
) -> Result<()>
where
    SS: StringStorage,
{
    for relation in pending {
        let cityobject = model
            .cityobjects_mut()
            .get_mut(relation.source_handle)
            .ok_or_else(|| {
                Error::InvalidValue(format!(
                    "missing inserted CityObject for '{}'",
                    relation.source_id
                ))
            })?;

        for parent in relation.parents {
            let handle = handle_by_id.get(parent).copied().ok_or_else(|| {
                Error::UnresolvedCityObjectReference {
                    source_id: relation.source_id.to_owned(),
                    target_id: parent.to_owned(),
                    relation: "parent",
                }
            })?;
            cityobject.add_parent(handle);
        }

        for child in relation.children {
            let handle = handle_by_id.get(child).copied().ok_or_else(|| {
                Error::UnresolvedCityObjectReference {
                    source_id: relation.source_id.to_owned(),
                    target_id: child.to_owned(),
                    relation: "child",
                }
            })?;
            cityobject.add_child(handle);
        }
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use cityjson_types::prelude::OwnedStringStorage;
    use cityjson_types::v2_0::AttributeValue;

    use super::StreamingCityObject;

    #[test]
    fn streaming_cityobject_deserializes_attributes_and_extra() {
        let json = r#"{
            "type": "Building",
            "attributes": {"name": "Main", "height": 12.5},
            "custom:flag": true,
            "custom:tags": ["a", "b"]
        }"#;

        let cityobject: StreamingCityObject<'_, OwnedStringStorage> =
            serde_json::from_str(json).expect("cityobject should deserialize");

        assert_eq!(cityobject.type_name, "Building");
        assert_eq!(
            cityobject
                .attributes
                .as_ref()
                .and_then(|attributes| attributes.get("name")),
            Some(&AttributeValue::String("Main".to_owned()))
        );
        assert_eq!(
            cityobject.extra.get("custom:flag"),
            Some(&AttributeValue::Bool(true))
        );
        assert_eq!(
            cityobject.extra.get("custom:tags"),
            Some(&AttributeValue::Vec(vec![
                AttributeValue::String("a".to_owned()),
                AttributeValue::String("b".to_owned()),
            ]))
        );
    }
}
