@file:Suppress("DeprecatedCallableAddReplaceWith")

package ru.dbotthepony.mc.otm.network.synchronizer

import it.unimi.dsi.fastutil.booleans.BooleanConsumer
import it.unimi.dsi.fastutil.floats.FloatConsumer
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.ReferenceArraySet
import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.core.*
import ru.dbotthepony.mc.otm.core.collect.ProxiedMap
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.util.BigDecimalValueCodec
import ru.dbotthepony.mc.otm.core.util.BinaryStringCodec
import ru.dbotthepony.mc.otm.core.util.ByteValueCodec
import ru.dbotthepony.mc.otm.core.util.DecimalValueCodec
import ru.dbotthepony.mc.otm.core.util.EnumValueCodec
import ru.dbotthepony.mc.otm.core.util.IStreamCodec
import ru.dbotthepony.mc.otm.core.util.ItemStackValueCodec
import ru.dbotthepony.mc.otm.core.util.ShortValueCodec
import ru.dbotthepony.mc.otm.core.util.UUIDValueCodec
import ru.dbotthepony.mc.otm.core.util.readVarIntLE
import ru.dbotthepony.mc.otm.core.util.readVarLongLE
import ru.dbotthepony.mc.otm.core.util.writeVarIntLE
import ru.dbotthepony.mc.otm.core.util.writeVarLongLE
import ru.dbotthepony.mc.otm.secondTime
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.InputStream
import java.lang.ref.WeakReference
import java.math.BigDecimal
import java.util.*
import java.util.function.BooleanSupplier
import java.util.function.DoubleConsumer
import java.util.function.DoubleSupplier
import java.util.function.IntConsumer
import java.util.function.IntSupplier
import java.util.function.LongConsumer
import java.util.function.LongSupplier
import java.util.function.Supplier
import kotlin.reflect.KMutableProperty0
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty0

/**
 * Universal, one-to-many value synchronizer, allowing to synchronize values from server to client
 * anywhere, where input/output streams are supported
 */
@Suppress("unused", "BlockingMethodInNonBlockingContext")
class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCallback: Boolean) {
	constructor() : this(Runnable {}, false)
	constructor(callback: Runnable) : this(callback, false)

	private var freeSlots = 0
	// почему не удалять поля напрямую?
	// чтоб не возникло проблем в состоянии гонки
	// формируем пакет -> удаляем поле по обе стороны -> клиент принимает пакет -> клиент считывает неверные данные
	// конечно, всё равно всё сломается если было удалено поле, которое находится в пакете
	// но если поля нет в пакете, то всё окей
	private val fields = ArrayList<AbstractField<*>?>(0)
	private val observers = ArrayList<AbstractField<*>>(0)

	private var nextFieldID = 0

	val hasObservers: Boolean get() = observers.isNotEmpty()
	val isEmpty: Boolean get() = fields.isEmpty()
	val isNotEmpty: Boolean get() = fields.isNotEmpty()

	var hasChanges: Boolean = false
		private set(value) {
			if (value != field) {
				field = value

				if (value && !alwaysCallCallback) {
					callback.run()
				}
			}

			if (alwaysCallCallback && value) {
				callback.run()
			}
		}

	fun markClean() {
		hasChanges = false
	}

	fun computedByte(getter: () -> Byte) = ComputedField(getter, ByteValueCodec)
	fun computedBool(getter: BooleanSupplier) = ComputedBooleanField(getter)
	fun computedShort(getter: () -> Short) = ComputedField(getter, ShortValueCodec)
	fun computedLong(getter: LongSupplier) = ComputedLongField(getter)
	fun computedFixedLong(getter: LongSupplier) = ComputedFixedLongField(getter)
	fun computedFloat(getter: FloatSupplier) = ComputedFloatField(getter)
	fun computedDouble(getter: DoubleSupplier) = ComputedDoubleField(getter)
	fun computedUuid(getter: () -> UUID) = ComputedField(getter, UUIDValueCodec)
	fun computedInt(getter: IntSupplier) = ComputedIntField(getter)
	fun computedFixedInt(getter: IntSupplier) = ComputedFixedIntField(getter)
	fun computedDecimal(getter: () -> Decimal) = ComputedField(getter, DecimalValueCodec)
	fun computedBigDecimal(getter: () -> BigDecimal) = ComputedField(getter, BigDecimalValueCodec)
	fun computedItem(getter: () -> ItemStack) = ComputedField(getter, ItemStackValueCodec)
	fun computedString(getter: () -> String) = ComputedField(getter, BinaryStringCodec)

	fun computedByte(getter: KProperty0<Byte>) = ComputedField(getter, ByteValueCodec)
	fun computedBool(getter: KProperty0<Boolean>) = ComputedBooleanField(getter::get)
	fun computedShort(getter: KProperty0<Short>) = ComputedField(getter, ShortValueCodec)
	fun computedLong(getter: KProperty0<Long>) = ComputedLongField(getter::get)
	fun computedFixedLong(getter: KProperty0<Long>) = ComputedFixedLongField(getter::get)
	fun computedFloat(getter: KProperty0<Float>) = ComputedFloatField(getter::get)
	fun computedDouble(getter: KProperty0<Double>) = ComputedDoubleField(getter::get)
	fun computedUuid(getter: KProperty0<UUID>) = ComputedField(getter, UUIDValueCodec)
	fun computedInt(getter: KProperty0<Int>) = ComputedIntField(getter::get)
	fun computedFixedInt(getter: KProperty0<Int>) = ComputedFixedIntField(getter::get)
	fun computedDecimal(getter: KProperty0<Decimal>) = ComputedField(getter, DecimalValueCodec)
	fun computedBigDecimal(getter: KProperty0<BigDecimal>) = ComputedField(getter, BigDecimalValueCodec)
	fun computedItem(getter: KProperty0<ItemStack>) = ComputedField(getter, ItemStackValueCodec)
	fun computedString(getter: KProperty0<String>) = ComputedField(getter, BinaryStringCodec)

	fun computedByte(getter: Supplier<Byte>) = ComputedField(getter::get, ByteValueCodec)
	fun computedBool(getter: Supplier<Boolean>) = ComputedBooleanField(getter::get)
	fun computedShort(getter: Supplier<Short>) = ComputedField(getter::get, ShortValueCodec)
	fun computedLong(getter: Supplier<Long>) = ComputedLongField(getter::get)
	fun computedFixedLong(getter: Supplier<Long>) = ComputedFixedLongField(getter::get)
	fun computedFloat(getter: Supplier<Float>) = ComputedFloatField(getter::get)
	fun computedDouble(getter: Supplier<Double>) = ComputedDoubleField(getter::get)
	fun computedUuid(getter: Supplier<UUID>) = ComputedField(getter::get, UUIDValueCodec)
	fun computedInt(getter: Supplier<Int>) = ComputedIntField(getter::get)
	fun computedFixedInt(getter: Supplier<Int>) = ComputedFixedIntField(getter::get)
	fun computedDecimal(getter: Supplier<Decimal>) = ComputedField(getter::get, DecimalValueCodec)
	fun computedBigDecimal(getter: Supplier<BigDecimal>) = ComputedField(getter::get, BigDecimalValueCodec)
	fun computedItem(getter: Supplier<ItemStack>) = ComputedField(getter::get, ItemStackValueCodec)
	fun computedString(getter: Supplier<String>) = ComputedField(getter::get, BinaryStringCodec)

