NestingGround.kt
package de.pflugradts.passbird.domain.service.password.tree
import de.pflugradts.kotlinextensions.MutableOption
import de.pflugradts.kotlinextensions.MutableOption.Companion.mutableOptionOf
import de.pflugradts.kotlinextensions.TryResult
import de.pflugradts.passbird.domain.model.egg.Egg
import de.pflugradts.passbird.domain.model.egg.EggIdFavorites
import de.pflugradts.passbird.domain.model.egg.EggIdMemory
import de.pflugradts.passbird.domain.model.egg.FavoriteMap
import de.pflugradts.passbird.domain.model.egg.MemoryMap
import de.pflugradts.passbird.domain.model.shell.EncryptedShell
import de.pflugradts.passbird.domain.model.shell.Shell
import de.pflugradts.passbird.domain.model.slot.Slot
import de.pflugradts.passbird.domain.service.eventhandling.EventRegistry
import de.pflugradts.passbird.domain.service.nest.NestStateView
import java.util.function.Predicate
class NestingGround constructor(
private val eggIdMemoryEnabled: Boolean,
private val passwordTreeAdapterPort: PasswordTreeAdapterPort,
private val nestStateView: NestStateView,
private val eventRegistry: EventRegistry,
) : EggRepository {
private val lazyFavorites: MutableOption<FavoriteMap> = mutableOptionOf()
private val lazyMemory: MutableOption<MemoryMap> = mutableOptionOf()
private val lazyEggs: MutableOption<MutableList<Egg>> = mutableOptionOf()
private var lastSyncedSnapshot: PasswordTreeState? = null
private val favorites: FavoriteMap get() = initializeIfEmpty().run { lazyFavorites.get() }
private val memory: MemoryMap get() = initializeIfEmpty().run { lazyMemory.get() }
private val eggs: MutableList<Egg> get() = initializeIfEmpty().run { lazyEggs.get() }
private val currentNestSlot get() = nestStateView.currentNestSlot()
private fun initializeIfEmpty() {
if (lazyEggs.isEmpty) {
val initialState = passwordTreeAdapterPort.restore()
lazyEggs.set(initialState.get().toList().toMutableList())
lazyFavorites.set(initialState.favorites())
lazyMemory.set(initialState.memory())
lazyEggs.get().forEach {
it.clearDomainEvents()
eventRegistry.register(it)
}
lastSyncedSnapshot = snapshot()
}
}
override fun add(egg: Egg) {
eventRegistry.register(egg)
eggs.add(egg)
updateMemory(egg, sync = false)
}
override fun delete(egg: Egg) {
eggs.remove(egg)
eventRegistry.deregister(egg)
}
override fun sync(): TryResult<Unit> {
discardFavoritesForEmptyNests()
val snapshot = snapshot()
return passwordTreeAdapterPort.sync(snapshot.toEggStreamSupplier())
.onSuccess { lastSyncedSnapshot = snapshot }
.onFailure { restoreLastSyncedSnapshot() }
}
override fun findAll(slot: Slot) = createEggStreamSupplier(slot).get()
override fun findAll() = createEggStreamSupplier(inNest(currentNestSlot)).get()
private fun createEggStreamSupplier(slot: Slot) = createEggStreamSupplier(inNest(slot))
private fun createEggStreamSupplier(predicate: Predicate<Egg>) = EggStreamSupplier({ eggs.stream().filter(predicate) })
override fun favorites() = favorites[currentNestSlot].get().copy()
override fun memory() = memory[currentNestSlot].get().copy()
override fun putFavorite(slot: Slot, encryptedShell: EncryptedShell) {
favorites[currentNestSlot].get().assign(slot, encryptedShell)
}
override fun discardFavorite(slot: Slot) {
favorites[currentNestSlot].get().discard(slot)
}
override fun discardFavorites(nestSlot: Slot, encryptedShell: EncryptedShell) {
favorites[nestSlot].get().discard(encryptedShell)
}
override fun discardFavorites(nestSlot: Slot) {
favorites[nestSlot].set(EggIdFavorites())
}
override fun renameFavorites(nestSlot: Slot, from: EncryptedShell, to: EncryptedShell) {
favorites[nestSlot].get().rename(from, to)
}
override fun updateMemory(mostRecentEgg: Egg, duplicate: EncryptedShell?, sync: Boolean) {
if (eggIdMemoryEnabled) {
memory[currentNestSlot].get().memorize(mostRecentEgg.viewEggId(), duplicate)
if (sync) sync()
}
}
private fun discardFavoritesForEmptyNests() {
nestStateView.snapshot().forEachIndexed { index, shell ->
if (shell.isEmpty) {
favorites[Slot.slotAt(index + 1)].set(EggIdFavorites())
}
}
}
private fun snapshot() = PasswordTreeState(
eggs = eggs.map(Egg::copy),
memory = memory.copyUsing(EggIdMemory::copy),
favorites = favorites.copyUsing(EggIdFavorites::copy),
nests = nestStateView.snapshot(),
)
private fun restoreLastSyncedSnapshot() {
lastSyncedSnapshot?.let { snapshot ->
eggs.forEach(eventRegistry::deregister)
eventRegistry.clearEvents()
lazyEggs.set(snapshot.eggs.map(Egg::copy).toMutableList())
lazyMemory.set(snapshot.memory.copyUsing(EggIdMemory::copy))
lazyFavorites.set(snapshot.favorites.copyUsing(EggIdFavorites::copy))
lazyEggs.get().forEach(eventRegistry::register)
}
}
}
private fun inNest(slot: Slot) = Predicate<Egg> { it.associatedNest() == slot }
private data class PasswordTreeState(
val eggs: List<Egg>,
val memory: MemoryMap,
val favorites: FavoriteMap,
val nests: List<Shell>,
) {
fun toEggStreamSupplier() = EggStreamSupplier({ eggs.map(Egg::copy).stream() }, memory, favorites, nests)
}