package ru.dbotthepony.mc.otm.data

import com.mojang.serialization.Codec
import com.mojang.serialization.DataResult
import com.mojang.serialization.Dynamic
import com.mojang.serialization.DynamicOps
import com.mojang.serialization.JsonOps
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.advancements.critereon.DamagePredicate
import net.minecraft.advancements.critereon.DamageSourcePredicate
import net.minecraft.advancements.critereon.EntityPredicate
import net.minecraft.advancements.critereon.MinMaxBounds
import java.util.Optional
import kotlin.reflect.KProperty1

val DoublesCodec: Codec<MinMaxBounds.Doubles> = RecordCodecBuilder.create {
	it.group(
		Codec.DOUBLE.optionalFieldOf("min").forGetter { Optional.ofNullable(it.min) },
		Codec.DOUBLE.optionalFieldOf("max").forGetter { Optional.ofNullable(it.max) },
	).apply(it) { min, max ->
		if (min.isEmpty && max.isEmpty)
			MinMaxBounds.Doubles.ANY
		else if (min.isEmpty)
			MinMaxBounds.Doubles.atMost(max.get())
		else if (max.isEmpty)
			MinMaxBounds.Doubles.atLeast(min.get())
		else
			MinMaxBounds.Doubles.between(min.get(), max.get())
	}
}

private val dealtReceived: Codec<Pair<MinMaxBounds.Doubles, MinMaxBounds.Doubles>> = RecordCodecBuilder.create {
	it.group(
		DoublesCodec.fieldOf("dealt").forGetter { it.first },
		DoublesCodec.fieldOf("taken").forGetter { it.second },
	).apply(it, ::Pair)
}

val DamagePredicateCodec: Codec<DamagePredicate> = RecordCodecBuilder.create {
	it.group(
		dealtReceived.fieldOf("damage").forGetter { it.dealtDamage to it.takenDamage },
		Codec.BOOL.optionalFieldOf("blocked").forGetter { Optional.ofNullable(it.blocked) },
		Codec.PASSTHROUGH.xmap({ EntityPredicate.fromJson(it.cast(JsonOps.INSTANCE)) }, { Dynamic(JsonOps.INSTANCE, it.serializeToJson()) }).fieldOf("source_entity").forGetter { it.sourceEntity },
		Codec.PASSTHROUGH.xmap({ DamageSourcePredicate.fromJson(it.cast(JsonOps.INSTANCE)) }, { Dynamic(JsonOps.INSTANCE, it.serializeToJson()) }).fieldOf("type").forGetter { it.type },
	).apply(it) { damage, blocked, source, type -> DamagePredicate(damage.first, damage.second, source, blocked.orElse(null), type) }
}

fun <V, T1> simpleCodec(factory: (T1) -> V, field1: KProperty1<V, T1>, codec1: Codec<T1>): Codec<V> {
	return RecordCodecBuilder.create {
		it.group(
			codec1.fieldOf(field1.name).forGetter(field1::get)
		).apply(it, factory)
	}
}

fun <V, T1, T2> simpleCodec(factory: (T1, T2) -> V, field1: KProperty1<V, T1>, codec1: Codec<T1>, field2: KProperty1<V, T2>, codec2: Codec<T2>): Codec<V> {
	return RecordCodecBuilder.create {
		it.group(
			codec1.fieldOf(field1.name).forGetter(field1::get),
			codec2.fieldOf(field2.name).forGetter(field2::get),
		).apply(it, factory)
	}
}

fun <V, T1, T2, T3> simpleCodec(factory: (T1, T2, T3) -> V, field1: KProperty1<V, T1>, codec1: Codec<T1>, field2: KProperty1<V, T2>, codec2: Codec<T2>, field3: KProperty1<V, T3>, codec3: Codec<T3>): Codec<V> {
	return RecordCodecBuilder.create {
		it.group(
			codec1.fieldOf(field1.name).forGetter(field1::get),
			codec2.fieldOf(field2.name).forGetter(field2::get),
			codec3.fieldOf(field3.name).forGetter(field3::get),
		).apply(it, factory)
	}
}