	fun <T : Enum<T>> computedEnum(type: Class<T>, getter: () -> T) = ComputedField(getter, EnumValueCodec(type))
	inline fun <reified T : Enum<T>> computedEnum(noinline getter: () -> T) = ComputedField(getter, EnumValueCodec(T::class.java))

	fun <T : Enum<T>> computedEnum(type: Class<T>, getter: KProperty0<T>) = ComputedField(getter, EnumValueCodec(type))
	inline fun <reified T : Enum<T>> computedEnum(getter: KProperty0<T>) = ComputedField(getter, EnumValueCodec(T::class.java))

	fun <T : Enum<T>> computedEnum(type: Class<T>, getter: Supplier<T>) = ComputedField(getter::get, EnumValueCodec(type))
	inline fun <reified T : Enum<T>> computedEnum(getter: Supplier<T>) = ComputedField(getter::get, EnumValueCodec(T::class.java))

	@JvmOverloads
	fun byte(
		value: Byte = 0,
		getter: FieldGetter<Byte>? = null,
		setter: FieldSetter<Byte>? = null,
	): Field<Byte> {
		return Field(value, ByteValueCodec, getter, setter)
	}

	@JvmOverloads
	fun bool(
		value: Boolean = false,
		getter: BooleanFieldGetter? = null,
		setter: BooleanFieldSetter? = null,
	): BooleanField {
		return BooleanField(value, getter, setter)
	}

	@JvmOverloads
	fun short(
		value: Short = 0,
		getter: FieldGetter<Short>? = null,
		setter: FieldSetter<Short>? = null,
	): Field<Short> {
		return Field(value, ShortValueCodec, getter, setter)
	}

	@JvmOverloads
	fun long(
		value: Long = 0L,
		getter: LongFieldGetter? = null,
		setter: LongFieldSetter? = null,
	): LongField {
		return LongField(value, getter, setter)
	}

	@JvmOverloads
	fun fixedLong(
		value: Long = 0L,
		getter: LongFieldGetter? = null,
		setter: LongFieldSetter? = null,
	): FixedLongField {
		return FixedLongField(value, getter, setter)
	}

	@JvmOverloads
	fun float(
		value: Float = 0f,
		getter: FloatFieldGetter? = null,
		setter: FloatFieldSetter? = null,
	): FloatField {
		return FloatField(value, getter, setter)
	}

	@JvmOverloads
	fun double(
		value: Double = 0.0,
		getter: DoubleFieldGetter? = null,
		setter: DoubleFieldSetter? = null,
	): DoubleField {
		return DoubleField(value, getter, setter)
	}

	@JvmOverloads
	fun uuid(
		value: UUID = UUID(0L, 0L),
		getter: FieldGetter<UUID>? = null,
		setter: FieldSetter<UUID>? = null,
	): Field<UUID> {
		return Field(value, UUIDValueCodec, getter, setter)
	}

	@JvmOverloads
	fun int(
		value: Int = 0,
		getter: IntFieldGetter? = null,
		setter: IntFieldSetter? = null,
	): IntField {
		return IntField(value, getter, setter)
	}

	@JvmOverloads
	fun string(
		value: String = "",
		getter: FieldGetter<String>? = null,
		setter: FieldSetter<String>? = null,
	): Field<String> {
		return Field(value, BinaryStringCodec, getter, setter)
	}

	@JvmOverloads
	fun fixedInt(
		value: Int = 0,
		getter: IntFieldGetter? = null,
		setter: IntFieldSetter? = null,
	): FixedIntField {
		return FixedIntField(value, getter, setter)
	}

	@JvmOverloads
	fun decimal(
		value: Decimal = Decimal.ZERO,
		getter: FieldGetter<Decimal>? = null,
		setter: FieldSetter<Decimal>? = null,
	): Field<Decimal> {
		return Field(value, DecimalValueCodec, getter, setter)
	}

	@JvmOverloads
	fun bigDecimal(
		value: BigDecimal = BigDecimal.ZERO,
		getter: FieldGetter<BigDecimal>? = null,
		setter: FieldSetter<BigDecimal>? = null,
	): Field<BigDecimal> {
		return Field(value, BigDecimalValueCodec, getter, setter)
	}

	@JvmOverloads
	fun <T : Enum<T>> enum(
		type: Class<T>,
		value: T = type.enumConstants[0],
		getter: FieldGetter<T>? = null,
		setter: FieldSetter<T>? = null,
	): Field<T> {
		return Field(value, EnumValueCodec(type), getter, setter)
	}

	@JvmOverloads
	fun <T : Enum<T>> enum(
		value: T,
		getter: FieldGetter<T>? = null,
		setter: FieldSetter<T>? = null,
	): Field<T> {
		return Field(value, EnumValueCodec(value::class.java), getter, setter)
	}

	@JvmOverloads
	fun item(
		value: ItemStack = ItemStack.EMPTY,
		getter: FieldGetter<ItemStack>? = null,
		setter: FieldSetter<ItemStack>? = null,
		observe: Boolean = true,
	): Field<ItemStack> {
		return Field(value, ItemStackValueCodec, getter, setter, isObserver = observe)
	}

	fun item(
		delegate: KMutableProperty0<ItemStack>,
	): ObservedField<ItemStack> {
		return ObservedField(delegate, ItemStackValueCodec)
	}

	private var endpointsMaxCapacity = 1
	private val endpoints = ArrayList<WeakReference<Endpoint>>(1)
	val defaultEndpoint = Endpoint()

	private var nextEndpointsCleanup = secondTime

	private fun notifyEndpoints(dirtyField: AbstractField<*>) {
		hasChanges = true

		forEachEndpoint {
			it.addDirtyField(dirtyField)
		}
	}

	private inline fun forEachEndpoint(execute: (Endpoint) -> Unit) {
		nextEndpointsCleanup = secondTime + 60

		synchronized(endpoints) {
			endpoints.forValidRefs { execute.invoke(it) }

			if (endpoints.size < endpointsMaxCapacity / 2) {
				endpoints.trimToSize()
				endpointsMaxCapacity = endpoints.size
			}
		}
	}

