package ru.dbotthepony.mc.otm.core.collect

abstract class ProxiedMap<K, V>(protected val backingMap: MutableMap<K, V> = HashMap()) : MutableMap<K, V> {
	protected abstract fun onClear()
	protected abstract fun onValueAdded(key: K, value: V)
	protected abstract fun onValueRemoved(key: K, value: V)

	final override val size: Int
		get() = backingMap.size

	final override fun containsKey(key: K): Boolean {
		return backingMap.containsKey(key)
	}

	final override fun containsValue(value: V): Boolean {
		return backingMap.containsValue(value)
	}

	final override fun get(key: K): V? {
		return backingMap[key]
	}

	final override fun isEmpty(): Boolean {
		return backingMap.isEmpty()
	}

	final override val entries: MutableSet<MutableMap.MutableEntry<K, V>> by lazy {
		object : MutableSet<MutableMap.MutableEntry<K, V>> {
			override fun add(element: MutableMap.MutableEntry<K, V>): Boolean {
				this@ProxiedMap[element.key] = element.value
				return true
			}

			override fun addAll(elements: Collection<MutableMap.MutableEntry<K, V>>): Boolean {
				for (value in elements) {
					add(value)
				}

				return true
			}

			override fun clear() {
				this@ProxiedMap.clear()
			}

			private val setParent = this@ProxiedMap.backingMap.entries

			override fun iterator(): MutableIterator<MutableMap.MutableEntry<K, V>> {
				return object : MutableIterator<MutableMap.MutableEntry<K, V>> {
					private val parent = setParent.iterator()
					private var last: MutableMap.MutableEntry<K, V>? = null

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

					override fun next(): MutableMap.MutableEntry<K, V> {
						return parent.next().also { last = it }
					}

					override fun remove() {
						val last = last ?: throw IllegalStateException("Never called next()")
						parent.remove()
						onValueRemoved(last.key, last.value)
					}
				}
			}

			override fun remove(element: MutableMap.MutableEntry<K, V>): Boolean {
				if (setParent.remove(element)) {
					onValueRemoved(element.key, element.value)
					return true
				}

				return false
			}

			override fun removeAll(elements: Collection<MutableMap.MutableEntry<K, V>>): Boolean {
				var any = false

				for (element in elements) {
					any = remove(element) || any
				}

				return any
			}

			override fun retainAll(elements: Collection<MutableMap.MutableEntry<K, V>>): Boolean {
				val iterator = this.iterator()
				var any = false

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

				return any
			}

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

			override fun contains(element: MutableMap.MutableEntry<K, V>): Boolean {
				return setParent.contains(element)
			}

			override fun containsAll(elements: Collection<MutableMap.MutableEntry<K, V>>): Boolean {
				return setParent.containsAll(elements)
			}

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

	final override val keys: MutableSet<K> by lazy {
		object : MutableSet<K> {
			val parent = this@ProxiedMap.backingMap.keys

			override fun add(element: K): Boolean {
				throw UnsupportedOperationException()
			}

			override fun addAll(elements: Collection<K>): Boolean {
				throw UnsupportedOperationException()
			}

			override fun clear() {
				this@ProxiedMap.clear()
			}

			override fun iterator(): MutableIterator<K> {
				return object : MutableIterator<K> {
					private val parentIterator = parent.iterator()
					private var last: K? = null

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

					override fun next(): K {
						return parentIterator.next().also { last = it }
					}

					override fun remove() {
						val last = last ?: throw IllegalStateException("Never called next()")
						val value = this@ProxiedMap[last] ?: throw ConcurrentModificationException()
						parentIterator.remove()
						onValueRemoved(last, value)
					}
				}
			}

			override fun remove(element: K): Boolean {
				return this@ProxiedMap.remove(element) != null
			}

			override fun removeAll(elements: Collection<K>): Boolean {
				var changes = false

				for (element in elements) {
					changes = remove(element) || changes
				}

				return changes
			}

			override fun retainAll(elements: Collection<K>): Boolean {
				val iterator = this.iterator()
				var any = false

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

				return any
			}

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

			override fun contains(element: K): Boolean {
				return parent.contains(element)
			}

			override fun containsAll(elements: Collection<K>): Boolean {
				return parent.containsAll(elements)
			}

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

	final override val values: MutableCollection<V> by lazy {
		object : MutableCollection<V> {
			private val parent = this@ProxiedMap.backingMap.values

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

			override fun contains(element: V): Boolean {
				return parent.contains(element)
			}

			override fun containsAll(elements: Collection<V>): Boolean {
				return parent.containsAll(elements)
			}

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

			override fun add(element: V): Boolean {
				throw UnsupportedOperationException()
			}

			override fun addAll(elements: Collection<V>): Boolean {
				throw UnsupportedOperationException()
			}

			override fun clear() {
				this@ProxiedMap.clear()
			}

			override fun iterator(): MutableIterator<V> {
				return object : MutableIterator<V> {
					private val parentIterator = parent.iterator()

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

					override fun next(): V {
						return parentIterator.next()
					}

					override fun remove() {
						parentIterator.remove()
					}
				}
			}

			override fun remove(element: V): Boolean {
				val indexOf = this@ProxiedMap.backingMap.firstNotNullOfOrNull { if (it.value == element) it.key else null } ?: return false
				this@ProxiedMap.remove(indexOf)
				return true
			}

			override fun removeAll(elements: Collection<V>): Boolean {
				var changes = false

				for (element in elements) {
					changes = remove(element) || changes
				}

				return changes
			}

			override fun retainAll(elements: Collection<V>): Boolean {
				val iterator = this.iterator()
				var changes = false

				for (element in iterator) {
					if (element !in elements) {
						iterator.remove()
						changes = true
					}
				}

				return changes
			}
		}
	}

	override fun clear() {
		onClear()
		return backingMap.clear()
	}

	override fun put(key: K, value: V): V? {
		val existing = backingMap.put(key, value)

		if (existing == value) {
			return existing
		}

		onValueAdded(key, value)
		return existing
	}

	final override fun putAll(from: Map<out K, V>) {
		for ((k, v) in from) {
			this[k] = v
		}
	}

	override fun remove(key: K): V? {
		val removed = backingMap.remove(key)

		if (removed != null) {
			onValueRemoved(key, removed)
		}

		return removed
	}
}

