package ru.dbotthepony.mc.otm.network.syncher

import net.minecraft.network.RegistryFriendlyByteBuf
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
import java.util.concurrent.CopyOnWriteArrayList

class DynamicSynchable<D, V : ISynchable>(
	initialValue: KOptional<D>,
	private val codec: MatteryStreamCodec<RegistryFriendlyByteBuf, D>,
	private val factory: (D) -> V
) : ISynchable {
	constructor(codec: MatteryStreamCodec<RegistryFriendlyByteBuf, D>, factory: (D) -> V) : this(KOptional(), codec, factory)

	private var _synchable: KOptional<V> = KOptional()

	val synchable: V
		get() = _synchable.orThrow { IllegalStateException("Not initialized") }

	private var _value: KOptional<D> = KOptional()
	private val remotes = CopyOnWriteArrayList<RemoteState>()

	var value: D
		get() = _value.orThrow { IllegalStateException("Not initialized") }
		set(value) {
			if (_value.isEmpty || _value.value != value) {
				_value = KOptional(value)
				_synchable = KOptional(factory(value))

				remotes.forEach { it.invalidateFull() }
			}
		}

	init {
		initialValue.ifPresent {
			_value = KOptional(it)
			_synchable = KOptional(factory(it))
		}
	}

	private inner class RemoteState(val listener: Runnable) : IRemoteState {
		private var valueChanged = true
		private var remote = synchable.createRemoteState(listener)

		override fun write(stream: RegistryFriendlyByteBuf) {
			stream.writeBoolean(valueChanged)

			if (valueChanged) {
				codec.encode(stream, _value.value)
			}

			valueChanged = false
			remote.write(stream)
		}

		fun invalidateFull() {
			valueChanged = true
			remote.close()
			remote = synchable.createRemoteState(listener)
			listener.run()
		}

		override fun invalidate() {
			valueChanged = true
			remote.invalidate()
		}

		override fun close() {
			remotes.remove(this)
			remote.close()
		}
	}

	override var isRemote: Boolean = false
		private set

	override fun read(stream: RegistryFriendlyByteBuf) {
		isRemote = true

		if (stream.readBoolean()) {
			_value = KOptional(codec.decode(stream))
			_synchable = KOptional(factory(_value.value))
		}

		_synchable.value.read(stream)
	}

	override val hasRemotes: Boolean
		get() = remotes.isNotEmpty()

	override fun createRemoteState(listener: Runnable): IRemoteState {
		return RemoteState(listener)
	}
}
