use cityjson_types::resources::storage::StringStorage;
use cityjson_types::v2_0::{
    CityObjectType, ContactRole, ContactType, ImageType, LoD, SemanticType, TextureType, WrapMode,
};
use cityjson_types::{CityJSONVersion, CityModelType};

use crate::errors::{Error, Result};

pub(crate) struct RootHeader {
    pub(crate) type_citymodel: CityModelType,
    pub(crate) version: CityJSONVersion,
}

pub(crate) fn parse_root_header(type_name: &str, version: Option<&str>) -> Result<RootHeader> {
    let type_citymodel = CityModelType::try_from(type_name)
        .map_err(|_| Error::UnsupportedType(type_name.to_owned()))?;
    let version = match (type_citymodel, version) {
        (CityModelType::CityJSONFeature, None) => CityJSONVersion::V2_0,
        (_, Some(version)) => CityJSONVersion::try_from(version)
            .map_err(|_| Error::UnsupportedVersion(version.to_owned()))?,
        (_, None) => return Err(Error::MalformedRootObject("missing root version")),
    };
    Ok(RootHeader {
        type_citymodel,
        version,
    })
}

pub(crate) fn parse_lod(value: Option<&str>) -> Result<Option<LoD>> {
    match value {
        None => Ok(None),
        Some("0") => Ok(Some(LoD::LoD0)),
        Some("0.0") => Ok(Some(LoD::LoD0_0)),
        Some("0.1") => Ok(Some(LoD::LoD0_1)),
        Some("0.2") => Ok(Some(LoD::LoD0_2)),
        Some("0.3") => Ok(Some(LoD::LoD0_3)),
        Some("1") => Ok(Some(LoD::LoD1)),
        Some("1.0") => Ok(Some(LoD::LoD1_0)),
        Some("1.1") => Ok(Some(LoD::LoD1_1)),
        Some("1.2") => Ok(Some(LoD::LoD1_2)),
        Some("1.3") => Ok(Some(LoD::LoD1_3)),
        Some("2") => Ok(Some(LoD::LoD2)),
        Some("2.0") => Ok(Some(LoD::LoD2_0)),
        Some("2.1") => Ok(Some(LoD::LoD2_1)),
        Some("2.2") => Ok(Some(LoD::LoD2_2)),
        Some("2.3") => Ok(Some(LoD::LoD2_3)),
        Some("3") => Ok(Some(LoD::LoD3)),
        Some("3.0") => Ok(Some(LoD::LoD3_0)),
        Some("3.1") => Ok(Some(LoD::LoD3_1)),
        Some("3.2") => Ok(Some(LoD::LoD3_2)),
        Some("3.3") => Ok(Some(LoD::LoD3_3)),
        Some(other) => Err(Error::InvalidValue(format!(
            "unsupported geometry lod value '{other}'"
        ))),
    }
}

pub(crate) fn parse_contact_role(value: &str) -> Result<ContactRole> {
    match value {
        "author" => Ok(ContactRole::Author),
        "co-author" => Ok(ContactRole::CoAuthor),
        "processor" => Ok(ContactRole::Processor),
        "pointOfContact" => Ok(ContactRole::PointOfContact),
        "owner" => Ok(ContactRole::Owner),
        "user" => Ok(ContactRole::User),
        "distributor" => Ok(ContactRole::Distributor),
        "originator" => Ok(ContactRole::Originator),
        "custodian" => Ok(ContactRole::Custodian),
        "resourceProvider" => Ok(ContactRole::ResourceProvider),
        "rightsHolder" => Ok(ContactRole::RightsHolder),
        "sponsor" => Ok(ContactRole::Sponsor),
        "principalInvestigator" => Ok(ContactRole::PrincipalInvestigator),
        "stakeholder" => Ok(ContactRole::Stakeholder),
        "publisher" => Ok(ContactRole::Publisher),
        _ => Err(Error::InvalidValue(format!(
            "unsupported pointOfContact.role value '{value}'"
        ))),
    }
}

pub(crate) fn parse_contact_type(value: &str) -> Result<ContactType> {
    match value {
        "individual" => Ok(ContactType::Individual),
        "organization" => Ok(ContactType::Organization),
        _ => Err(Error::InvalidValue(format!(
            "unsupported pointOfContact.contactType value '{value}'"
        ))),
    }
}

pub(crate) fn parse_image_type(value: &str) -> Result<ImageType> {
    match value {
        "PNG" => Ok(ImageType::Png),
        "JPG" => Ok(ImageType::Jpg),
        _ => Err(Error::InvalidValue(format!(
            "unsupported texture image type '{value}'"
        ))),
    }
}

pub(crate) fn parse_wrap_mode(value: &str) -> Result<WrapMode> {
    match value {
        "wrap" => Ok(WrapMode::Wrap),
        "mirror" => Ok(WrapMode::Mirror),
        "clamp" => Ok(WrapMode::Clamp),
        "border" => Ok(WrapMode::Border),
        "none" => Ok(WrapMode::None),
        _ => Err(Error::InvalidValue(format!(
            "unsupported texture wrapMode value '{value}'"
        ))),
    }
}

