package moe.nea.firmament.features.texturepack

import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtByte
import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtDouble
import net.minecraft.nbt.NbtElement
import net.minecraft.nbt.NbtFloat
import net.minecraft.nbt.NbtInt
import net.minecraft.nbt.NbtList
import net.minecraft.nbt.NbtLong
import net.minecraft.nbt.NbtShort
import net.minecraft.nbt.NbtString
import moe.nea.firmament.util.extraAttributes

fun interface NbtMatcher {
    fun matches(nbt: NbtElement): Boolean

    object Parser {
        fun parse(jsonElement: JsonElement): NbtMatcher? {
            if (jsonElement is JsonPrimitive) {
                if (jsonElement.isString) {
                    val string = jsonElement.asString
                    return MatchStringExact(string)
                }
                if (jsonElement.isNumber) {
                    return MatchNumberExact(jsonElement.asLong) //TODO: parse generic number
                }
            }
            if (jsonElement is JsonObject) {
                var encounteredParser: NbtMatcher? = null
                for (entry in ExclusiveParserType.entries) {
                    val data = jsonElement[entry.key] ?: continue
                    if (encounteredParser != null) {
                        // TODO: warn
                        return null
                    }
                    encounteredParser = entry.parse(data) ?: return null
                }
                return encounteredParser
            }
            return null
        }

        enum class ExclusiveParserType(val key: String) {
            STRING("string") {
                override fun parse(element: JsonElement): NbtMatcher? {
                    return MatchString(StringMatcher.parse(element))
                }
            },
            INT("int") {
                override fun parse(element: JsonElement): NbtMatcher? {
                    return parseGenericNumber(element,
                                              { it.asInt },
                                              { (it as? NbtInt)?.intValue() },
                                              { a, b ->
                                                  if (a == b) Comparison.EQUAL
                                                  else if (a < b) Comparison.LESS_THAN
                                                  else Comparison.GREATER
                                              })
                }
            },
            FLOAT("float") {
                override fun parse(element: JsonElement): NbtMatcher? {
                    return parseGenericNumber(element,
                                              { it.asFloat },
                                              { (it as? NbtFloat)?.floatValue() },
                                              { a, b ->
                                                  if (a == b) Comparison.EQUAL
                                                  else if (a < b) Comparison.LESS_THAN
                                                  else Comparison.GREATER
                                              })
                }
            },
            DOUBLE("double") {
                override fun parse(element: JsonElement): NbtMatcher? {
                    return parseGenericNumber(element,
                                              { it.asDouble },
                                              { (it as? NbtDouble)?.doubleValue() },
                                              { a, b ->
                                                  if (a == b) Comparison.EQUAL
                                                  else if (a < b) Comparison.LESS_THAN
                                                  else Comparison.GREATER
                                              })
                }
            },
            LONG("long") {
                override fun parse(element: JsonElement): NbtMatcher? {
                    return parseGenericNumber(element,
                                              { it.asLong },
                                              { (it as? NbtLong)?.longValue() },
                                              { a, b ->
                                                  if (a == b) Comparison.EQUAL
                                                  else if (a < b) Comparison.LESS_THAN
                                                  else Comparison.GREATER
                                              })
                }
            },
            SHORT("short") {
                override fun parse(element: JsonElement): NbtMatcher? {
                    return parseGenericNumber(element,
                                              { it.asShort },
                                              { (it as? NbtShort)?.shortValue() },
                                              { a, b ->
                                                  if (a == b) Comparison.EQUAL
                                                  else if (a < b) Comparison.LESS_THAN
                                                  else Comparison.GREATER
                                              })
                }
            },
            BYTE("byte") {
                override fun parse(element: JsonElement): NbtMatcher? {
                    return parseGenericNumber(element,
                                              { it.asByte },
                                              { (it as? NbtByte)?.byteValue() },
                                              { a, b ->
                                                  if (a == b) Comparison.EQUAL
                                                  else if (a < b) Comparison.LESS_THAN
                                                  else Comparison.GREATER
                                              })
                }
            },
            ;

            abstract fun parse(element: JsonElement): NbtMatcher?
        }

        enum class Comparison {
            LESS_THAN, EQUAL, GREATER
        }

        inline fun <T : Any> parseGenericNumber(
            jsonElement: JsonElement,
            primitiveExtractor: (JsonPrimitive) -> T?,
            crossinline nbtExtractor: (NbtElement) -> T?,
            crossinline compare: (T, T) -> Comparison
        ): NbtMatcher? {
            if (jsonElement is JsonPrimitive) {
                val expected = primitiveExtractor(jsonElement) ?: return null
                return NbtMatcher {
                    val actual = nbtExtractor(it) ?: return@NbtMatcher false
                    compare(actual, expected) == Comparison.EQUAL
                }
            }
            if (jsonElement is JsonObject) {
                val minElement = jsonElement.getAsJsonPrimitive("min")
                val min = if (minElement != null) primitiveExtractor(minElement) ?: return null else null
                val minExclusive = jsonElement.get("minExclusive")?.asBoolean ?: false
                val maxElement = jsonElement.getAsJsonPrimitive("max")
                val max = if (maxElement != null) primitiveExtractor(maxElement) ?: return null else null
                val maxExclusive = jsonElement.get("maxExclusive")?.asBoolean ?: true
                if (min == null && max == null) return null
                return NbtMatcher {
                    val actual = nbtExtractor(it) ?: return@NbtMatcher false
                    if (max != null) {
                        val comp = compare(actual, max)
                        if (comp == Comparison.GREATER) return@NbtMatcher false
                        if (comp == Comparison.EQUAL && maxExclusive) return@NbtMatcher false
                    }
                    if (min != null) {
                        val comp = compare(actual, min)
                        if (comp == Comparison.LESS_THAN) return@NbtMatcher false
                        if (comp == Comparison.EQUAL && minExclusive) return@NbtMatcher false
                    }
                    return@NbtMatcher true
                }
            }
            return null

        }
    }

    class MatchNumberExact(val number: Long) : NbtMatcher {
        override fun matches(nbt: NbtElement): Boolean {
            return when (nbt) {
                is NbtByte -> nbt.byteValue().toLong() == number
                is NbtInt -> nbt.intValue().toLong() == number
                is NbtShort -> nbt.shortValue().toLong() == number
                is NbtLong -> nbt.longValue().toLong() == number
                else -> false
            }
        }

    }

    class MatchStringExact(val string: String) : NbtMatcher {
        override fun matches(nbt: NbtElement): Boolean {
            return nbt is NbtString && nbt.asString() == string
        }

        override fun toString(): String {
            return "MatchNbtStringExactly($string)"
        }
    }

    class MatchString(val string: StringMatcher) : NbtMatcher {
        override fun matches(nbt: NbtElement): Boolean {
            return nbt is NbtString && string.matches(nbt.asString())
        }

        override fun toString(): String {
            return "MatchNbtString($string)"
        }
    }
}

