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

import net.minecraft.network.RegistryFriendlyByteBuf
import ru.dbotthepony.kommons.util.Listenable
import ru.dbotthepony.kommons.util.ListenableDelegate
import ru.dbotthepony.kommons.util.Observer
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
import java.util.concurrent.atomic.AtomicInteger
import java.util.function.Consumer
import java.util.function.Supplier

class SynchableDelegate<V>(val delegate: ListenableDelegate<V>, val codec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>) : ISynchable, ListenableDelegate<V>, Observer {
	constructor(delegate: Supplier<V>, codec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>) : this(ListenableDelegate.CustomShadow(delegate, codec::compare, codec::copy), codec)

	private val listeners = Listenable.Impl<V>()
	private val l = delegate.addListener(listeners)
	private val remoteCount = AtomicInteger()

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

	override var isRemote: Boolean = false
		private set

	override fun read(stream: RegistryFriendlyByteBuf) {
		isRemote = false
		delegate.accept(codec.decode(stream))
	}

	inner class Remote(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 fun createRemoteState(listener: Runnable): IRemoteState {
		return Remote(listener)
	}

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

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

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

	override val shouldBeObserved: Boolean
		get() = delegate is ListenableDelegate.AbstractShadow

	override fun observe(): Boolean {
		if (delegate is ListenableDelegate.AbstractShadow) {
			return delegate.observe()
		}

		return false
	}
}
