HashMapStore.kt

package com.hexagontk.store.hashmap

import com.hexagontk.core.filterNotEmpty
import com.hexagontk.store.Store
import kotlin.reflect.KClass
import kotlin.reflect.KProperty1

class HashMapStore<T : Any, K : Any>(
    override val type: KClass<T>,
    override val key: KProperty1<T, K>,
    override val name: String = type.java.simpleName,
    private val store: HashMap<K, Map<String, Any>> = hashMapOf(),
    override val encoder: (T) -> Map<String, *>,
    override val decoder: (Map<String, *>) -> T,
) : Store<T, K> {

    override fun insertOne(instance: T): K {
        store[key.get(instance)] = map(instance)
        return key.get(instance)
    }

    override fun insertMany(instances: List<T>): List<K> {
        instances.forEach {
            store[key.get(it)] = map(it)
        }

        return instances.map { key.get(it) }
    }

    override fun saveOne(instance: T): K? {
        if (store.containsKey(key.get(instance))) {
            store[key.get(instance)] = map(instance)
            return null
        }

        store[key.get(instance)] = map(instance)
        @Suppress("UNCHECKED_CAST")
        return fromStore(key.get(instance)) as K
    }

    override fun saveMany(instances: List<T>): List<K?> {
        return instances.map(::saveOne)
    }

    override fun replaceOne(instance: T): Boolean =
        store.replace(key.get(instance), map(instance)) != null


    override fun replaceMany(instances: List<T>): List<T> =
        instances.mapNotNull { if (replaceOne(it)) it else null }


    override fun updateOne(key: K, updates: Map<String, *>): Boolean {
        if (!store.containsKey(key)) return false

        val instance = store[key]!!.toMutableMap()

        updates
            .filterNotEmpty()
            .forEach {
                instance[it.key] = toStore(it.value)
            }

        return store.replace(key, instance) != null
    }

    override fun updateMany(filter: Map<String, *>, updates: Map<String, *>): Long {
        val filteredInstances = store.filter(filter)

        return filteredInstances.map { updateOne(it, updates) }.count { it }.toLong()
    }

    override fun deleteOne(id: K): Boolean =
        store.remove(id) != null

    override fun deleteMany(filter: Map<String, *>): Long {
        val filteredInstances = store.filter(filter)

        return filteredInstances.map { deleteOne(it) }.count { it }.toLong()
    }

    override fun findOne(key: K): T? {
        val result = store[key]
        return result?.let { fromStore(result) }
    }

    override fun findOne(key: K, fields: List<String>): Map<String, *>? {
        val instance = store[key]

        return if (instance == null) null else fields.associateWith { instance[it] }
    }

    override fun findMany(
        filter: Map<String, *>,
        limit: Int?,
        skip: Int?,
        sort: Map<String, Boolean>
    ): List<T> {
        val filteredKeys = store.filter(filter)
        val filteredInstances = filteredKeys.map { store[it]!! }

        @Suppress("UNCHECKED_CAST")
        return filteredInstances
            .sort(sort)
            .paginate(skip ?: 0, limit ?: filteredKeys.size)
            .map { fromStore(it as Map<String, Any>) }
    }

    override fun findMany(
        filter: Map<String, *>,
        fields: List<String>,
        limit: Int?,
        skip: Int?,
        sort: Map<String, Boolean>
    ): List<Map<String, *>> {
        val filteredInstances = store.filter(filter)
        val result = filteredInstances.mapNotNull { findOne(it, fields) }

        return result
            .paginate(skip ?: 0, limit ?: result.size)
            .sort(sort)
    }

    override fun count(filter: Map<String, *>): Long =
        store.filter(filter).size.toLong()

    override fun drop() =
        store.clear()

    private fun map(instance: T): Map<String, Any> = toStore(instance)

    private fun HashMap<K, Map<String, Any>>.filter(filter: Map<String, *>): List<K> =
        filter { it.value.containsValues(filter) }
            .map { it.key }

    private fun Map<String, Any>.containsValues(filter: Map<String, *>): Boolean =
        filter.all {
            when (val value = it.value) {
                is List<*> -> value.contains(this[it.key])
                else -> value == this[it.key]
            }
        }

    private fun List<Map<String, *>>.paginate(skip: Int, limit: Int): List<Map<String, *>> =
        let {
            var endIndex = skip + limit
            if (endIndex > this.size) endIndex = this.size

            this.subList(skip, endIndex)
        }

    // TODO: Add sorting functionality (now only sorts by first field)
    private fun List<Map<String, *>>.sort(sortFields: Map<String, Boolean>): List<Map<String, *>> =
        if (sortFields.isEmpty())
            this
        else
            sortedBy {
                val firstSortField = sortFields.entries.first()
                val sortingValue = it[firstSortField.key]
                @Suppress("UNCHECKED_CAST")
                if (sortingValue is Comparable<*>)
                    sortingValue as? Comparable<Any>
                else
                    error("Not comparable value")
            }

    private fun toStore(instance: T): Map<String, Any>  =
        encoder(instance)
            .filterNotEmpty()
            .mapKeys { it.key.toString() }
            .mapValues { it.value }

    private fun toStore(value: Any): Any = value

    private fun fromStore(map: Map<String, Any>): T =
        decoder(map.filterNotEmpty())

    private fun fromStore(value: Any): Any =
        value
}