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 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.TileDamageConfig
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.ForkJoinTask
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<PlayerConfig>()
		private set

	var actorMovementParameters by Delegates.notNull<ActorMovementParameters>()
		private set

	var movementParameters by Delegates.notNull<MovementParameters>()
		private set

	var client by Delegates.notNull<ClientConfig>()
		private set

	var worldTemplate by Delegates.notNull<WorldTemplateConfig>()
		private set

	var terrestrialWorlds by Delegates.notNull<TerrestrialWorldsConfig>()
		private set

	var asteroidWorlds by Delegates.notNull<AsteroidWorldsConfig>()
		private set

	var dungeonWorlds by Delegates.notNull<ImmutableMap<String, DungeonWorldsConfig>>()
		private set

	var grassDamage by Delegates.notNull<TileDamageConfig>()
		private set

	var treeDamage by Delegates.notNull<TileDamageConfig>()
		private set

	var bushDamage by Delegates.notNull<TileDamageConfig>()
		private set

	var tileDamage by Delegates.notNull<TileDamageConfig>()
		private set

	var sky by Delegates.notNull<SkyGlobalConfig>()
		private set

	var universeServer by Delegates.notNull<UniverseServerConfig>()
		private set

	var worldServer by Delegates.notNull<WorldServerConfig>()
		private set

	var itemDrop by Delegates.notNull<ItemDropConfig>()
		private set

	var currencies by Delegates.notNull<ImmutableMap<String, CurrencyDefinition>>()
		private set

	var systemObjects by Delegates.notNull<ImmutableMap<String, SystemWorldObjectConfig>>()
		private set

	var systemWorld by Delegates.notNull<SystemWorldConfig>()
		private set

	var celestialBaseInformation by Delegates.notNull<CelestialBaseInformation>()
		private set

	var celestialConfig by Delegates.notNull<CelestialConfig>()
		private set

	var celestialNames by Delegates.notNull<CelestialNames>()
		private set

	var instanceWorlds by Delegates.notNull<ImmutableMap<String, InstanceWorldConfig>>()
		private set

	var itemParameters by Delegates.notNull<ItemGlobalConfig>()
		private set

	private var profanityFilterInternal by Delegates.notNull<ImmutableList<String>>()

	val profanityFilter: ImmutableSet<String> by lazy {
		// reverse "encryption"
		val words = ImmutableSet.Builder<String>()

		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()
	}

	private fun <T> load(path: String, accept: KMutableProperty0<T>, adapter: Lazy<TypeAdapter<T>>): Future<*> {
		val file = Starbound.loadJsonAsset(path)

		if (file == null) {
			LOGGER.fatal("$path does not exist or is not a file, expect bad things to happen!")
			return CompletableFuture.completedFuture(Unit)
		} else {
			return Starbound.EXECUTOR.submit {
				try {
					AssetPathStack("/") {
						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 <reified T> load(path: String, accept: KMutableProperty0<T>): Future<*> {
		return load(path, accept, lazy(LazyThreadSafetyMode.NONE) { Starbound.gson.getAdapter(T::class.java) })
	}

	fun load(): List<Future<*>> {
		val tasks = ArrayList<Future<*>>()

		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(load("/dungeon_worlds.config", ::dungeonWorlds, lazy { Starbound.gson.mapAdapter() }))
		tasks.add(load("/currencies.config", ::currencies, lazy { Starbound.gson.mapAdapter() }))
		tasks.add(load("/system_objects.config", ::systemObjects, lazy { Starbound.gson.mapAdapter() }))
		tasks.add(load("/instance_worlds.config", ::instanceWorlds, lazy { Starbound.gson.mapAdapter() }))

		tasks.add(load("/names/profanityfilter.config", ::profanityFilterInternal, lazy { Starbound.gson.listAdapter() }))

		return tasks
	}
}
