All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.io.nacular.doodle.utils.Observables.kt Maven / Gradle / Ivy

There is a newer version: 0.10.4
Show newest version
package io.nacular.doodle.utils

import kotlin.math.max
import kotlin.math.min
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

/**
 * Created by Nicholas Eddy on 10/21/17.
 */
public typealias SetObserver      = (source: S, removed: Set,      added: Set                                    ) -> Unit
public typealias ListObserver     = (source: S, removed: Map, added: Map, moved: Map>) -> Unit
public typealias ChangeObserver      = (source: S                                                                         ) -> Unit
public typealias PropertyObserver = (source: S, old: T, new: T                                                         ) -> Unit

public fun  setObserver(source: B, to: SetObserver): SetObserver = { _,removed,added ->
    to(source, removed, added)
}

public interface Pool {
    public operator fun plusAssign (item: T)
    public operator fun minusAssign(item: T)
}

public typealias ChangeObservers      = Pool>
public typealias PropertyObservers = Pool>

public expect open class SetPool(delegate: MutableSet): Pool, Set {
    public constructor()

    protected val delegate: MutableSet
}

public expect class ChangeObserversImpl(source: S, mutableSet: MutableSet>): SetPool> {
    public constructor(source: S)

    public operator fun invoke(): Unit
}

public expect class PropertyObserversImpl(source: S, mutableSet: MutableSet>): SetPool> {
    public constructor(source: S)

    public operator fun invoke(old: T, new: T): Unit
}

public interface ObservableList: MutableList {
    public val changed: Pool, E>>

    public fun move(element: E, to: Int): Boolean

    public operator fun plusAssign(element: E)

    public operator fun minusAssign(element: E)

    public fun replaceAll(elements: Collection): Boolean

    public fun  batch(block: MutableList.() -> T): T

    public fun notifyChanged(index: Int)

    public companion object {
        public operator fun  invoke(             ): ObservableList = ObservableListImpl(mutableListOf     ())
        public operator fun  invoke(list: List): ObservableList = ObservableListImpl(list.toMutableList())

        internal fun  wrap(list: MutableList): ObservableList = ObservableListImpl(list)
    }
}

public fun  ObservableList.sortWith(comparator: Comparator) {
    batch { sortWith(comparator) }
}

public fun  ObservableList.sortWithDescending(comparator: Comparator) {
    batch { sortWith(comparator.reversed()) }
}

public open class ObservableListImpl internal constructor(private val list: MutableList): ObservableList, MutableList by list {

    private val changed_ = SetPool, E>>()
    public override val changed: Pool, E>> = changed_

    public override fun move(element: E, to: Int): Boolean {
        val oldIndex = indexOf(element)

        if (to !in 0 until size || oldIndex < 0 || oldIndex == to) return false

        list.removeAt(oldIndex)
        list.add     (to, element)

        changed_.forEach {
            it(this, mapOf(), mapOf(), mapOf(to to (oldIndex to element)))
        }

        return true
    }

    override fun iterator(): MutableIterator = list.iterator().let {
        object: MutableIterator by it {
            private var index = -1

            override fun next() = it.next().also { index++ }

            override fun remove() {
                val element = list[index--]
                it.remove()
                changed_.forEach {
                    it(this@ObservableListImpl, mapOf(index to element), mapOf(), mapOf())
                }
            }
        }
    }

    public override operator fun plusAssign(element: E) {
        add(element)
    }

    public override operator fun minusAssign(element: E) {
        remove(element)
    }

    override fun add(element: E): Boolean = list.add(element).ifTrue {
        changed_.forEach {
            it(this, mapOf(), mapOf(list.size - 1 to element), mapOf())
        }
    }

    override fun remove(element: E): Boolean {
        val index = list.indexOf(element)

        return when {
            index < 0 -> false
            else      -> list.remove(element).ifTrue { changed_.forEach { it(this, mapOf(index to element), mapOf(), mapOf()) } }
        }
    }

    public override fun addAll(elements: Collection): Boolean = batch { addAll(elements) }

    public override fun addAll(index: Int, elements: Collection): Boolean = batch { addAll(index, elements) }

    public override fun removeAll(elements: Collection): Boolean = batch { removeAll(elements) }
    public override fun retainAll(elements: Collection): Boolean = batch { retainAll(elements) }

    public override fun replaceAll(elements: Collection): Boolean = batch { clear(); addAll(elements) }

    private class Move(val from: Int, val to:Int, val value: T) {
        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (other !is Move<*>) return false

//            if (value != other.value) return false

            if (from !in listOf(other.from, other.to  )) return false
            if (to   !in listOf(other.to,   other.from)) return false

            return true
        }

        override fun hashCode(): Int {
            val first = min(from, to)
            val last  = max(from, to)

            var result = first
            result = 31 * result + last
            return result
        }
    }

    public override fun  batch(block: MutableList.() -> T): T = if (changed_.isEmpty()) {
        list.run(block)
    } else {
        // TODO: Can this be optimized?
        val old = ArrayList(list)

        list.run(block).also {
            diffLists(old, this)?.let { diffs ->
                changed_.forEach {
                    it(this, diffs.removed, diffs.added, diffs.moved)
                }
            }
        }
    }

    override fun clear() {
        val size    = list.size
        val oldList = ArrayList(list)

        list.clear()

        changed_.forEach {
            it(this, (0 until size).associate { it to oldList[it] }, mapOf(), mapOf())
        }
    }

    override operator fun set(index: Int, element: E): E = list.set(index, element).also { old ->
        if (old != element) {
            changed_.forEach {
                it(this, mapOf(index to old), mapOf(index to element), mapOf())
            }
        }
    }

    public override fun notifyChanged(index: Int) {
        changed_.forEach {
            it(this, mapOf(index to this[index]), mapOf(index to this[index]), mapOf())
        }
    }

    override fun add(index: Int, element: E) {
        list.add(index, element)

        changed_.forEach {
            it(this, mapOf(), mapOf(index to element), mapOf())
        }
    }

    override fun removeAt(index: Int): E = list.removeAt(index).also { removed ->
        changed_.forEach {
            it(this, mapOf(index to removed), mapOf(), mapOf())
        }
    }
}