pub(crate) fn parse_texture_type(value: &str) -> Result<TextureType> {
    match value {
        "unknown" => Ok(TextureType::Unknown),
        "specific" => Ok(TextureType::Specific),
        "typical" => Ok(TextureType::Typical),
        _ => Err(Error::InvalidValue(format!(
            "unsupported texture textureType value '{value}'"
        ))),
    }
}

pub(crate) fn parse_semantic_type<'de, SS: StringStorage>(
    value: &'de str,
) -> Result<SemanticType<SS>>
where
    SS::String: From<&'de str>,
{
    Ok(match value {
        "RoofSurface" => SemanticType::RoofSurface,
        "GroundSurface" => SemanticType::GroundSurface,
        "WallSurface" => SemanticType::WallSurface,
        "ClosureSurface" => SemanticType::ClosureSurface,
        "OuterCeilingSurface" => SemanticType::OuterCeilingSurface,
        "OuterFloorSurface" => SemanticType::OuterFloorSurface,
        "Window" => SemanticType::Window,
        "Door" => SemanticType::Door,
        "InteriorWallSurface" => SemanticType::InteriorWallSurface,
        "CeilingSurface" => SemanticType::CeilingSurface,
        "FloorSurface" => SemanticType::FloorSurface,
        "WaterSurface" => SemanticType::WaterSurface,
        "WaterGroundSurface" => SemanticType::WaterGroundSurface,
        "WaterClosureSurface" => SemanticType::WaterClosureSurface,
        "TrafficArea" => SemanticType::TrafficArea,
        "AuxiliaryTrafficArea" => SemanticType::AuxiliaryTrafficArea,
        "TransportationMarking" => SemanticType::TransportationMarking,
        "TransportationHole" => SemanticType::TransportationHole,
        _ if value.starts_with('+') => SemanticType::Extension(SS::String::from(value)),
        _ => {
            return Err(Error::InvalidValue(format!(
                "invalid Semantic type: {value}"
            )));
        }
    })
}

pub(crate) fn parse_cityobject_type<'de, SS: StringStorage>(
    value: &'de str,
) -> Result<CityObjectType<SS>>
where
    SS::String: From<&'de str>,
{
    Ok(match value {
        "Bridge" => CityObjectType::Bridge,
        "BridgePart" => CityObjectType::BridgePart,
        "BridgeInstallation" => CityObjectType::BridgeInstallation,
        "BridgeConstructiveElement" => CityObjectType::BridgeConstructiveElement,
        "BridgeRoom" => CityObjectType::BridgeRoom,
        "BridgeFurniture" => CityObjectType::BridgeFurniture,
        "Building" => CityObjectType::Building,
        "BuildingPart" => CityObjectType::BuildingPart,
        "BuildingInstallation" => CityObjectType::BuildingInstallation,
        "BuildingConstructiveElement" => CityObjectType::BuildingConstructiveElement,
        "BuildingFurniture" => CityObjectType::BuildingFurniture,
        "BuildingStorey" => CityObjectType::BuildingStorey,
        "BuildingRoom" => CityObjectType::BuildingRoom,
        "BuildingUnit" => CityObjectType::BuildingUnit,
        "CityFurniture" => CityObjectType::CityFurniture,
        "CityObjectGroup" => CityObjectType::CityObjectGroup,
        "Default" => CityObjectType::Default,
        "GenericCityObject" => CityObjectType::GenericCityObject,
        "LandUse" => CityObjectType::LandUse,
        "OtherConstruction" => CityObjectType::OtherConstruction,
        "PlantCover" => CityObjectType::PlantCover,
        "SolitaryVegetationObject" => CityObjectType::SolitaryVegetationObject,
        "TINRelief" => CityObjectType::TINRelief,
        "WaterBody" => CityObjectType::WaterBody,
        "Road" => CityObjectType::Road,
        "Railway" => CityObjectType::Railway,
        "Waterway" => CityObjectType::Waterway,
        "TransportSquare" => CityObjectType::TransportSquare,
        "Tunnel" => CityObjectType::Tunnel,
        "TunnelPart" => CityObjectType::TunnelPart,
        "TunnelInstallation" => CityObjectType::TunnelInstallation,
        "TunnelConstructiveElement" => CityObjectType::TunnelConstructiveElement,
        "TunnelHollowSpace" => CityObjectType::TunnelHollowSpace,
        "TunnelFurniture" => CityObjectType::TunnelFurniture,
        _ if value.starts_with('+') => CityObjectType::Extension(SS::String::from(value)),
        _ => {
            return Err(Error::InvalidValue(format!(
                "invalid CityObject type '{value}'"
            )));
        }
    })
}
