package dev.inmo.micro_utils.repos.onetomany import android.database.sqlite.SQLiteOpenHelper import androidx.core.content.contentValuesOf import dev.inmo.micro_utils.common.mapNotNullA import dev.inmo.micro_utils.pagination.FirstPagePagination import dev.inmo.micro_utils.pagination.Pagination import dev.inmo.micro_utils.pagination.PaginationResult import dev.inmo.micro_utils.pagination.createPaginationResult import dev.inmo.micro_utils.pagination.utils.reverse import dev.inmo.micro_utils.repos.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.runBlocking import kotlinx.serialization.KSerializer import kotlinx.serialization.json.Json private val internalSerialFormat = Json { ignoreUnknownKeys = true } class OneToManyAndroidRepo( private val tableName: String, private val keySerializer: KSerializer, private val valueSerializer: KSerializer, private val helper: SQLiteOpenHelper ) : OneToManyKeyValueRepo { private val _onNewValue: MutableSharedFlow> = MutableSharedFlow() override val onNewValue: Flow> = _onNewValue.asSharedFlow() private val _onValueRemoved: MutableSharedFlow> = MutableSharedFlow() override val onValueRemoved: Flow> = _onValueRemoved.asSharedFlow() private val _onDataCleared = MutableSharedFlow() override val onDataCleared: Flow = _onDataCleared.asSharedFlow() private val idColumnName = "id" private val valueColumnName = "value" private fun Key.asId() = internalSerialFormat.encodeToString(keySerializer, this) private fun Value.asValue() = internalSerialFormat.encodeToString(valueSerializer, this) private fun String.asValue(): Value = internalSerialFormat.decodeFromString(valueSerializer, this) private fun String.asKey(): Key = internalSerialFormat.decodeFromString(keySerializer, this) init { helper.blockingWritableTransaction { createTable( tableName, internalId to internalIdType, idColumnName to ColumnType.Text.NOT_NULLABLE, valueColumnName to ColumnType.Text.NULLABLE ) } } override suspend fun add(toAdd: Map>) { val added = mutableListOf>() helper.blockingWritableTransaction { for ((k, values) in toAdd) { values.forEach { v -> insert( tableName, null, contentValuesOf( idColumnName to k.asId(), valueColumnName to v.asValue() ) ).also { if (it != -1L) { added.add(k to v) } } } } } added.forEach { _onNewValue.emit(it) } } override suspend fun clear(k: Key) { helper.blockingWritableTransaction { delete(tableName, "$idColumnName=?", arrayOf(k.asId())) }.also { if (it > 0) { _onDataCleared.emit(k) } } } override suspend fun set(toSet: Map>) { val (clearedKeys, inserted) = helper.blockingWritableTransaction { toSet.mapNotNull { (k, _) -> if (delete(tableName, "$idColumnName=?", arrayOf(k.asId())) > 0) { k } else { null } } to toSet.flatMap { (k, values) -> values.map { v -> insert( tableName, null, contentValuesOf(idColumnName to k.asId(), valueColumnName to v.asValue()) ) k to v } } } clearedKeys.forEach { _onDataCleared.emit(it) } inserted.forEach { newPair -> _onNewValue.emit(newPair) } } override suspend fun contains(k: Key): Boolean = helper.blockingReadableTransaction { select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use { it.count > 0 } } override suspend fun contains(k: Key, v: Value): Boolean = helper.blockingReadableTransaction { select( tableName, selection = "$idColumnName=? AND $valueColumnName=?", selectionArgs = arrayOf(k.asId(), v.asValue()), limit = FirstPagePagination(1).limitClause() ).use { it.count > 0 } } override suspend fun count(): Long =helper.blockingReadableTransaction { select( tableName ).use { it.count } }.toLong() override suspend fun count(k: Key): Long = helper.blockingReadableTransaction { select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use { it.count } }.toLong() override suspend fun get( k: Key, pagination: Pagination, reversed: Boolean ): PaginationResult = count(k).let { count -> val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination } helper.blockingReadableTransaction { select( tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = resultPagination.limitClause() ).use { c -> mutableListOf().also { if (c.moveToFirst()) { do { it.add(c.getString(valueColumnName).asValue()) } while (c.moveToNext()) } } } }.createPaginationResult( pagination, count ) } override suspend fun keys( pagination: Pagination, reversed: Boolean ): PaginationResult = count().let { count -> val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination } helper.blockingReadableTransaction { select( tableName, limit = resultPagination.limitClause() ).use { c -> mutableListOf().also { if (c.moveToFirst()) { do { it.add(c.getString(idColumnName).asKey()) } while (c.moveToNext()) } } } }.createPaginationResult( pagination, count ) } override suspend fun keys( v: Value, pagination: Pagination, reversed: Boolean ): PaginationResult = count().let { count -> val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination } helper.blockingReadableTransaction { select( tableName, selection = "$valueColumnName=?", selectionArgs = arrayOf(v.asValue()), limit = resultPagination.limitClause() ).use { c -> mutableListOf().also { if (c.moveToFirst()) { do { it.add(c.getString(idColumnName).asKey()) } while (c.moveToNext()) } } } }.createPaginationResult( pagination, count ) } override suspend fun remove(toRemove: Map>) { helper.blockingWritableTransaction { toRemove.flatMap { (k, vs) -> vs.mapNotNullA { v -> if (delete(tableName, "$idColumnName=? AND $valueColumnName=?", arrayOf(k.asId(), v.asValue())) > 0) { k to v } else { null } } } }.forEach { (k, v) -> _onValueRemoved.emit(k to v) } } }