data class ExtraAttributesPredicate(
    val path: NbtPrism,
    val matcher: NbtMatcher,
) : FirmamentModelPredicate {

    object Parser : FirmamentModelPredicateParser {
        override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? {
            if (jsonElement !is JsonObject) return null
            val path = jsonElement.get("path") ?: return null
            val pathSegments = if (path is JsonArray) {
                path.map { (it as JsonPrimitive).asString }
            } else if (path is JsonPrimitive && path.isString) {
                path.asString.split(".")
            } else return null
            val matcher = NbtMatcher.Parser.parse(jsonElement.get("match") ?: jsonElement)
                ?: return null
            return ExtraAttributesPredicate(NbtPrism(pathSegments), matcher)
        }
    }

    override fun test(stack: ItemStack): Boolean {
        return path.access(stack.extraAttributes)
            .any { matcher.matches(it) }
    }
}

class NbtPrism(val path: List<String>) {
    override fun toString(): String {
        return "Prism($path)"
    }
    fun access(root: NbtElement): Collection<NbtElement> {
        var rootSet = mutableListOf(root)
        var switch = mutableListOf<NbtElement>()
        for (pathSegment in path) {
            if (pathSegment == ".") continue
            for (element in rootSet) {
                if (element is NbtList) {
                    if (pathSegment == "*")
                        switch.addAll(element)
                    val index = pathSegment.toIntOrNull() ?: continue
                    if (index !in element.indices) continue
                    switch.add(element[index])
                }
                if (element is NbtCompound) {
                    if (pathSegment == "*")
                        element.keys.mapTo(switch) { element.get(it)!! }
                    switch.add(element.get(pathSegment) ?: continue)
                }
            }
            val temp = switch
            switch = rootSet
            rootSet = temp
            switch.clear()
        }
        return rootSet
    }
}