package com.r3corda.client.fxutils

import com.sun.javafx.collections.MapListenerHelper
import javafx.beans.InvalidationListener
import javafx.collections.MapChangeListener
import javafx.collections.ObservableMap
import java.util.*

/**
 * [ReadOnlyBackedObservableMapBase] is a base class implementing all abstract functions required for an [ObservableMap]
 * using a backing HashMap that subclasses should modify.
 *
 * Non-read-only API calls throw.
 *
 * @param K The key type.
 * @param A The exposed map element type.
 * @param B Auxiliary data subclasses may wish to store in the backing map.
 */
open class ReadOnlyBackedObservableMapBase<K, A, B> : ObservableMap<K, A> {
    protected val backingMap = HashMap<K, Pair<A, B>>()
    private var mapListenerHelper: MapListenerHelper<K, A>? = null

    protected fun fireChange(change: MapChangeListener.Change<out K, out A>) {
        MapListenerHelper.fireValueChangedEvent(mapListenerHelper, change)
    }

    override fun addListener(listener: InvalidationListener) {
        mapListenerHelper = MapListenerHelper.addListener(mapListenerHelper, listener)
    }

    override fun addListener(listener: MapChangeListener<in K, in A>?) {
        mapListenerHelper = MapListenerHelper.addListener(mapListenerHelper, listener)
    }

    override fun removeListener(listener: InvalidationListener?) {
        mapListenerHelper = MapListenerHelper.removeListener(mapListenerHelper, listener)
    }

    override fun removeListener(listener: MapChangeListener<in K, in A>?) {
        mapListenerHelper = MapListenerHelper.removeListener(mapListenerHelper, listener)
    }

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

    override fun containsKey(key: K) = backingMap.containsKey(key)

    override fun containsValue(value: A) = backingMap.any { it.value.first == value }

    override fun get(key: K) = backingMap.get(key)?.first

    override fun isEmpty() = backingMap.isEmpty()

    override val entries: MutableSet<MutableMap.MutableEntry<K, A>> get() = backingMap.entries.fold(mutableSetOf()) { set, entry ->
        set.add(object : MutableMap.MutableEntry<K, A> {
            override var value: A = entry.value.first
            override val key = entry.key
            override fun setValue(newValue: A): A {
                val old = value
                value = newValue
                return old
            }
        })
        set
    }
    override val keys: MutableSet<K> get() = backingMap.keys
    override val values: MutableCollection<A> get() = ArrayList(backingMap.values.map { it.first })

    override fun clear() {
        throw UnsupportedOperationException("clear() can't be called on ReadOnlyObservableMapBase")
    }

    override fun put(key: K, value: A): A {
        throw UnsupportedOperationException("put() can't be called on ReadOnlyObservableMapBase")
    }

    override fun putAll(from: Map<out K, A>) {
        throw UnsupportedOperationException("putAll() can't be called on ReadOnlyObservableMapBase")
    }

    override fun remove(key: K): A {
        throw UnsupportedOperationException("remove() can't be called on ReadOnlyObservableMapBase")
    }

}

fun <A, K> ObservableMap<K, A>.createMapChange(key: K, removedValue: A?, addedValue: A?): MapChangeListener.Change<K, A> {
    return object : MapChangeListener.Change<K, A>(this) {
        override fun getKey() = key
        override fun wasRemoved() = removedValue != null
        override fun wasAdded() = addedValue != null
        override fun getValueRemoved() = removedValue
        override fun getValueAdded() = addedValue
    }
}

