package ru.dbotthepony.kstarbound import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableSet import com.google.gson.TypeAdapter import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import kotlinx.coroutines.future.asCompletableFuture import kotlinx.coroutines.future.await import kotlinx.coroutines.launch import org.apache.logging.log4j.LogManager import ru.dbotthepony.kstarbound.defs.ActorMovementParameters import ru.dbotthepony.kstarbound.defs.ClientConfig import ru.dbotthepony.kstarbound.defs.CurrencyDefinition import ru.dbotthepony.kstarbound.defs.MovementParameters import ru.dbotthepony.kstarbound.defs.UniverseServerConfig import ru.dbotthepony.kstarbound.defs.WorldServerConfig import ru.dbotthepony.kstarbound.defs.actor.player.PlayerConfig import ru.dbotthepony.kstarbound.defs.item.ItemDropConfig import ru.dbotthepony.kstarbound.defs.item.ItemGlobalConfig import ru.dbotthepony.kstarbound.defs.tile.TileDamageParameters import ru.dbotthepony.kstarbound.defs.world.TerrestrialWorldsConfig import ru.dbotthepony.kstarbound.defs.world.AsteroidWorldsConfig import ru.dbotthepony.kstarbound.defs.world.CelestialBaseInformation import ru.dbotthepony.kstarbound.defs.world.CelestialConfig import ru.dbotthepony.kstarbound.defs.world.CelestialNames import ru.dbotthepony.kstarbound.defs.world.DungeonWorldsConfig import ru.dbotthepony.kstarbound.defs.world.InstanceWorldConfig import ru.dbotthepony.kstarbound.defs.world.SkyGlobalConfig import ru.dbotthepony.kstarbound.defs.world.SystemWorldConfig import ru.dbotthepony.kstarbound.defs.world.SystemWorldObjectConfig import ru.dbotthepony.kstarbound.defs.world.WorldTemplateConfig import ru.dbotthepony.kstarbound.json.listAdapter import ru.dbotthepony.kstarbound.json.mapAdapter import ru.dbotthepony.kstarbound.util.AssetPathStack import java.util.concurrent.CompletableFuture import java.util.concurrent.Future import kotlin.properties.Delegates import kotlin.reflect.KMutableProperty0 object Globals { private val LOGGER = LogManager.getLogger() var player by Delegates.notNull() private set var actorMovementParameters by Delegates.notNull() private set var movementParameters by Delegates.notNull() private set var client by Delegates.notNull() private set var worldTemplate by Delegates.notNull() private set var terrestrialWorlds by Delegates.notNull() private set var asteroidWorlds by Delegates.notNull() private set var dungeonWorlds by Delegates.notNull>() private set var grassDamage by Delegates.notNull() private set var treeDamage by Delegates.notNull() private set var bushDamage by Delegates.notNull() private set var tileDamage by Delegates.notNull() private set var sky by Delegates.notNull() private set var universeServer by Delegates.notNull() private set var worldServer by Delegates.notNull() private set var itemDrop by Delegates.notNull() private set var currencies by Delegates.notNull>() private set var systemObjects by Delegates.notNull>() private set var systemWorld by Delegates.notNull() private set var celestialBaseInformation by Delegates.notNull() private set var celestialConfig by Delegates.notNull() private set var celestialNames by Delegates.notNull() private set var instanceWorlds by Delegates.notNull>() private set var itemParameters by Delegates.notNull() private set private var profanityFilterInternal by Delegates.notNull>() val profanityFilter: ImmutableSet by lazy { // reverse "encryption" val words = ImmutableSet.Builder() for (word in profanityFilterInternal) { val chars = CharArray(word.length) for (i in word.indices) { var c = word[i] if ((c >= 'a' + 13 && c <= 'm' + 13) || (c >= 'A' + 13 && c <= 'M' + 13)) c -= 13 else if ((c >= 'n' - 13 && c <= 'z' - 13) || (c >= 'N' - 13 && c <= 'Z' - 13)) c += 13 chars[i] = c } words.add(String(chars)) } words.build() } val onLoadedFuture = CompletableFuture() private suspend fun load(path: String, accept: KMutableProperty0, adapter: Lazy>) { val file = Starbound.loadJsonAsset(path).await() if (file == null) { LOGGER.fatal("$path does not exist or is not a file, expect bad things to happen!") } else { try { AssetPathStack(path) { accept.set(adapter.value.fromJsonTree(file)) } } catch (err: Throwable) { LOGGER.fatal("Error while reading $path, expect bad things to happen!", err) throw err } } } private inline fun load(path: String, accept: KMutableProperty0): CompletableFuture<*> { return Starbound.GLOBAL_SCOPE.launch { load(path, accept, lazy(LazyThreadSafetyMode.NONE) { Starbound.gson.getAdapter(T::class.java) }) }.asCompletableFuture() } private inline fun mapAdapter(filename: String): Lazy>> { val parent by lazy { Starbound.gson.getAdapter(T::class.java) } return lazy(LazyThreadSafetyMode.NONE) { object : TypeAdapter>() { override fun write(out: JsonWriter, value: ImmutableMap) { TODO("Not yet implemented") } override fun read(`in`: JsonReader): ImmutableMap { `in`.beginObject() val builder = ImmutableMap.Builder() while (`in`.hasNext()) { val name = `in`.nextName() val element = Starbound.ELEMENTS_ADAPTER.read(`in`) try { builder.put(name, parent.fromJsonTree(element)) } catch (err: Throwable) { LOGGER.error("Exception reading element at $name from $filename; it will not be loaded", err) } } `in`.endObject() return builder.build() } } } } fun load(): List> { val tasks = ArrayList>() tasks.add(load("/default_actor_movement.config", ::actorMovementParameters)) tasks.add(load("/default_movement.config", ::movementParameters)) tasks.add(load("/client.config", ::client)) tasks.add(load("/terrestrial_worlds.config", ::terrestrialWorlds)) tasks.add(load("/asteroids_worlds.config", ::asteroidWorlds)) tasks.add(load("/world_template.config", ::worldTemplate)) tasks.add(load("/sky.config", ::sky)) tasks.add(load("/universe_server.config", ::universeServer)) tasks.add(load("/worldserver.config", ::worldServer)) tasks.add(load("/player.config", ::player)) tasks.add(load("/systemworld.config", ::systemWorld)) tasks.add(load("/itemdrop.config", ::itemDrop)) tasks.add(load("/celestial.config", ::celestialBaseInformation)) tasks.add(load("/celestial.config", ::celestialConfig)) tasks.add(load("/celestial/names.config", ::celestialNames)) tasks.add(load("/items/defaultParameters.config", ::itemParameters)) tasks.add(load("/plants/grassDamage.config", ::grassDamage)) tasks.add(load("/plants/treeDamage.config", ::treeDamage)) tasks.add(load("/plants/bushDamage.config", ::bushDamage)) tasks.add(load("/tiles/defaultDamage.config", ::tileDamage)) tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/dungeon_worlds.config", ::dungeonWorlds, mapAdapter("/dungeon_worlds.config")) }.asCompletableFuture()) tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/currencies.config", ::currencies, mapAdapter("/currencies.config")) }.asCompletableFuture()) tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/system_objects.config", ::systemObjects, mapAdapter("/system_objects.config")) }.asCompletableFuture()) tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/instance_worlds.config", ::instanceWorlds, mapAdapter("/instance_worlds.config")) }.asCompletableFuture()) tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/names/profanityfilter.config", ::profanityFilterInternal, lazy { Starbound.gson.listAdapter() }) }.asCompletableFuture()) CompletableFuture.allOf(*tasks.toTypedArray()).thenApply { onLoadedFuture.complete(Unit) }.exceptionally { onLoadedFuture.completeExceptionally(it) } return tasks } }