	inner class Endpoint {
		init {
			synchronized(endpoints) {
				endpoints.add(WeakReference(this))
				endpointsMaxCapacity = endpointsMaxCapacity.coerceAtLeast(endpoints.size)

				if (secondTime >= nextEndpointsCleanup) {
					nextEndpointsCleanup = secondTime + 60

					val iterator = endpoints.listIterator()

					for (value in iterator) {
						if (value.get() == null) {
							iterator.remove()
						}
					}

					if (endpoints.size < endpointsMaxCapacity / 2) {
						endpoints.trimToSize()
						endpointsMaxCapacity = endpoints.size
					}
				}
			}
		}

		private val dirtyFields = ReferenceArraySet<AbstractField<*>>(4)

		// use LinkedList because it is ensured memory is freed on LinkedList#clear
		private val mapBacklogs = Reference2ObjectOpenHashMap<Map<*, *>, LinkedList<Pair<Any?, (DataOutputStream) -> Unit>>>()
		private val setBacklogs = Reference2ObjectOpenHashMap<Set<*>, LinkedList<Pair<Any?, (DataOutputStream) -> Unit>>>()

		var unused: Boolean = false
			private set

		fun markUnused() {
			require(this === defaultEndpoint) { "This is not a default endpoint" }
			if (unused) return
			unused = true
			mapBacklogs.clear()
			dirtyFields.clear()

			val iterator = endpoints.listIterator()

			for (value in iterator) {
				if (value.get() === this) {
					iterator.remove()
				}
			}
		}

		init {
			markDirty()
		}

		fun markDirty() {
			for (field in fields) {
				field?.markDirty(this)
			}
		}

		internal fun addDirtyField(field: AbstractField<*>) {
			if (unused) {
				return
			}

			dirtyFields.add(field)
		}

		internal fun removeDirtyField(field: AbstractField<*>) {
			dirtyFields.remove(field)
		}

		internal fun <K, V> getMapBacklog(map: Map<K, V>): LinkedList<Pair<Any?, (DataOutputStream) -> Unit>> {
			if (unused) {
				return LinkedList()
			}

			return mapBacklogs.computeIfAbsent(map, Reference2ObjectFunction {
				LinkedList()
			})
		}

		internal fun <K, V> removeMapBacklog(map: Map<K, V>) {
			mapBacklogs.remove(map)
		}

		internal fun <V> getSetBacklog(set: Set<V>): LinkedList<Pair<Any?, (DataOutputStream) -> Unit>> {
			if (unused) {
				return LinkedList()
			}

			return setBacklogs.computeIfAbsent(set, Reference2ObjectFunction {
				LinkedList()
			})
		}

		internal fun <V> removeSetBacklog(set: Set<V>) {
			setBacklogs.remove(set)
		}

		fun collectNetworkPayload(): FastByteArrayOutputStream? {
			if (unused || dirtyFields.isEmpty()) {
				return null
			}

			val stream = FastByteArrayOutputStream()
			val dataStream = DataOutputStream(stream)

			for (field in dirtyFields) {
				stream.writeVarIntLE(field.id)
				field.write(dataStream, this)
			}

			dirtyFields.clear()
			stream.write(0)

			return stream
		}
	}

	private val boundEndpoints = WeakHashMap<Any, Endpoint>()

	fun computeEndpointFor(obj: Any): Endpoint {
		return boundEndpoints.computeIfAbsent(obj) { Endpoint() }
	}

	fun removeEndpointFor(obj: Any): Endpoint? {
		return boundEndpoints.remove(obj)
	}

	fun endpointFor(obj: Any): Endpoint? {
		return boundEndpoints[obj]
	}

	@Suppress("LeakingThis")
	abstract inner class AbstractField<V> : IField<V> {
		val id: Int

		init {
			if (freeSlots > 0) {
				var found = -1

				for (i in fields.indices) {
					if (fields[i] == null) {
						fields[i] = this
						found = i + 1
						freeSlots--
						break
					}
				}

				if (found == -1) {
					throw RuntimeException("freeSlots = $freeSlots but no null entries in field list!")
				} else {
					id = found
				}
			} else {
				fields.add(this)
				id = fields.size
			}
		}

		final override var isRemoved = false
			private set

		protected var isDirty = false

		override fun remove() {
			if (isRemoved)
				return

			isRemoved = true
			freeSlots++
			fields[id - 1] = null
			observers.remove(this)

			while (fields[fields.size - 1] == null) {
				fields.removeAt(fields.size - 1)
				freeSlots--
			}

			forEachEndpoint {
				it.removeDirtyField(this)
			}
		}

		override fun markDirty(endpoint: Endpoint) {
			check(!isRemoved) { "Field was removed" }
			endpoint.addDirtyField(this)
		}

		override fun markDirty() {
			check(!isRemoved) { "Field was removed" }
			notifyEndpoints(this@AbstractField)
			isDirty = true
		}
	}

	/**
	 * Networked variable with backing field holding immutable value
	 */
	inner class Field<V>(
		private var field: V,
		private val codec: IStreamCodec<V>,
		private val getter: FieldGetter<V>? = null,
		private val setter: FieldSetter<V>? = null,
		isObserver: Boolean = false,
	) : AbstractField<V>(), IMutableField<V> {
		private var remote: V = codec.copy(field)

		init {
			if (isObserver) {
				observers.add(this)
			}
		}

		private val access = object : FieldAccess<V> {
			override fun read(): V {
				return field
			}

			override fun write(value: V) {
				if (!isDirty && !codec.compare(remote, value)) {
					notifyEndpoints(this@Field)
					isDirty = true
				}

				this@Field.field = value
			}
		}

		override fun observe(): Boolean {
			check(!isRemoved) { "Field was removed" }

			if (!isDirty && !codec.compare(remote, field)) {
				notifyEndpoints(this@Field)
				isDirty = true
			}

			return isDirty
		}

		override var value: V
			get() {
				val getter = this.getter

				if (getter != null) {
					return getter.invoke(access)
				}

				return this.field
			}
			set(value) {
				val setter = this.setter

				if (setter != null) {
					setter.invoke(value, access, false)
					return
				}

				if (!isDirty && !codec.compare(remote, value)) {
					notifyEndpoints(this@Field)
					isDirty = true
				}

				this.field = value
			}

		override fun write(stream: DataOutputStream, endpoint: Endpoint) {
			check(!isRemoved) { "Field was removed" }
			codec.write(stream, field)
			isDirty = false
			remote = codec.copy(field)
		}

		override fun read(stream: DataInputStream) {
			check(!isRemoved) { "Field was removed" }
			val value = codec.read(stream)
			val setter = this.setter

			if (setter != null) {
				setter.invoke(value, access, true)
				return
			}

			this.field = value
		}
	}

	abstract inner class PrimitiveField<V : Any> : AbstractField<V>() {
		override fun observe(): Boolean {
			check(!isRemoved) { "Field was removed" }
			return isDirty
		}
	}

