package ru.dbotthepony.mc.otm.network

import java.util.function.Function
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.player.Player
import net.minecraftforge.network.NetworkDirection
import net.minecraftforge.network.NetworkEvent
import net.minecraftforge.network.NetworkRegistry
import net.minecraftforge.network.PacketDistributor
import net.minecraftforge.network.simple.SimpleChannel
import ru.dbotthepony.mc.otm.NULLABLE_MINECRAFT_SERVER
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import java.math.BigDecimal
import java.math.BigInteger
import java.util.Optional
import java.util.concurrent.CompletableFuture
import java.util.function.BiConsumer
import java.util.function.Supplier
import kotlin.reflect.KClass

fun Supplier<NetworkEvent.Context>.enqueueWork(lambda: Runnable): CompletableFuture<Void> = get().enqueueWork(lambda)
fun Supplier<NetworkEvent.Context>.enqueueWork(lambda: () -> Unit): CompletableFuture<Void> = get().enqueueWork(lambda)
var Supplier<NetworkEvent.Context>.packetHandled: Boolean
	get() = get().packetHandled
	set(value) { get().packetHandled = value }

val Supplier<NetworkEvent.Context>.sender: ServerPlayer?
	get() = get().sender

fun FriendlyByteBuf.writeDecimal(value: BigDecimal) {
	writeInt(value.scale())
	writeByteArray(value.unscaledValue().toByteArray())
}

fun FriendlyByteBuf.readDecimal(): BigDecimal {
	val scale = readInt()
	val bytes = readByteArray()

	return BigDecimal(BigInteger(bytes), scale)
}

interface MatteryPacket {
	fun write(buff: FriendlyByteBuf)
	fun play(context: Supplier<NetworkEvent.Context>)
}

abstract class MatteryNetworkChannel(val version: String, val name: String) {
	val channel: SimpleChannel = NetworkRegistry.newSimpleChannel(
		ResourceLocation(OverdriveThatMatters.MOD_ID, name),
		{ version },
		{ it == version },
		{ it == version },
	)

	fun sendToServer(packet: Any) = channel.sendToServer(packet)

	fun send(ply: Player, packet: Any) {
		if (ply is ServerPlayer) {
			channel.send(PacketDistributor.PLAYER.with { ply }, packet)
		}
	}

	fun sendTracking(entity: Entity, packet: Any) {
		if ((NULLABLE_MINECRAFT_SERVER?.playerCount ?: 0) <= 0) {
			return
		}

		channel.send(PacketDistributor.TRACKING_ENTITY.with { entity }, packet)
	}

	fun sendTrackingAndSelf(entity: Entity, packet: Any) {
		if ((NULLABLE_MINECRAFT_SERVER?.playerCount ?: 0) <= 0) {
			return
		}

		channel.send(PacketDistributor.TRACKING_ENTITY_AND_SELF.with { entity }, packet)
	}

	fun send(distributor: PacketDistributor.PacketTarget, packet: Any) = channel.send(distributor, packet)

	private var nextNetworkPacketID = 0

	fun <T> add(
		packetClass: Class<T>,
		writer: BiConsumer<T, FriendlyByteBuf>,
		reader: Function<FriendlyByteBuf, T>,
		handler: BiConsumer<T, Supplier<NetworkEvent.Context>>,
		direction: NetworkDirection? = null
	) {
		if (nextNetworkPacketID >= 256) {
			throw IndexOutOfBoundsException("Network message ID overflow!")
		}

		@Suppress("INACCESSIBLE_TYPE")
		channel.registerMessage(nextNetworkPacketID++, packetClass, writer, reader, handler, if (direction == null) Optional.empty() else Optional.of(direction))
	}

	fun <T : MatteryPacket> add(
		packetClass: Class<T>,
		reader: Function<FriendlyByteBuf, T>,
		direction: NetworkDirection? = null
	) {
		add(packetClass, MatteryPacket::write, reader, MatteryPacket::play, direction)
	}

	fun <T : MatteryPacket> add(
		packetClass: KClass<T>,
		reader: Function<FriendlyByteBuf, T>,
		direction: NetworkDirection? = null
	) {
		add(packetClass.java, MatteryPacket::write, reader, MatteryPacket::play, direction)
	}
}