internal data class DiffResult(val removed: Map, val added: Map, val moved: Map>)

private class Move(var from: Int, var to:Int, var value: T) {
    override fun toString() = (from to to to value).toString()

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is Move<*>) return false

//            if (value != other.value) return false

        if (from !in listOf(other.from, other.to  )) return false
        if (to   !in listOf(other.to,   other.from)) return false

        return true
    }

    override fun hashCode(): Int {
        val first = min(from, to)
        val last  = max(from, to)

        var result = first
        result = 31 * result + last
        return result
    }
}

internal fun  diffLists(old: List, new: List): DiffResult? {
    if (old != new) {
        val removed       = mutableMapOf()
        val added         = mutableMapOf()
        val uniqueMoved   = mutableListOf>()
        val unusedIndexes = new.indices.toMutableSet()

        old.forEachIndexed { index, item ->
            if (index >= new.size || new[index] != item) {
                when (val newIndex = unusedIndexes.firstOrNull { new.getOrNull(it) == item }) {
                    null -> removed[index] = item
                    else -> {
                        uniqueMoved += Move(from = index, to = newIndex, value = item)
                        unusedIndexes.remove(newIndex)
                    }
                }
            }
        }

        val removedIndexes = removed.keys.sorted()
        var j = removedIndexes.size - 1
        var i = uniqueMoved.size - 1

        while(j >= 0 && i >= 0) {
            when {
                uniqueMoved[i].from > removedIndexes[j] -> {
                    uniqueMoved[i].from -= j + 1
                    if (uniqueMoved[i].from == uniqueMoved[i].to) {
                        uniqueMoved.removeAt(i)
                    }

                    --i
                }
                else                                    -> {
                    if (uniqueMoved[i].from == removedIndexes[j]) {
                        uniqueMoved.removeAt(i)
                        --i
                    }

                    --j
                }
            }
        }

        unusedIndexes.forEach {
            val item = new[it]

            if (it >= old.size || old[it] != item) {
                added[it] = item

                // Adjust all the moves
                uniqueMoved.filter { element -> element.from >= it }.sortedByDescending { it.to }.forEach { element ->
                    uniqueMoved.remove(element)

                    if (element.from + 1 != element.to) {
                        uniqueMoved.add(Move(element.from + 1, element.to, element.value))
                    }
                }
            }
        }

        return DiffResult(removed = removed, added = added, moved = uniqueMoved.toSet().associate { it.from to (it.to to it.value) })
    }

    return null
}

public open class ObservableSet private constructor(protected val set: MutableSet): MutableSet by set {
    private val changed_ = SetPool, E>>()
    public val changed: Pool, E>> = changed_

    override fun add(element: E): Boolean = set.add(element).ifTrue {
        changed_.forEach {
            it(this, emptySet(), setOf(element))
        }
    }

    public override fun remove(element: E): Boolean = set.remove(element).ifTrue { changed_.forEach { it(this, setOf(element), emptySet()) } }

    public override fun addAll(elements: Collection): Boolean = batch { addAll(elements) }

    public override fun removeAll(elements: Collection): Boolean = batch { removeAll(elements) }
    public override fun retainAll(elements: Collection): Boolean = batch { retainAll(elements) }

    public fun replaceAll(elements: Collection): Boolean = batch { clear(); addAll(elements) }

    public fun  batch(block: MutableSet.() -> T): T = if (changed_.isEmpty()) {
        set.run(block)
    } else {
        // TODO: Can this be optimized?
        val old = HashSet(set)

        set.run(block).also {
            if (old != this) {
                changed_.forEach {
                    it(this, old.asSequence().filter { it !in set }.toSet(), set.asSequence().filter { it !in old }.toSet())
                }
            }
        }
    }

    override fun clear() {
        val oldSet = HashSet(set)

        set.clear()

        changed_.forEach {
            it(this, oldSet, emptySet())
        }
    }

    public companion object {
        public operator fun  invoke(           ): ObservableSet = ObservableSet(mutableSetOf())
        public operator fun  invoke(set: Set): ObservableSet = ObservableSet(set.toMutableSet())
    }
}

public fun  observable(initial: T, onChange: S.(old: T, new: T) ->Unit): ReadWriteProperty = ObservableProperty(initial) { thisRef, old, new ->
    onChange(thisRef, old, new)
}

public fun  observable(initial: T, observers: Iterable>): ReadWriteProperty = ObservableProperty(initial) { thisRef, old, new ->
    observers.forEach { it(thisRef, old, new) }
}
public fun  observable(initial: T, observers: Iterable>, onChange: (old: T, new: T) -> Unit): ReadWriteProperty = ObservableProperty(initial) { thisRef, old, new ->
    onChange(old, new)
    observers.forEach { it(thisRef, old, new) }
}

private class ObservableProperty(initial: T, private val onChange: (thisRef: S, old: T, new: T) -> Unit): ReadWriteProperty {
    private var value: T = initial

    override operator fun getValue(thisRef: S, property: KProperty<*>): T = value

    override operator fun setValue(thisRef: S, property: KProperty<*>, value: T) {
        if (value != this.value) {
            val old = this.value

            this.value = value

            onChange(thisRef, old, this.value)
        }
    }
}