package ch.dissem.yaep.domain

@Suppress("JavaDefaultMethodsNotOverriddenByDelegation")
class ObservableSet<E> private constructor(
    private val mutableSet: MutableSet<E>,
    private val onElementChanged: (before: Set<E>, after: Set<E>) -> Unit
) : MutableSet<E> by mutableSet {
    constructor(
        elements: Collection<E>,
        onElementChanged: (before: Set<E>, after: Set<E>) -> Unit
    ) : this(
        elements.toMutableSet(),
        onElementChanged
    )

    private inner class ObservableIterator(
        private val delegate: MutableIterator<E>
    ) : MutableIterator<E> by delegate {

        override fun remove() {
            val previous = mutableSet.toSet()
            delegate.remove()
            onElementChanged(previous, mutableSet.toSet())
        }
    }

    override fun iterator(): MutableIterator<E> {
        return ObservableIterator(mutableSet.iterator())
    }

    override fun add(element: E): Boolean {
        return observe(setOf(element)) { mutableSet.add(element) }
    }


    override fun remove(element: E): Boolean {
        return observe(setOf(element)) { mutableSet.remove(element) }
    }

    override fun addAll(elements: Collection<E>): Boolean {
        return observe(elements.toSet()) { mutableSet.addAll(elements) }
    }

    override fun removeAll(elements: Collection<E>): Boolean =
        observe(elements.toSet(), mutableSet::removeAll)

    override fun retainAll(elements: Collection<E>): Boolean =
        observe(elements.toSet(), mutableSet::retainAll)

    override fun clear() {
        return observe(setOf()) { mutableSet.clear() }
    }

    private fun <R> observe(values: Set<E>, operation: (Set<E>) -> R): R {
        val previous = mutableSet.toSet()
        val result = operation(values)
        val changed = !(previous.containsAll(mutableSet) && previous.size == mutableSet.size)
        if (changed) {
            val new = mutableSet.toSet()
            onElementChanged(previous, new)
        }
        return result
    }
}
