package gaf3.core.jpa.query

import org.bson.Document
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.util.function.Function
import javax.persistence.criteria.CriteriaBuilder
import javax.persistence.criteria.Predicate
import javax.persistence.criteria.Root

class BSONQuery private constructor(private val query: Document) {

    // isValue
    // isArray
    // is Or
    // is And
//    fun dummy() {
//        val query: BasicQuery? = null
//    }

    // map: Function<T, R>, and: BinaryOperator<R>? = null, or: BinaryOperator<R>? = null): R {
    fun <T> evaluate(supplier: Function<QueryCondition, T>): List<T> {
        lateinit var eval: (Document, String?)-> Array<QueryCondition>

        eval = { rule, path ->
            rule.entries.map {
                if (path.isNullOrBlank() && it.value !is Document)
                    arrayOf(QueryCondition(it.key, it.value))
                else if (!path.isNullOrBlank() && Comparison.isComparison(it.key))
                    arrayOf(QueryCondition(path, it.value, Comparison.fromValue(it.key)))
                else if (it.value is Document)
                    eval(it.value as Document, it.key)
                else
                    emptyArray()
            }.reduce { acc, u ->
                arrayOf(*acc, *u)
            }
        }
        return eval(query, null).map { supplier.apply(it) }
    }

    fun matches(data: Map<String, String>): Boolean {
        val supplier = Function { cond: QueryCondition ->
            log.trace("matches {} field: {} <-> {}", cond.path, data[cond.path], cond)
            val dataValue = data[cond.path]
            val cv = compareValues(dataValue, cond.value as String?)
            when(cond.comparison) {
                Comparison.eq -> dataValue == cond.value
                Comparison.ne -> dataValue != cond.value
                Comparison.lt -> cv < 0
                Comparison.lte -> cv <= 0
                Comparison.gt -> cv > 0
                Comparison.gte -> cv >= 0
                Comparison.like -> dataValue?.contains(cond.value as String) ?: false
            }
        }

//        val collector = Collector.of(
//                Supplier<MutableList<Boolean>> { mutableListOf() },
//                BiConsumer { _: MutableList<Boolean>, _: Boolean ->  },
//                BinaryOperator<MutableList<Boolean>> { t, u -> t.addAll(u); t },
//                Function<MutableList<Boolean>, Boolean> { a -> a.all { it }})

        return evaluate(supplier).all { it }
    }

    inline fun <reified T> getPredicates(root: Root<T>, cb: CriteriaBuilder): List<Predicate> {
        val supplier = Function { cond: QueryCondition ->
            // TODO: 目前只支持字符串查询
            when (cond.comparison) {
                Comparison.eq -> cb.equal(root.get<String>(cond.path), cond.value)
                Comparison.ne -> cb.notEqual(root.get<String>(cond.path), cond.value)
                Comparison.lt -> cb.lessThan(root.get<String>(cond.path), cond.value as String)
                Comparison.lte -> cb.lessThanOrEqualTo(root.get<String>(cond.path), cond.value as String)
                Comparison.gt -> cb.greaterThan(root.get<String>(cond.path), cond.value as String)
                Comparison.gte -> cb.greaterThanOrEqualTo(root.get<String>(cond.path), cond.value as String)
                Comparison.like -> cb.like(root.get<String>(cond.path), cond.value as String)
                // else -> TODO()
            }
        }

        return evaluate(supplier)
    }

    companion object {
        val log: Logger = LoggerFactory.getLogger(BSONQuery::class.java)
        fun parse(bson: String):BSONQuery {
            return BSONQuery(Document.parse(bson))
        }

        fun checkData(bson: String): Boolean {
            return runCatching {
                Document.parse(bson)
                true
            }.getOrElse { false }
        }
    }
}

//fun main() {
//    // var json = """{ age : { @lt : 50 }, "accounts.balance" : { @gt : 1000.00 }, name: { @eq: "abc"}}""".replace('@', '$')
//    var json = """{ name: { @eq: "abc" } }""".replace('@', '$')
//    val doc = Document.parse(json)
//    doc.entries.forEach {
//        println("${it.key}: ${it.value is Document}")
//    }
//    doc.entries.forEach {
//        println("${it.key}: ${it.value}")
//    }
//
//    val query = BSONQuery.parse(json)
//    println(query.matches(mapOf("name" to "xxx")))
//    println(query.matches(mapOf("name" to "abc")))
//}