	/**
	 * Type specific field, storing primitive [Float] directly
	 */
	inner class FloatField(private var field: Float, private val getter: FloatFieldGetter? = null, private val setter: FloatFieldSetter? = null) : PrimitiveField<Float>(), IMutableFloatField {
		override val property = object : IMutableFloatProperty {
			override fun getValue(thisRef: Any?, property: KProperty<*>): Float {
				return this@FloatField.float
			}

			override fun setValue(thisRef: Any?, property: KProperty<*>, value: Float) {
				this@FloatField.float = value
			}
		}

		private val access = object : FloatFieldAccess {
			override fun readFloat(): Float {
				return this@FloatField.field
			}

			override fun write(value: Float) {
				if (!isDirty && value != this@FloatField.field) {
					notifyEndpoints(this@FloatField)
					isDirty = true
				}

				this@FloatField.field = value
			}
		}

		override var float: Float
			get() {
				return getter?.invoke(access) ?: this.field
			}
			set(value) {
				val setter = this.setter

				if (setter != null) {
					setter.invoke(value, access, false)
				} else if (value != this.field) {
					if (!isDirty) {
						notifyEndpoints(this)
						isDirty = true
					}

					this.field = value
				}
			}

		override fun write(stream: DataOutputStream, endpoint: Endpoint) {
			check(!isRemoved) { "Field was removed" }
			stream.writeFloat(this.field)
			isDirty = false
		}

		override fun read(stream: DataInputStream) {
			check(!isRemoved) { "Field was removed" }
			val value = stream.readFloat()
			val setter = this.setter

			if (setter != null) {
				setter.invoke(value, access, true)
			} else {
				this.field = value
			}
		}
	}

	/**
	 * Type specific field, storing primitive [Double] directly
	 */
	inner class DoubleField(private var field: Double, private val getter: DoubleFieldGetter? = null, private val setter: DoubleFieldSetter? = null) : PrimitiveField<Double>(), IMutableDoubleField {
		override val property = object : IMutableDoubleProperty {
			override fun getValue(thisRef: Any?, property: KProperty<*>): Double {
				return this@DoubleField.double
			}

			override fun setValue(thisRef: Any?, property: KProperty<*>, value: Double) {
				this@DoubleField.double = value
			}
		}

		private val access = object : DoubleFieldAccess {
			override fun readDouble(): Double {
				return this@DoubleField.field
			}

			override fun write(value: Double) {
				if (!isDirty && value != this@DoubleField.field) {
					notifyEndpoints(this@DoubleField)
					isDirty = true
				}

				this@DoubleField.field = value
			}
		}

		override var double: Double
			get() {
				return getter?.invoke(access) ?: this.field
			}
			set(value) {
				val setter = this.setter

				if (setter != null) {
					setter.invoke(value, access, false)
				} else if (value != this.field) {
					if (!isDirty) {
						notifyEndpoints(this)
						isDirty = true
					}

					this.field = value
				}
			}

		override fun write(stream: DataOutputStream, endpoint: Endpoint) {
			check(!isRemoved) { "Field was removed" }
			stream.writeDouble(this.field)
			isDirty = false
		}

		override fun read(stream: DataInputStream) {
			check(!isRemoved) { "Field was removed" }
			val value = stream.readDouble()
			val setter = this.setter

			if (setter != null) {
				setter.invoke(value, access, true)
			} else {
				this.field = value
			}
		}
	}

