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

import net.minecraft.network.RegistryFriendlyByteBuf
import ru.dbotthepony.kommons.util.Delegate
import ru.dbotthepony.kommons.util.Listenable
import ru.dbotthepony.kommons.util.ListenableDelegate
import ru.dbotthepony.kommons.util.ValueObserver
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantLock
import java.util.function.Consumer
import kotlin.concurrent.withLock

class SynchableObservedDelegate<V>(val delegate: Delegate<V>, val codec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>) : ISynchable, ListenableDelegate<V>, ValueObserver<V> {
	private object Mark

	private val listeners = Listenable.Impl<V>()
	private val remoteCount = AtomicInteger()
	private var observed: Any? = Mark
	private val observeLock = ReentrantLock()

	override var isRemote: Boolean = false
		private set

	override fun read(stream: RegistryFriendlyByteBuf) {
		isRemote = true
		val read = codec.decode(stream)
		observed = codec.copy(read)
		delegate.accept(read)
	}

	override fun observe(): Boolean {
		val get = delegate.get()

		if (observed === Mark || !codec.compare(get, observed as V)) {
			observeLock.withLock {
				if (observed === Mark || !codec.compare(get, observed as V)) {
					observed = codec.copy(get)
					listeners.accept(get)
				}

				return true
			}
		}

		return false
	}

	override fun getAndObserve(): Pair<V, Boolean> {
		val get = delegate.get()

		if (observed === Mark || !codec.compare(get, observed as V)) {
			observeLock.withLock {
				if (observed === Mark || !codec.compare(get, observed as V)) {
					observed = codec.copy(get)
					listeners.accept(get)
				}

				return get to  true
			}
		}

		return get to false
	}

	override fun get(): V {
		return delegate.get()
	}

	override fun accept(t: V) {
		delegate.accept(t)
	}

	override val shouldBeObserved: Boolean
		get() = true

	override fun addListener(listener: Consumer<V>): Listenable.L {
		return listeners.addListener(listener)
	}

	private inner class RemoteState(listener: Runnable) : IRemoteState {
		private val l = listeners.addListener(listener)

		init {
			remoteCount.incrementAndGet()
		}

		override fun write(stream: RegistryFriendlyByteBuf) {
			codec.encode(stream, delegate.get())
		}

		override fun invalidate() {
			// do nothing
		}

		override fun close() {
			l.remove()
			remoteCount.decrementAndGet() // unsafe because close() can be called multiple times
		}
	}

	override val hasRemotes: Boolean
		get() = remoteCount.get() > 0

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