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

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

class SynchableSet<V>(val delegate: ListenableSet<V>, val codec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>) : ISynchable {
	private data class SetAction<V>(val value: KOptional<V>, val action: Int)
	private val remoteSlots = CopyOnWriteArrayList<RemoteState>()

	private inner class RemoteState(val listener: Runnable) : IRemoteState, ListenableSet.SetListener<V> {
		private val changelist = ArrayList<SetAction<V>>()
		private val changelistLock = Any()
		private val l = delegate.addListener(this)

		init {
			remoteSlots.add(this)
		}

		override fun write(stream: RegistryFriendlyByteBuf) {
			synchronized(changelistLock) {
				for (change in changelist) {
					stream.writeByte(change.action)
					change.value.ifPresent { codec.encode(stream, it) }
				}

				changelist.clear()
			}

			stream.writeByte(END)
		}

		override fun invalidate() {
			synchronized(changelistLock) {
				changelist.clear()
				changelist.add(SetAction(KOptional(), CLEAR))

				for (v in delegate) {
					changelist.add(SetAction(KOptional(codec.copy(v)), ADD))
				}
			}
		}

		override fun close() {
			remoteSlots.remove(this)
			l.remove()

			synchronized(changelistLock) {
				changelist.clear()
			}
		}

		override fun onClear() {
			changelist.clear()

			synchronized(changelistLock) {
				changelist.add(SetAction(KOptional(), CLEAR))
			}

			listener.run()
		}

		override fun onValueAdded(element: V) {
			synchronized(changelistLock) {
				changelist.removeIf { it.value.valueEquals(element) }
				changelist.add(SetAction(KOptional(codec.copy(element)), ADD))
			}

			listener.run()
		}

		override fun onValueRemoved(element: V) {
			synchronized(changelistLock) {
				changelist.removeIf { it.value.valueEquals(element) }
				changelist.add(SetAction(KOptional(codec.copy(element)), REMOVE))
			}

			listener.run()
		}

		init {
			invalidate()
		}
	}

	override var isRemote: Boolean = false
		private set

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

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

	override fun observe(): Boolean {
		return false
	}

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

		var action = stream.readByte().toInt()

		while (true) {
			when (action) {
				ADD -> delegate.add(codec.decode(stream))
				REMOVE -> delegate.remove(codec.decode(stream))
				CLEAR -> delegate.clear()
				else -> break
			}

			action = stream.readByte().toInt()
		}
	}

	companion object {
		private const val END = 0
		private const val CLEAR = 1
		private const val ADD = 2
		private const val REMOVE = 3
	}
}
