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

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

class SynchableMap<K, V>(
	val delegate: ListenableMap<K, V>,
	val keyCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, K>,
	val valueCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>
) : ISynchable {
	override var isRemote: Boolean = false
		private set

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

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

		while (true) {
			when (action) {
				ADD -> delegate.put(keyCodec.decode(stream), valueCodec.decode(stream))
				REMOVE -> delegate.remove(keyCodec.decode(stream))
				CLEAR -> delegate.clear()
				else -> break
			}

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

	private val remoteSlots = CopyOnWriteArrayList<RemoteState>()
	private data class MapAction<K, V>(val key: KOptional<K>, val value: KOptional<V>, val action: Int)

	private inner class RemoteState(val listener: Runnable) : IRemoteState, ListenableMap.MapListener<K, V> {
		private val changelist = ArrayList<MapAction<K, 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.key.ifPresent { keyCodec.encode(stream, it) }
					change.value.ifPresent { valueCodec.encode(stream, it) }
				}

				changelist.clear()
			}

			stream.writeByte(END)
		}

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

				for ((k, v) in delegate) {
					changelist.add(MapAction(KOptional(keyCodec.copy(k)), KOptional(valueCodec.copy(v)), ADD))
				}
			}
		}

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

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

		override fun onClear() {
			synchronized(changelistLock) {
				changelist.clear()
				changelist.add(MapAction(KOptional(), KOptional(), CLEAR))
			}

			listener.run()
		}

		override fun onValueAdded(key: K, value: V) {
			synchronized(changelistLock) {
				changelist.removeIf { it.key.valueEquals(key) }
				changelist.add(MapAction(KOptional(keyCodec.copy(key)), KOptional(valueCodec.copy(value)), ADD))
			}

			listener.run()
		}

		override fun onValueRemoved(key: K, value: V) {
			synchronized(changelistLock) {
				changelist.removeIf { it.key.valueEquals(key) }
				changelist.add(MapAction(KOptional(keyCodec.copy(key)), KOptional(), REMOVE))
			}

			listener.run()
		}

		init {
			invalidate()
		}
	}

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

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

	override fun observe(): Boolean {
		return false
	}

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