package app.furmeet.events.system.helper

import java.io.File
import java.io.Serializable
import java.lang.ref.WeakReference

abstract class FixedFileObserver(val path: String) : Serializable {
    companion object {
        val observers: MutableMap<String, MutableSet<WeakReference<FixedFileObserver>>> = mutableMapOf()
        private val observerSingletons: MutableMap<String, FileObserverCompat> = mutableMapOf()
        private const val observersLock = false
        private const val observersSingletonLock = false
        private fun newObserverFor(canonical: String, givenPath: String): FileObserverCompat = FileObserverCompatCallable(canonical) { event: Int, _: String? ->
            synchronized(::observersLock) {
                observers[canonical]?.apply {
                    doAsync {
                        removeAll { it.get() == null }
                        forEach {
                            nullAndLogIfException {
                                it.get()?.onEvent(event, givenPath)
                            }
                        }
                    }
                }
            }
        }

        fun getObserverFor(givenPath: String, retrying: Boolean = false): FileObserverCompat {
            val canonical = File(givenPath).absoluteFile.canonicalPath
            val observerSaved: Pair<FileObserverCompat, Boolean> =
                    synchronized(::observersSingletonLock) { observerSingletons[canonical] }.let {
                        if (it != null)
                            Pair(it, true)
                        else
                            Pair(newObserverFor(canonical, givenPath), false)
                    }
            val observer = observerSaved.first
            val saved = observerSaved.second
            if (!saved)
                synchronized(::observersSingletonLock) { observerSingletons[canonical] = observer }
            return observer.apply {
                nullAndLogIfException {
                    stopWatching()
                }
                try {
                    startWatching()
                } catch (npe: NullPointerException) {
                    logException(npe)
                    if (!retrying) {
                        synchronized(::observersSingletonLock) { observerSingletons.remove(canonical) }
                        return getObserverFor(givenPath, true)
                    } else
                        throw npe
                }
            }
        }
    }

    private val canonicalPath = File(path).absoluteFile.canonicalPath
    private var watching = false

    init {
        synchronized(::observersLock) {
            getObserverFor(path)
            if (observers[canonicalPath] == null) {
                observers[canonicalPath] = mutableSetOf()
            }
        }
    }

    fun startWatching() = synchronized(::observersLock) {
        val set = observers[canonicalPath] ?: mutableSetOf()
        if (canonicalPath !in observers) observers[canonicalPath] = set
        set.add(WeakReference(this))
    }

    fun stopWatching() = synchronized(::observersLock) {
        observers[canonicalPath]?.removeAll {
            it.get()?.equals(this) ?: true
        } ?: false
    }

    val isWatching get() = watching
    abstract fun onEvent(event: Int, path: String)
}