package de.kif.common

import kotlinx.serialization.Serializable

@Serializable
data class SearchElement(
    val fields: Map<String, String> = emptyMap(),
    val flags: Map<String, Boolean> = emptyMap(),
    val numbers: Map<String, Double> = emptyMap()
) {

    fun stringify(): String {
        return Message.json.stringify(serializer(), this)
    }

    companion object {
        fun parse(data: String): SearchElement {
            return Message.json.parse(serializer(), data)
        }
    }
}

object Search {

    private val regex = """
        ((\w+)\s?[:=]\s?)?
        ((\[.*]|\+\w+|-\w+|!\w+)|
        ((\w+)\s?([<=>]+)\s?(\d+))|
        (\w+|"(.*)"))
    """.trimIndent().replace("\n", "").toRegex()

    fun match(search: String, element: SearchElement): Boolean {
        val matches = regex.findAll(search)

        val fields = mutableMapOf<String, String>()
        val flags = mutableMapOf<String, Boolean>()
        val numbers = mutableMapOf<String, ClosedRange<Double>>()

        for (match in matches) {
            val name = match.groups[2]?.value ?: ""
            val field = match.groups[10]?.value ?: match.groups[9]?.value
            val flag = match.groups[4]?.value
            val numberName = match.groups[6]?.value
            val numberRelation = match.groups[7]?.value
            val numberDigits = match.groups[8]?.value?.toDoubleOrNull()

            if (flag != null) {
                val h = flag.replace("[\\[\\]+!\\-]".toRegex(), "")
                val b = ("-" !in flag && "!" !in flag)
                flags[h] = b
            } else if (numberName != null && numberRelation != null && numberDigits != null) {
                when (numberRelation) {
                    "<" -> numbers[numberName] = Double.NEGATIVE_INFINITY..(numberDigits - Double.MIN_VALUE)
                    "<=" -> numbers[numberName] = Double.NEGATIVE_INFINITY..numberDigits
                    "==" -> numbers[numberName] = numberDigits..numberDigits
                    ">" -> numbers[numberName] = (numberDigits + Double.MIN_VALUE)..Double.POSITIVE_INFINITY
                    ">=" -> numbers[numberName] = numberDigits..Double.POSITIVE_INFINITY
                }
            } else if (field != null) {
                val old = fields[name]
                fields[name] = if (old == null) field else "$old $field"
            }
        }

        val fieldRadio = if (fields.isEmpty()) 1.0 else fields.count { (searchKey, searchValue) ->
            for ((elementKey, elementValue) in element.fields) {
                if (elementKey.contains(searchKey, true) && elementValue.contains(searchValue, true)) {
                    return@count true
                }
            }
            for ((elementKey, elementValue) in element.flags) {
                if (elementValue && elementKey.contains(searchValue, true)) {
                    return@count true
                }
            }
            for ((elementKey, _) in element.numbers) {
                if (elementKey.contains(searchValue, true)) {
                    return@count true
                }
            }
            false
        } / fields.size.toDouble()

        for ((searchKey, searchValue) in flags) {
            for ((elementKey, elementValue) in element.flags) {
                if (elementKey.contains(searchKey, true) && searchValue != elementValue)
                    return false
            }
        }

        for ((searchKey, searchValue) in numbers) {
            for ((elementKey, elementValue) in element.numbers) {
                if (elementKey.contains(searchKey, true) && elementValue !in searchValue)
                    return false
            }
        }

        //println("$fieldRadio (${fieldRadio >= 0.5}) for $element")
        return fieldRadio >= 0.5
    }
}

/*
fun main() {
    val element = SearchElement(
        mapOf(
            "name" to "lorem"
        ), mapOf(
            "beamer" to true,
            "room" to true,
            "day" to false
        ), mapOf(
            "places" to 500.0
        )
    )
    println(Search.match("""search text data:"text to search" name = hans""", element))

    println(Search.match("""lorem [beamer] places >= 100 +room -day""", element))
}
*/
