package ru.dbotthepony.kstarbound.defs

import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import com.google.common.collect.ImmutableSet
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonNull
import com.google.gson.JsonObject
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.client.render.RenderLayer
import ru.dbotthepony.kstarbound.defs.actor.StatModifier
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
import ru.dbotthepony.kstarbound.fromJson
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.json.mergeJson
import ru.dbotthepony.kstarbound.math.AABB
import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNIT
import ru.dbotthepony.kstarbound.world.physics.Poly
import java.util.concurrent.CompletableFuture
import java.util.function.Function

@JsonFactory
data class ProjectileDefinition(
	val projectileName: String,
	val description: String = "",
	val boundBox: AABB = AABB.withSide(Vector2d.ZERO, 5.0, 5.0),
	val speed: Double = 50.0,
	val acceleration: Double = 0.0,
	val power: Double = 1.0,
	@Deprecated("", replaceWith = ReplaceWith("this.actualDamagePoly"))
	val damagePoly: Poly? = null,
	val piercing: Boolean = false,
	val falldown: Boolean = false,
	val bounces: Int = 0,
	val actionOnCollide: JsonArray = JsonArray(),
	val actionOnReap: JsonArray = JsonArray(),
	val actionOnHit: JsonArray = JsonArray(),
	val actionOnTimeout: JsonArray = JsonArray(),
	val periodicActions: JsonArray = JsonArray(),
	val image: SpriteReference,
	val frameNumber: Int = 1,
	val animationCycle: Double = 1.0,
	val animationLoops: Boolean = true,
	val windupFrames: Int = 0,
	val intangibleWindup: Boolean = false,
	val winddownFrames: Int = 0,
	val intangibleWinddown: Boolean = false,
	val flippable: Boolean = false,
	val orientationLocked: Boolean = false,
	val fullbright: Boolean = false,
	val renderLayer: RenderLayer.Point = RenderLayer.Point(RenderLayer.Projectile),
	val lightColor: RGBAColor = RGBAColor.TRANSPARENT_BLACK,
	val lightPosition: Vector2d = Vector2d.ZERO,
	val pointLight: Boolean = false,
	val persistentAudio: AssetPath = AssetPath(""),
	val damageTeam: EntityDamageTeam? = null,

	// Initialize timeToLive after animationCycle so we can have the default be
	// based on animationCycle
	val timeToLive: Double = if (animationLoops) animationCycle else 5.0,
	val damageKindImage: AssetPath = AssetPath(""),
	val damageKind: String = "",
	val damageType: String = "Damage",
	val damageRepeatGroup: String? = null,
	val damageRepeatTimeout: Double? = null,
	val statusEffects: ImmutableList<EphemeralStatusEffect> = ImmutableList.of(),
	val emitters: ImmutableSet<String> = ImmutableSet.of(),
	val hydrophobic: Boolean = false,
	val rayCheckToSource: Boolean = false,
	val knockback: Double = 0.0,
	val knockbackDirectional: Boolean = false,
	val onlyHitTerrain: Boolean = false,
	val clientEntityMode: ClientEntityMode = ClientEntityMode.CLIENT_MASTER_ALLOWED,
	val masterOnly: Boolean = false,
	val scripts: ImmutableList<AssetPath> = ImmutableList.of(),
	val physicsForces: ImmutableMap<String, PhysicsForceRegion> = ImmutableMap.of(),
	val physicsCollisions: ImmutableMap<String, PhysicsMovingCollision> = ImmutableMap.of(),
	val persistentStatusEffects: ImmutableList<Either<String, StatModifier>> = ImmutableList.of(),
	val statusEffectArea: Poly = Poly.EMPTY,

	val physicsType: String = "default",

	@Deprecated("", replaceWith = ReplaceWith("this.actualMovementSettings"))
	val movementSettings: JsonObject = JsonObject(),
) {
	val actualDamagePoly = if (damagePoly != null) damagePoly * (1.0 / PIXELS_IN_STARBOUND_UNIT) else null

	val actualMovementSettings: CompletableFuture<MovementParameters> = Starbound
		.loadJsonAsset("/projectiles/physics.config:$physicsType")
		.thenApplyAsync(Function {
			Starbound.gson.fromJson(mergeJson(it?.deepCopy() ?: JsonNull.INSTANCE, movementSettings))
		}, Starbound.EXECUTOR)

	init {
		actualMovementSettings.exceptionally {
			LOGGER.error("Exception loading movement parameters for projectile with physics type $physicsType. Is it missing from /projectiles/physics.config?", it)
			null
		}
	}

	companion object {
		private val LOGGER = LogManager.getLogger()
	}
}
