package ru.dbotthepony.mc.otm.config

import net.minecraftforge.common.ForgeConfigSpec.ConfigValue
import org.apache.logging.log4j.LogManager
import java.util.function.Consumer
import java.util.function.Supplier
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

abstract class ObservedConfigValue<V : Any>(val parent: ConfigValue<String>) : ReadWriteProperty<Any, V>, Supplier<V>, Consumer<V> {
	var rawValue: String by parent
	private var observedValue: String? = null
	private var cachedValue: V? = null

	protected abstract fun fromString(value: String): V?

	/**
	 * value [V] -> newRawValue [String], newValue [V]
	 */
	protected abstract fun toString(value: V): Pair<String, V>

	override fun get(): V {
		if (cachedValue == null || rawValue != observedValue) {
			var deserialized = fromString(rawValue)

			if (deserialized == null) {
				val default = parent.default
				deserialized = fromString(default)

				if (deserialized == null) {
					throw RuntimeException("Deserializer did not accept default value: $default")
				} else {
					LOGGER.error("Config value of <${parent.path.joinToString(".")}>: $rawValue is invalid, replacing with default $default")
				}

				rawValue = default
			}

			cachedValue = deserialized
			observedValue = rawValue
		}

		return cachedValue ?: throw ConcurrentModificationException()
	}

	override fun getValue(thisRef: Any, property: KProperty<*>): V {
		return get()
	}

	override fun accept(value: V) {
		if (cachedValue == null || rawValue != observedValue) {
			cachedValue = fromString(rawValue)
			observedValue = rawValue
		}

		if (cachedValue != value) {
			val (newRaw, newValue) = toString(value)

			if (cachedValue != newValue) {
				rawValue = newRaw
				observedValue = rawValue
				cachedValue = newValue
			}
		}
	}

	override fun setValue(thisRef: Any, property: KProperty<*>, value: V) {
		accept(value)
	}

	companion object {
		private val LOGGER = LogManager.getLogger()
	}
}
