package de.arxsilex.maptools.converter.worker

import de.arxsilex.maptools.converter.core.ContentSelection
import java.nio.file.Files
import java.nio.file.Path
import javax.xml.XMLConstants
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
import org.w3c.dom.Element

internal object MapsforgeTagMapping {
    private val pathHighwayValues = setOf(
        "bridleway", "byway", "cycleway", "footway", "path", "pedestrian", "steps", "track",
    )
    private val roadKeys = setOf(
        "access", "aeroway", "bridge", "highway", "motor_vehicle", "oneway", "railway", "route",
        "toll", "tracktype", "tunnel",
    )
    private val pathKeys = setOf("aerialway", "mountain_pass", "piste:difficulty", "piste:type")
    private val buildingKeys = setOf(
        "building", "building:colour", "building:levels", "building:material", "building:min_level",
        "building:part", "height", "min_height", "roof:angle", "roof:colour", "roof:direction",
        "roof:height", "roof:levels", "roof:material", "roof:orientation", "roof:shape",
    )
    private val landAndWaterKeys = setOf(
        "admin_level", "boundary", "landuse", "leisure", "lock", "natural", "place", "waterway", "wood",
    )
    private val poiKeys = setOf(
        "amenity", "barrier", "historic", "man_made", "military", "power", "shop", "sport", "tourism",
    )

    fun write(selection: ContentSelection, output: Path) {
        val input = MapsforgeTagMapping::class.java.classLoader.getResourceAsStream("tag-mapping.xml")
            ?: error("Das interne Mapsforge-Tag-Mapping wurde nicht gefunden.")
        val factory = DocumentBuilderFactory.newInstance().apply {
            isNamespaceAware = true
            setFeature("http://apache.org/xml/features/disallow-doctype-decl", true)
            setFeature("http://xml.org/sax/features/external-general-entities", false)
            setFeature("http://xml.org/sax/features/external-parameter-entities", false)
            setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "")
            setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "")
        }
        val document = input.use { factory.newDocumentBuilder().parse(it) }
        val tags = document.getElementsByTagNameNS("http://mapsforge.org/tag-mapping", "osm-tag")
        for (index in tags.length - 1 downTo 0) {
            val tag = tags.item(index) as Element
            val section = tag.parentNode.localName
            if (!isIncluded(section, tag.getAttribute("key"), tag.getAttribute("value"), selection)) {
                tag.parentNode.removeChild(tag)
            }
        }
        val groups = listOf("pois", "ways").flatMap { name ->
            val nodes = document.getElementsByTagNameNS("http://mapsforge.org/tag-mapping", name)
            (0 until nodes.length).map(nodes::item)
        }
        groups.filter { group ->
            (0 until group.childNodes.length).none { child -> childNodeIsTag(group.childNodes.item(child)) }
        }.forEach { emptyGroup -> emptyGroup.parentNode.removeChild(emptyGroup) }

        Files.newOutputStream(output).use { outputStream ->
            TransformerFactory.newInstance().newTransformer().apply {
                setOutputProperty(OutputKeys.INDENT, "yes")
                setOutputProperty(OutputKeys.ENCODING, "UTF-8")
            }.transform(DOMSource(document), StreamResult(outputStream))
        }
    }

    private fun childNodeIsTag(node: org.w3c.dom.Node): Boolean =
        node.nodeType == org.w3c.dom.Node.ELEMENT_NODE && node.localName == "osm-tag"

    private fun isIncluded(section: String, key: String, value: String, selection: ContentSelection): Boolean {
        if (section == "pois") return selection.pointsOfInterest
        if (key == "id" || key == "area") return true
        if (key == "highway") {
            return if (value in pathHighwayValues) selection.pathsAndTrails else selection.roads
        }
        return when (key) {
            in buildingKeys -> selection.buildings
            in landAndWaterKeys -> selection.landAndWater
            in poiKeys -> selection.pointsOfInterest
            in pathKeys -> selection.pathsAndTrails
            in roadKeys -> selection.roads
            else -> selection.sourceMetadata
        }
    }
}
