package ru.dbotthepony.kstarbound import com.google.gson.GsonBuilder import com.google.gson.JsonElement import com.google.gson.JsonObject import com.google.gson.TypeAdapter import com.google.gson.TypeAdapterFactory import com.google.gson.stream.JsonReader import org.apache.logging.log4j.LogManager import ru.dbotthepony.kommons.util.Either import ru.dbotthepony.kstarbound.defs.Json2Function import ru.dbotthepony.kstarbound.defs.JsonConfigFunction import ru.dbotthepony.kstarbound.defs.JsonFunction import ru.dbotthepony.kstarbound.defs.Species import ru.dbotthepony.kstarbound.defs.StatusEffectDefinition import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition import ru.dbotthepony.kstarbound.defs.item.impl.BackArmorItemDefinition import ru.dbotthepony.kstarbound.defs.item.impl.ChestArmorItemDefinition import ru.dbotthepony.kstarbound.defs.item.impl.CurrencyItemDefinition import ru.dbotthepony.kstarbound.defs.item.impl.FlashlightDefinition import ru.dbotthepony.kstarbound.defs.item.impl.HarvestingToolPrototype import ru.dbotthepony.kstarbound.defs.item.impl.HeadArmorItemDefinition import ru.dbotthepony.kstarbound.defs.item.impl.ItemDefinition import ru.dbotthepony.kstarbound.defs.item.impl.LegsArmorItemDefinition import ru.dbotthepony.kstarbound.defs.item.impl.LiquidItemDefinition import ru.dbotthepony.kstarbound.defs.item.impl.MaterialItemDefinition import ru.dbotthepony.kstarbound.defs.monster.MonsterSkillDefinition import ru.dbotthepony.kstarbound.defs.monster.MonsterTypeDefinition import ru.dbotthepony.kstarbound.defs.npc.NpcTypeDefinition import ru.dbotthepony.kstarbound.defs.npc.TenantDefinition import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition import ru.dbotthepony.kstarbound.defs.actor.player.TechDefinition import ru.dbotthepony.kstarbound.defs.animation.ParticleConfig import ru.dbotthepony.kstarbound.defs.projectile.ProjectileDefinition import ru.dbotthepony.kstarbound.defs.quest.QuestTemplate import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import ru.dbotthepony.kstarbound.defs.world.BushVariant import ru.dbotthepony.kstarbound.defs.world.GrassVariant import ru.dbotthepony.kstarbound.defs.world.TreeVariant import ru.dbotthepony.kstarbound.defs.world.terrain.BiomeDefinition import ru.dbotthepony.kstarbound.defs.world.terrain.TerrainSelectorFactory import ru.dbotthepony.kstarbound.defs.world.terrain.TerrainSelectorType import ru.dbotthepony.kstarbound.util.AssetPathStack import java.util.* import java.util.concurrent.CompletableFuture import java.util.concurrent.ExecutorService import java.util.concurrent.Future import java.util.function.Supplier import kotlin.collections.ArrayList object Registries { private val LOGGER = LogManager.getLogger() private val registriesInternal = ArrayList>() val registries: List> = Collections.unmodifiableList(registriesInternal) private val adapters = ArrayList() fun registerAdapters(gsonBuilder: GsonBuilder) { adapters.forEach { gsonBuilder.registerTypeAdapterFactory(it) } } val tiles = Registry("tiles").also(registriesInternal::add).also { adapters.add(it.adapter()) } val tileModifiers = Registry("tile modifiers").also(registriesInternal::add).also { adapters.add(it.adapter()) } val liquid = Registry("liquid").also(registriesInternal::add).also { adapters.add(it.adapter()) } val species = Registry("species").also(registriesInternal::add).also { adapters.add(it.adapter()) } val statusEffects = Registry("status effect").also(registriesInternal::add).also { adapters.add(it.adapter()) } val particles = Registry("particle").also(registriesInternal::add).also { adapters.add(it.adapter()) } val items = Registry("item").also(registriesInternal::add).also { adapters.add(it.adapter()) } val questTemplates = Registry("quest template").also(registriesInternal::add).also { adapters.add(it.adapter()) } val techs = Registry("tech").also(registriesInternal::add).also { adapters.add(it.adapter()) } val jsonFunctions = Registry("json function").also(registriesInternal::add).also { adapters.add(it.adapter()) } val json2Functions = Registry("json 2function").also(registriesInternal::add).also { adapters.add(it.adapter()) } val jsonConfigFunctions = Registry("json config function").also(registriesInternal::add).also { adapters.add(it.adapter()) } val npcTypes = Registry("npc type").also(registriesInternal::add).also { adapters.add(it.adapter()) } val projectiles = Registry("projectile").also(registriesInternal::add).also { adapters.add(it.adapter()) } val tenants = Registry("tenant").also(registriesInternal::add).also { adapters.add(it.adapter()) } val treasurePools = Registry("treasure pool").also(registriesInternal::add).also { adapters.add(it.adapter()) } val monsterSkills = Registry("monster skill").also(registriesInternal::add).also { adapters.add(it.adapter()) } val monsterTypes = Registry("monster type").also(registriesInternal::add).also { adapters.add(it.adapter()) } val worldObjects = Registry("world object").also(registriesInternal::add).also { adapters.add(it.adapter()) } val biomes = Registry("biome").also(registriesInternal::add).also { adapters.add(it.adapter()) } val terrainSelectors = Registry>("terrain selector").also(registriesInternal::add).also { adapters.add(it.adapter()) } val grassVariants = Registry("grass variant").also(registriesInternal::add).also { adapters.add(it.adapter()) } val treeStemVariants = Registry("tree stem variant").also(registriesInternal::add).also { adapters.add(it.adapter()) } val treeFoliageVariants = Registry("tree foliage variant").also(registriesInternal::add).also { adapters.add(it.adapter()) } val bushVariants = Registry("bush variant").also(registriesInternal::add).also { adapters.add(it.adapter()) } private fun key(mapper: (T) -> String): (T) -> Pair { return { mapper.invoke(it) to null } } private fun key(mapper: (T) -> String, mapperInt: (T) -> Int): (T) -> Pair { return { mapper.invoke(it) to mapperInt.invoke(it) } } fun validate(): CompletableFuture { val futures = ArrayList>() for (registry in registriesInternal) futures.add(CompletableFuture.supplyAsync { registry.validate() }) return CompletableFuture.allOf(*futures.toTypedArray()).thenApply { futures.all { it.get() } } } private inline fun loadRegistry( registry: Registry, files: List, noinline keyProvider: (T) -> Pair ): List> { val adapter by lazy { Starbound.gson.getAdapter(T::class.java) } val elementAdapter by lazy { Starbound.gson.getAdapter(JsonElement::class.java) } return files.map { listedFile -> Starbound.EXECUTOR.submit { try { AssetPathStack(listedFile.computeDirectory()) { // TODO: json patch support val elem = elementAdapter.read(listedFile.jsonReader()) val read = adapter.fromJsonTree(elem) val keys = keyProvider(read) registry.add { if (keys.second != null) registry.add(keys.first, keys.second!!, read, elem, listedFile) else registry.add(keys.first, read, elem, listedFile) } } } catch (err: Throwable) { LOGGER.error("Loading ${registry.name} definition file $listedFile", err) } } } } fun finishLoad() { registriesInternal.forEach { it.finishLoad() } } fun load(fileTree: Map>): List> { val tasks = ArrayList>() tasks.addAll(loadItemDefinitions(fileTree)) tasks.addAll(loadTerrainSelectors(fileTree["terrain"] ?: listOf())) tasks.addAll(loadRegistry(tiles, fileTree["material"] ?: listOf(), key(TileDefinition::materialName, TileDefinition::materialId))) tasks.addAll(loadRegistry(tileModifiers, fileTree["matmod"] ?: listOf(), key(MaterialModifier::modName, MaterialModifier::modId))) tasks.addAll(loadRegistry(liquid, fileTree["liquid"] ?: listOf(), key(LiquidDefinition::name, LiquidDefinition::liquidId))) tasks.addAll(loadRegistry(worldObjects, fileTree["object"] ?: listOf(), key(ObjectDefinition::objectName))) tasks.addAll(loadRegistry(statusEffects, fileTree["statuseffect"] ?: listOf(), key(StatusEffectDefinition::name))) tasks.addAll(loadRegistry(species, fileTree["species"] ?: listOf(), key(Species::kind))) tasks.addAll(loadRegistry(particles, fileTree["particle"] ?: listOf(), { (it.kind ?: throw NullPointerException("Missing 'kind' value")) to null })) tasks.addAll(loadRegistry(questTemplates, fileTree["questtemplate"] ?: listOf(), key(QuestTemplate::id))) tasks.addAll(loadRegistry(techs, fileTree["tech"] ?: listOf(), key(TechDefinition::name))) tasks.addAll(loadRegistry(npcTypes, fileTree["npctype"] ?: listOf(), key(NpcTypeDefinition::type))) tasks.addAll(loadRegistry(monsterSkills, fileTree["monsterskill"] ?: listOf(), key(MonsterSkillDefinition::name))) tasks.addAll(loadRegistry(biomes, fileTree["biome"] ?: listOf(), key(BiomeDefinition::name))) tasks.addAll(loadRegistry(grassVariants, fileTree["grass"] ?: listOf(), key(GrassVariant.Data::name))) tasks.addAll(loadRegistry(treeStemVariants, fileTree["modularstem"] ?: listOf(), key(TreeVariant.StemData::name))) tasks.addAll(loadRegistry(treeFoliageVariants, fileTree["modularfoliage"] ?: listOf(), key(TreeVariant.FoliageData::name))) tasks.addAll(loadRegistry(bushVariants, fileTree["bush"] ?: listOf(), key(BushVariant.Data::name))) tasks.addAll(loadCombined(jsonFunctions, fileTree["functions"] ?: listOf())) tasks.addAll(loadCombined(json2Functions, fileTree["2functions"] ?: listOf())) tasks.addAll(loadCombined(jsonConfigFunctions, fileTree["configfunctions"] ?: listOf())) tasks.addAll(loadCombined(treasurePools, fileTree["treasurepools"] ?: listOf()) { name = it }) return tasks } private fun loadItemDefinitions(files: Map>): List> { val fileMap = mapOf( "item" to ItemDefinition::class.java, "currency" to CurrencyItemDefinition::class.java, "liqitem" to LiquidItemDefinition::class.java, "matitem" to MaterialItemDefinition::class.java, "flashlight" to FlashlightDefinition::class.java, "harvestingtool" to HarvestingToolPrototype::class.java, "head" to HeadArmorItemDefinition::class.java, "chest" to ChestArmorItemDefinition::class.java, "legs" to LegsArmorItemDefinition::class.java, "back" to BackArmorItemDefinition::class.java, ) val tasks = ArrayList>() val objects = Starbound.gson.getAdapter(JsonObject::class.java) for ((ext, clazz) in fileMap) { val fileList = files[ext] ?: continue val adapter by lazy { Starbound.gson.getAdapter(clazz) } for (listedFile in fileList) { tasks.add(Starbound.EXECUTOR.submit { try { val json = objects.read(listedFile.jsonReader()) val def = AssetPathStack(listedFile.computeDirectory()) { adapter.fromJsonTree(json) } items.add { items.add(def.itemName, def, json, listedFile) } } catch (err: Throwable) { LOGGER.error("Loading item definition file $listedFile", err) } }) } } return tasks } private inline fun loadCombined(registry: Registry, files: Collection, noinline transform: T.(String) -> Unit = {}): List> { val adapter by lazy { Starbound.gson.getAdapter(T::class.java) } val elementAdapter by lazy { Starbound.gson.getAdapter(JsonObject::class.java) } return files.map { listedFile -> Starbound.EXECUTOR.submit { try { // TODO: json patch support val json = elementAdapter.read(JsonReader(listedFile.reader()).also { it.isLenient = true }) for ((k, v) in json.entrySet()) { try { val value = adapter.fromJsonTree(v) transform(value, k) registry.add { registry.add(k, value, v, listedFile) } } catch (err: Exception) { LOGGER.error("Loading ${registry.name} definition $k from file $listedFile", err) } } } catch (err: Exception) { LOGGER.error("Loading ${registry.name} definition $listedFile", err) } } } } private fun loadTerrainSelectors(files: Collection): List> { return files.map { listedFile -> Starbound.EXECUTOR.submit { try { val factory = TerrainSelectorType.createFactory(Starbound.gson.getAdapter(JsonObject::class.java).read(JsonReader(listedFile.reader()).also { it.isLenient = true })) terrainSelectors.add { terrainSelectors.add(factory.name, factory) } } catch (err: Exception) { LOGGER.error("Loading terrain selector $listedFile", err) } } } } }