	abstract inner class AbstractIntField(protected var field: Int, private val getter: IntFieldGetter? = null, protected val setter: IntFieldSetter? = null) : PrimitiveField<Int>(), IMutableIntField {
		final override val property = object : IMutableIntProperty {
			override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
				return this@AbstractIntField.int
			}

			override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
				this@AbstractIntField.int = value
			}
		}

		protected val access = object : IntFieldAccess {
			override fun readInt(): Int {
				return this@AbstractIntField.field
			}

			override fun write(value: Int) {
				if (!isDirty && value != this@AbstractIntField.field) {
					notifyEndpoints(this@AbstractIntField)
					isDirty = true
				}

				this@AbstractIntField.field = value
			}
		}

		final override var int: Int
			get() {
				return getter?.invoke(access) ?: this.field
			}
			set(value) {
				val setter = this.setter

				if (setter != null) {
					setter.invoke(value, access, false)
				} else if (value != this.field) {
					if (!isDirty) {
						notifyEndpoints(this)
						isDirty = true
					}

					this.field = value
				}
			}
	}

	/**
	 * Type specific field, storing primitive [Int] directly, and networking it as variable length integer
	 */
	inner class IntField(field: Int, getter: IntFieldGetter? = null, setter: IntFieldSetter? = null) : AbstractIntField(field, getter, setter) {
		override fun write(stream: DataOutputStream, endpoint: Endpoint) {
			check(!isRemoved) { "Field was removed" }
			stream.writeVarIntLE(this.field)
			isDirty = false
		}

		override fun read(stream: DataInputStream) {
			check(!isRemoved) { "Field was removed" }
			val value = stream.readVarIntLE()
			val setter = this.setter

			if (setter != null) {
				setter.invoke(value, access, true)
			} else {
				this.field = value
			}
		}
	}

	/**
	 * Type specific field, storing primitive [Int] directly, and networking it as 4 octets
	 */
	inner class FixedIntField(field: Int, getter: IntFieldGetter? = null, setter: IntFieldSetter? = null) : AbstractIntField(field, getter, setter) {
		override fun write(stream: DataOutputStream, endpoint: Endpoint) {
			check(!isRemoved) { "Field was removed" }
			stream.writeInt(this.field)
			isDirty = false
		}

		override fun read(stream: DataInputStream) {
			check(!isRemoved) { "Field was removed" }
			val value = stream.readInt()
			val setter = this.setter

			if (setter != null) {
				setter.invoke(value, access, true)
			} else {
				this.field = value
			}
		}
	}

	/**
	 * Type specific field, storing primitive [Long] directly
	 */
	abstract inner class AbstractLongField(protected var field: Long, private val getter: LongFieldGetter? = null, protected val setter: LongFieldSetter? = null) : PrimitiveField<Long>(), IMutableLongField {
		final override val property = object : IMutableLongProperty {
			override fun getValue(thisRef: Any?, property: KProperty<*>): Long {
				return this@AbstractLongField.long
			}

			override fun setValue(thisRef: Any?, property: KProperty<*>, value: Long) {
				this@AbstractLongField.long = value
			}
		}

		protected val access = object : LongFieldAccess {
			override fun readLong(): Long {
				return this@AbstractLongField.field
			}

			override fun write(value: Long) {
				if (!isDirty && value != this@AbstractLongField.field) {
					notifyEndpoints(this@AbstractLongField)
					isDirty = true
				}

				this@AbstractLongField.field = value
			}
		}

		final override var long: Long
			get() {
				return getter?.invoke(access) ?: this.field
			}
			set(value) {
				val setter = this.setter

				if (setter != null) {
					setter.invoke(value, access, false)
				} else if (value != this.field) {
					if (!isDirty) {
						notifyEndpoints(this)
						isDirty = true
					}

					this.field = value
				}
			}
	}

	/**
	 * Type specific field, storing primitive [Long] directly, and networking it as variable length integer
	 */
	inner class LongField(field: Long, getter: LongFieldGetter? = null, setter: LongFieldSetter? = null) : AbstractLongField(field, getter, setter) {
		override fun write(stream: DataOutputStream, endpoint: Endpoint) {
			check(!isRemoved) { "Field was removed" }
			stream.writeVarLongLE(this.field)
			isDirty = false
		}

		override fun read(stream: DataInputStream) {
			check(!isRemoved) { "Field was removed" }
			val value = stream.readVarLongLE()
			val setter = this.setter

			if (setter != null) {
				setter.invoke(value, access, true)
			} else {
				this.field = value
			}
		}
	}

	/**
	 * Type specific field, storing primitive [Long] directly, and networking it as 8 octets
	 */
	inner class FixedLongField(field: Long, getter: LongFieldGetter? = null, setter: LongFieldSetter? = null) : AbstractLongField(field, getter, setter) {
		override fun write(stream: DataOutputStream, endpoint: Endpoint) {
			check(!isRemoved) { "Field was removed" }
			stream.writeLong(this.field)
			isDirty = false
		}

		override fun read(stream: DataInputStream) {
			check(!isRemoved) { "Field was removed" }
			val value = stream.readLong()
			val setter = this.setter

			if (setter != null) {
				setter.invoke(value, access, true)
			} else {
				this.field = value
			}
		}
	}

	/**
	 * Type specific field, storing primitive [Boolean] directly
	 */
	inner class BooleanField(private var field: Boolean, private val getter: BooleanFieldGetter? = null, private val setter: BooleanFieldSetter? = null) : PrimitiveField<Boolean>(), IMutableBooleanField {
		override val property = object : IMutableBooleanProperty {
			override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean {
				return this@BooleanField.boolean
			}

			override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) {
				this@BooleanField.boolean = value
			}
		}

		private val access = object : BooleanFieldAccess {
			override fun readBoolean(): Boolean {
				return this@BooleanField.field
			}

			override fun write(value: Boolean) {
				if (!isDirty && value != this@BooleanField.field) {
					notifyEndpoints(this@BooleanField)
					isDirty = true
				}

				this@BooleanField.field = value
			}
		}

		override var boolean: Boolean
			get() {
				return getter?.invoke(access) ?: this.field
			}
			set(value) {
				val setter = this.setter

				if (setter != null) {
					setter.invoke(value, access, false)
				} else if (value != this.field) {
					if (!isDirty) {
						notifyEndpoints(this)
						isDirty = true
					}

					this.field = value
				}
			}

		override fun write(stream: DataOutputStream, endpoint: Endpoint) {
			check(!isRemoved) { "Field was removed" }
			stream.writeBoolean(this.field)
			isDirty = false
		}

		override fun read(stream: DataInputStream) {
			check(!isRemoved) { "Field was removed" }
			val value = stream.readBoolean()
			val setter = this.setter

			if (setter != null) {
				setter.invoke(value, access, true)
			} else {
				this.field = value
			}
		}
	}

	/**
	 * Networked value with backing getter which is constantly polled
	 */
	inner class ComputedField<V>(
		private val getter: () -> V,
		private val codec: IStreamCodec<V>,
		private val observer: (new: V) -> Unit = {}
	) : AbstractField<V>(), IField<V> {
		private var remote: V? = null
		private var clientValue: V? = null

		init {
			observers.add(this)
		}

		override fun observe(): Boolean {
			check(!isRemoved) { "Field was removed" }

			val value = value

			if (!isDirty && (remote == null || !codec.compare(remote ?: throw ConcurrentModificationException(), value))) {
				notifyEndpoints(this)
				isDirty = true
				remote = codec.copy(value)
			}

			return isDirty
		}

		override val value: V
			get() = clientValue ?: getter.invoke()

		override fun write(stream: DataOutputStream, endpoint: Endpoint) {
			check(!isRemoved) { "Field was removed" }
			codec.write(stream, value)
			isDirty = false
		}

		override fun read(stream: DataInputStream) {
			check(!isRemoved) { "Field was removed" }
			val newValue = codec.read(stream)
			clientValue = newValue
			observer.invoke(newValue)
		}
	}

	/**
	 * Networked value with backing getter which is constantly polled
	 *
	 * This class has concrete implementation for [Float] primitive
	 */
	inner class ComputedFloatField(private val getter: FloatSupplier, private val observer: FloatConsumer = FloatConsumer {}) : AbstractField<Float>(), IFloatField {
		private var remote: Float = 0f
		private var isRemoteSet = false
		private var clientValue: Float = 0f
		private var isClientValue = false

		override val property = object : IFloatProperty {
			override fun getValue(thisRef: Any?, property: KProperty<*>): Float {
				return float
			}
		}

		init {
			observers.add(this)
		}

		override fun observe(): Boolean {
			check(!isRemoved) { "Field was removed" }

			val value = getter.getAsFloat()

			if (!isDirty && (!isRemoteSet || remote != value)) {
				notifyEndpoints(this)
				isDirty = true
				isRemoteSet = true
				remote = value
			}

			return isDirty
		}

		override fun markDirty() {
			check(!isRemoved) { "Field was removed" }
			notifyEndpoints(this)
			isDirty = true
		}

		override val float: Float
			get() {
				if (isClientValue) {
					return clientValue
				} else {
					return getter.getAsFloat()
				}
			}

		override fun write(stream: DataOutputStream, endpoint: Endpoint) {
			check(!isRemoved) { "Field was removed" }
			stream.writeFloat(getter.getAsFloat())
			isDirty = false
		}

		override fun read(stream: DataInputStream) {
			check(!isRemoved) { "Field was removed" }
			val newValue = stream.readFloat()
			clientValue = newValue
			isClientValue = true
			observer.accept(newValue)
		}

		@Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property"))
		override fun getValue(thisRef: Any?, property: KProperty<*>): Float {
			return float
		}
	}

	/**
	 * Networked value with backing getter which is constantly polled
	 *
	 * This class has concrete implementation for [Double] primitive
	 */
	inner class ComputedDoubleField(private val getter: DoubleSupplier, private val observer: DoubleConsumer = DoubleConsumer {}) : AbstractField<Double>(), IDoubleField {
		private var remote: Double = 0.0
		private var isRemoteSet = false
		private var clientValue: Double = 0.0
		private var isClientValue = false

		override val property = object : IDoubleProperty {
			override fun getValue(thisRef: Any?, property: KProperty<*>): Double {
				return double
			}
		}

		init {
			observers.add(this)
		}

		override fun observe(): Boolean {
			check(!isRemoved) { "Field was removed" }

			val value = getter.asDouble

			if (!isDirty && (!isRemoteSet || remote != value)) {
				notifyEndpoints(this)
				isDirty = true
				isRemoteSet = true
				remote = value
			}

			return isDirty
		}

		override val double: Double
			get() {
				if (isClientValue) {
					return clientValue
				} else {
					return getter.asDouble
				}
			}

		override fun write(stream: DataOutputStream, endpoint: Endpoint) {
			check(!isRemoved) { "Field was removed" }
			stream.writeDouble(getter.asDouble)
			isDirty = false
		}

		override fun read(stream: DataInputStream) {
			check(!isRemoved) { "Field was removed" }
			val newValue = stream.readDouble()
			clientValue = newValue
			isClientValue = true
			observer.accept(newValue)
		}

		@Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property"))
		override fun getValue(thisRef: Any?, property: KProperty<*>): Double {
			return double
		}
	}

	/**
	 * Networked value with backing getter which is constantly polled
	 *
	 * This class has concrete implementation for [Int] primitive
	 */
	abstract inner class AbstractComputedIntField(protected val getter: IntSupplier, protected val observer: IntConsumer = IntConsumer {}) : AbstractField<Int>(), IIntField {
		private var remote: Int = 0
		private var isRemoteSet = false
		protected var clientValue: Int = 0
		protected var isClientValue = false

		final override val property = object : IIntProperty {
			override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
				return int
			}
		}

		init {
			observers.add(this)
		}

		final override fun observe(): Boolean {
			check(!isRemoved) { "Field was removed" }

			val value = getter.asInt

			if (!isDirty && (!isRemoteSet || remote != value)) {
				notifyEndpoints(this)
				isDirty = true
				isRemoteSet = true
				remote = value
			}

			return isDirty
		}

		final override val int: Int
			get() {
				if (isClientValue) {
					return clientValue
				} else {
					return getter.asInt
				}
			}

		@Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property"))
		override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
			return int
		}
	}

	/**
	 * Networked value with backing getter which is constantly polled
	 *
	 * This class has concrete implementation for [Int] primitive, networking it as variable length integer
	 */
	inner class ComputedIntField(getter: IntSupplier, observer: IntConsumer = IntConsumer {}) : AbstractComputedIntField(getter, observer) {
		override fun write(stream: DataOutputStream, endpoint: Endpoint) {
			check(!isRemoved) { "Field was removed" }
			stream.writeVarIntLE(getter.asInt)
			isDirty = false
		}

		override fun read(stream: DataInputStream) {
			check(!isRemoved) { "Field was removed" }
			val newValue = stream.readVarIntLE()
			clientValue = newValue
			isClientValue = true
			observer.accept(newValue)
		}
	}

	/**
	 * Networked value with backing getter which is constantly polled
	 *
	 * This class has concrete implementation for [Int] primitive, networking it as 4 octets
	 */
	inner class ComputedFixedIntField(getter: IntSupplier, observer: IntConsumer = IntConsumer {}) : AbstractComputedIntField(getter, observer) {
		override fun write(stream: DataOutputStream, endpoint: Endpoint) {
			check(!isRemoved) { "Field was removed" }
			stream.writeInt(getter.asInt)
			isDirty = false
		}

		override fun read(stream: DataInputStream) {
			check(!isRemoved) { "Field was removed" }
			val newValue = stream.readInt()
			clientValue = newValue
			isClientValue = true
			observer.accept(newValue)
		}
	}

	/**
	 * Networked value with backing getter which is constantly polled
	 *
	 * This class has concrete implementation for [Long] primitive
	 */
	abstract inner class AbstractComputedLongField(protected val getter: LongSupplier, protected val observer: LongConsumer = LongConsumer {}) : AbstractField<Long>(), ILongField {
		private var remote: Long = 0L
		private var isRemoteSet = false
		protected var clientValue: Long = 0L
		protected var isClientValue = false

		final override val property = object : ILongProperty {
			override fun getValue(thisRef: Any?, property: KProperty<*>): Long {
				return long
			}
		}

		init {
			observers.add(this)
		}

		final override fun observe(): Boolean {
			check(!isRemoved) { "Field was removed" }

			val value = getter.asLong

			if (!isDirty && (!isRemoteSet || remote != value)) {
				notifyEndpoints(this)
				isDirty = true
				isRemoteSet = true
				remote = value
			}

			return isDirty
		}

		final override val long: Long
			get() {
				if (isClientValue) {
					return clientValue
				} else {
					return getter.asLong
				}
			}

		@Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property"))
		override fun getValue(thisRef: Any?, property: KProperty<*>): Long {
			return long
		}
	}

	/**
	 * Networked value with backing getter which is constantly polled
	 *
	 * This class has concrete implementation for [Long] primitive, networking it as variable length integer
	 */
	inner class ComputedLongField(getter: LongSupplier, observer: LongConsumer = LongConsumer {}) : AbstractComputedLongField(getter, observer) {
		override fun write(stream: DataOutputStream, endpoint: Endpoint) {
			check(!isRemoved) { "Field was removed" }
			stream.writeVarLongLE(getter.asLong)
			isDirty = false
		}

		override fun read(stream: DataInputStream) {
			check(!isRemoved) { "Field was removed" }
			val newValue = stream.readVarLongLE()
			clientValue = newValue
			isClientValue = true
			observer.accept(newValue)
		}
	}

	/**
	 * Networked value with backing getter which is constantly polled
	 *
	 * This class has concrete implementation for [Long] primitive, networking it as 8 octets
	 */
	inner class ComputedFixedLongField(getter: LongSupplier, observer: LongConsumer = LongConsumer {}) : AbstractComputedLongField(getter, observer) {
		override fun write(stream: DataOutputStream, endpoint: Endpoint) {
			check(!isRemoved) { "Field was removed" }
			stream.writeLong(getter.asLong)
			isDirty = false
		}

		override fun read(stream: DataInputStream) {
			check(!isRemoved) { "Field was removed" }
			val newValue = stream.readLong()
			clientValue = newValue
			isClientValue = true
			observer.accept(newValue)
		}
	}

	/**
	 * Networked value with backing getter which is constantly polled
	 *
	 * This class has concrete implementation for [Boolean] primitive
	 */
	inner class ComputedBooleanField(private val getter: BooleanSupplier, private val observer: BooleanConsumer = BooleanConsumer {}) : AbstractField<Boolean>(), IBooleanField {
		private var remote: Boolean = false
		private var isRemoteSet = false
		private var clientValue: Boolean = false
		private var isClientValue = false

		override val property = object : IBooleanProperty {
			override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean {
				return boolean
			}
		}

		init {
			observers.add(this)
		}

		override fun observe(): Boolean {
			check(!isRemoved) { "Field was removed" }

			val value = getter.asBoolean

			if (!isDirty && (!isRemoteSet || remote != value)) {
				notifyEndpoints(this)
				isDirty = true
				isRemoteSet = true
				remote = value
			}

			return isDirty
		}

		override val boolean: Boolean
			get() {
				if (isClientValue) {
					return clientValue
				} else {
					return getter.asBoolean
				}
			}

		override fun write(stream: DataOutputStream, endpoint: Endpoint) {
			check(!isRemoved) { "Field was removed" }
			stream.writeBoolean(getter.asBoolean)
			isDirty = false
		}

		override fun read(stream: DataInputStream) {
			check(!isRemoved) { "Field was removed" }
			val newValue = stream.readBoolean()
			clientValue = newValue
			isClientValue = true
			observer.accept(newValue)
		}

		@Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property"))
		override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean {
			return boolean
		}
	}

	/**
	 * Networked variable with backing field holding mutable value, which is constantly observed for changes
	 */
	inner class ObservedField<V> : AbstractField<V>, IMutableField<V> {
		private val codec: IStreamCodec<V>
		private val getter: () -> V
		private val setter: (V) -> Unit
		private var remote: V

		override var value: V
			get() = getter.invoke()
			set(value) { setter.invoke(value) }

		constructor(field: KMutableProperty0<V>, codec: IStreamCodec<V>) : super() {
			this.codec = codec
			getter = field::get
			setter = field::set
			remote = codec.copy(value)
		}

		constructor(getter: () -> V, setter: (V) -> Unit, codec: IStreamCodec<V>) : super() {
			this.codec = codec
			this.getter = getter
			this.setter = setter
			remote = codec.copy(value)
		}

		init {
			observers.add(this)
		}

		override fun observe(): Boolean {
			check(!isRemoved) { "Field was removed" }

			val value = value

			if (!isDirty && !codec.compare(remote, value)) {
				notifyEndpoints(this)
				isDirty = true
				remote = codec.copy(value)
			}

			return isDirty
		}

		override fun write(stream: DataOutputStream, endpoint: Endpoint) {
			check(!isRemoved) { "Field was removed" }
			codec.write(stream, value)
			isDirty = false
		}

		override fun read(stream: DataInputStream) {
			check(!isRemoved) { "Field was removed" }
			this.value = codec.read(stream)
		}
	}

	inner class Set<E>(
		private val codec: IStreamCodec<E>,
		private val backingSet: MutableSet<E>,
		private val callback: ((changes: Collection<SetChangeset<E>>) -> Unit)? = null,
	) : AbstractField<MutableSet<E>>() {
		private var isRemote = false

		private fun pushBacklog(element: E, action: (DataOutputStream) -> Unit) {
			check(!isRemote) { "Field marked as remote" }

			val pair = element to action

			forEachEndpoint {
				val list = it.getSetBacklog(this)
				val iterator = list.listIterator()

				for (value in iterator) {
					if (value.first == element) {
						iterator.remove()
					}
				}

				list.addLast(pair)
			}
		}

		override fun observe(): Boolean {
			return false
		}

		override fun remove() {
			if (!isRemoved) {
				forEachEndpoint { it.removeSetBacklog(this) }
			}

			super.remove()
		}

		override fun markDirty(endpoint: Endpoint) {
			super.markDirty(endpoint)

			endpoint.getSetBacklog(this).let {
				it.clear()
				it.add(null to ClearBacklogEntry)

				for (value in backingSet) {
					it.add(value to { it.write(ChangesetAction.ADD.ordinal + 1); codec.write(it, value) })
				}
			}
		}

		override val value: MutableSet<E> = object : MutableSet<E> {
			override fun add(element: E): Boolean {
				if (backingSet.add(element)) {
					if (!isRemote) {
						markDirty()

						val copy = codec.copy(element)

						pushBacklog(element) {
							it.write(ChangesetAction.ADD.ordinal + 1)
							codec.write(it, copy)
						}
					}

					return true
				}

				return false
			}

			override fun addAll(elements: Collection<E>): Boolean {
				var any = false
				elements.forEach { any = add(it) || any }
				return any
			}

			override fun clear() {
				if (backingSet.isNotEmpty()) {
					backingSet.clear()

					if (!isRemote) {
						markDirty()

						forEachEndpoint {
							it.getSetBacklog(this@Set).let {
								it.clear()
								it.add(null to ClearBacklogEntry)
							}
						}
					}
				}
			}

			override fun iterator(): MutableIterator<E> {
				return object : MutableIterator<E> {
					private val parent = backingSet.iterator()
					private var lastElement: Any? = Mark

					override fun hasNext(): Boolean {
						return parent.hasNext()
					}

					override fun next(): E {
						return parent.next().also { lastElement = it }
					}

					override fun remove() {
						parent.remove()
						val lastElement = lastElement

						if (lastElement !== Mark) {
							this.lastElement = Mark

							if (!isRemote) {
								markDirty()

								@Suppress("unchecked_cast")
								pushBacklog(lastElement as E) {
									it.write(ChangesetAction.REMOVE.ordinal + 1)
									codec.write(it, lastElement as E)
								}
							}
						}
					}
				}
			}

			override fun remove(element: E): Boolean {
				if (backingSet.remove(element)) {
					if (!isRemote) {
						markDirty()

						val copy = codec.copy(element)

						pushBacklog(element) {
							it.write(ChangesetAction.REMOVE.ordinal + 1)
							codec.write(it, copy)
						}
					}

					return true
				}

				return false
			}

			override fun removeAll(elements: Collection<E>): Boolean {
				var any = false
				elements.forEach { any = remove(it) || any }
				return any
			}

			override fun retainAll(elements: Collection<E>): Boolean {
				var any = false

				val iterator = iterator()

				for (value in iterator) {
					if (value !in elements) {
						any = true
						iterator.remove()
					}
				}

				return any
			}

			override val size: Int
				get() = backingSet.size

			override fun contains(element: E): Boolean {
				return element in backingSet
			}

			override fun containsAll(elements: Collection<E>): Boolean {
				return backingSet.containsAll(elements)
			}

			override fun isEmpty(): Boolean {
				return backingSet.isEmpty()
			}
		}

		override fun write(stream: DataOutputStream, endpoint: Endpoint) {
			val list = endpoint.getSetBacklog(this)

			for (value in list) {
				value.second.invoke(stream)
			}

			stream.write(0)
			list.clear()
			isDirty = false
		}

		override fun read(stream: DataInputStream) {
			if (!isRemote) {
				isRemote = true
				forEachEndpoint { it.removeSetBacklog(this) }
			}

			isDirty = false

			var action = stream.read()
			val changeset = LinkedList<SetChangeset<E>>()

			while (action != 0) {
				if ((action - 1) !in ChangesetActionList.indices) {
					throw IllegalArgumentException("Unknown changeset action with index ${action - 1}")
				}

				when (ChangesetActionList[action - 1]) {
					ChangesetAction.CLEAR -> {
						changeset.add(SetChangeset(ChangesetAction.CLEAR, null))
						backingSet.clear()
					}

					ChangesetAction.ADD -> {
						val read = codec.read(stream)
						changeset.add(SetChangeset(ChangesetAction.ADD, read))
						backingSet.add(read)
					}

					ChangesetAction.REMOVE -> {
						val read = codec.read(stream)
						changeset.add(SetChangeset(ChangesetAction.REMOVE, read))
						backingSet.remove(read)
					}
				}

				action = stream.read()
			}

			callback?.invoke(changeset)
		}
	}

	inner class Map<K, V>(
		private val keyCodec: IStreamCodec<K>,
		private val valueCodec: IStreamCodec<V>,
		private val backingMap: MutableMap<K, V>,
		private val callback: ((changes: Collection<MapChangeset<K, V>>) -> Unit)? = null,
	) : AbstractField<MutableMap<K, V>>(), IField<MutableMap<K, V>> {
		private var sentAllValues = false
		private var isRemote = false

		private fun pushBacklog(key: Any?, value: (DataOutputStream) -> Unit) {
			val pair = key to value

			forEachEndpoint {
				val list = it.getMapBacklog(this)
				val iterator = list.listIterator()

				for (e in iterator) {
					if (e.first == key) {
						iterator.remove()
					}
				}

				list.addLast(pair)
			}
		}

		override fun observe(): Boolean {
			return false
		}

		override fun markDirty() {
			check(!isRemoved) { "Field was removed" }

			if (isRemote || endpoints.isEmpty())
				return

			isDirty = true
			val backlogs = LinkedList<LinkedList<Pair<Any?, (DataOutputStream) -> Unit>>>()

			forEachEndpoint {
				it.addDirtyField(this)
				val value = it.getMapBacklog(this)
				backlogs.add(value)
				value.clear()
				value.add(null to ClearBacklogEntry)
			}

			for ((key, value) in backingMap) {
				val valueCopy = valueCodec.copy(value)

				val action = { it: DataOutputStream ->
					it.write(ChangesetAction.ADD.ordinal + 1)
					keyCodec.write(it, key)
					valueCodec.write(it, valueCopy)
				}

				for (backlog in backlogs) {
					backlog.add(key to action)
				}
			}
		}

		override fun markDirty(endpoint: Endpoint) {
			check(!isRemoved) { "Field was removed" }

			if (isRemote)
				return

			val backlog = endpoint.getMapBacklog(this)

			backlog.clear()
			backlog.add(null to ClearBacklogEntry)

			for ((key, value) in backingMap) {
				val valueCopy = valueCodec.copy(value)

				backlog.add(key to {
					it.write(ChangesetAction.ADD.ordinal + 1)
					keyCodec.write(it, key)
					valueCodec.write(it, valueCopy)
				})
			}
		}

		private fun lmarkDirty() {
			if (!isDirty) {
				notifyEndpoints(this@Map)
				isDirty = true
			}
		}

		override val value: MutableMap<K, V> = object : ProxiedMap<K, V>(backingMap) {
			override fun onClear() {
				if (isRemote || endpoints.isEmpty())
					return

				forEachEndpoint { endpoint ->
					endpoint.getMapBacklog(this@Map).let {
						it.clear()
						it.add(null to ClearBacklogEntry)
					}
				}

				lmarkDirty()
				hasChanges = true
			}

			override fun onValueAdded(key: K, value: V) {
				if (isRemote || endpoints.isEmpty())
					return

				val keyCopy = keyCodec.copy(key)
				val valueCopy = valueCodec.copy(value)

				pushBacklog(key) {
					@Suppress("BlockingMethodInNonBlockingContext") // false positive
					it.write(ChangesetAction.ADD.ordinal + 1)
					keyCodec.write(it, keyCopy)
					valueCodec.write(it, valueCopy)
				}

				lmarkDirty()
			}

			override fun onValueRemoved(key: K, value: V) {
				if (isRemote || endpoints.isEmpty())
					return

				val keyCopy = keyCodec.copy(key)

				pushBacklog(key) {
					@Suppress("BlockingMethodInNonBlockingContext") // false positive
					it.write(ChangesetAction.REMOVE.ordinal + 1)
					keyCodec.write(it, keyCopy)
				}

				lmarkDirty()
			}
		}

		override fun write(stream: DataOutputStream, endpoint: Endpoint) {
			sentAllValues = false
			isDirty = false

			val iterator = endpoint.getMapBacklog(this).listIterator()

			for (entry in iterator) {
				entry.second.invoke(stream)
				iterator.remove()
			}

			stream.write(0)
		}

		override fun read(stream: DataInputStream) {
			if (!isRemote) {
				isRemote = true
				forEachEndpoint { it.removeMapBacklog(this) }
			}

			isDirty = false

			val changeset = LinkedList<MapChangeset<K, V>>()
			var readAction = stream.read() - 1

			while (readAction != -1) {
				if (readAction >= ChangesetActionList.size) {
					throw IndexOutOfBoundsException("Unknown map action with ID $readAction")
				}

				when (ChangesetActionList[readAction]) {
					ChangesetAction.CLEAR -> {
						backingMap.clear()
						changeset.add(ClearMapChangeset)
					}

					ChangesetAction.ADD -> {
						val key = keyCodec.read(stream)
						val value = valueCodec.read(stream)
						backingMap[key] = value
						changeset.add(MapChangeset(ChangesetAction.ADD, key, value))
					}

					ChangesetAction.REMOVE -> {
						val key = keyCodec.read(stream)
						backingMap.remove(key)
						changeset.add(MapChangeset(ChangesetAction.REMOVE, key, null))
					}
				}

				readAction = stream.read() - 1
			}

			if (changeset.size != 0) {
				callback?.invoke(changeset)
			}
		}
	}

	/**
	 * marks all fields dirty, invalidates mappings for each endpoint
	 */
	fun invalidate() {
		for (field in fields) {
			field?.markDirty()
		}
	}

	/**
	 * Observe changes of all fields with backing computation lambda
	 */
	fun observe(): Boolean {
		var changes = false

		if (observers.isNotEmpty()) {
			for (field in observers) {
				changes = field.observe() || changes
			}
		}

		return changes
	}

	/**
	 * [defaultEndpoint]#collectNetworkPayload
	 */
	fun collectNetworkPayload(): FastByteArrayOutputStream? {
		check(!defaultEndpoint.unused) { "Default endpoint is not used" }
		observe()
		val values = defaultEndpoint.collectNetworkPayload()
		markClean()
		return values
	}

	fun read(stream: InputStream): Int {
		var fieldId = stream.readVarIntLE()
		var i = 0
		val dataStream = DataInputStream(stream)

		while (fieldId != 0) {
			val field = fields.getOrNull(fieldId - 1) ?: throw IllegalArgumentException("Unknown field ID ${fieldId - 1}")
			field.read(dataStream)
			fieldId = stream.readVarIntLE()
			i++
		}

		return i
	}

	private object Mark

	companion object {
		private val ClearBacklogEntry = { stream: DataOutputStream -> stream.write(ChangesetAction.CLEAR.ordinal + 1) }
		private val ChangesetActionList = ChangesetAction.values()
		private val ClearMapChangeset = MapChangeset(ChangesetAction.CLEAR, null, null)
	}
}
