package fr.username404.snowygui.config import com.typesafe.config.Config import com.typesafe.config.ConfigException import com.typesafe.config.ConfigFactory.empty import com.typesafe.config.ConfigFactory.load import com.typesafe.config.ConfigFactory.parseFile import com.typesafe.config.ConfigFactory.parseString import com.typesafe.config.ConfigRenderOptions import com.typesafe.config.ConfigValueFactory import fr.username404.snowygui.ClickGui import fr.username404.snowygui.Snowy import fr.username404.snowygui.Snowy.Companion.MissingComponent import fr.username404.snowygui.gui.feature.shouldSave import fr.username404.snowygui.gui.feature.Category import fr.username404.snowygui.gui.feature.Macro import io.github.config4k.extract import io.github.config4k.getValue import net.minecraft.client.Minecraft import java.io.File import kotlin.reflect.KProperty import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import org.jetbrains.annotations.ApiStatus import kotlin.reflect.KClass import kotlin.reflect.full.isSuperclassOf @OptIn(ExperimentalSerializationApi::class) object Configuration { @Deprecated("Use the getValue or setValue methods instead", level = DeprecationLevel.ERROR) @JvmStatic val ModifiableValues: MutableMap by lazy { mutableMapOf( "enabledFeatures" to enabledFeatures, "macros" to lazy { Category.MACROS.box.buttons.map { Json.encodeToString(it as Macro) } }, "box_colors" to mapOf(*ClickGui.clickBoxes.filter { box -> (box.name.key != MissingComponent.key) && Category.fromBox(box)?.categoryColor != box.color }.map { it.name.key to it.color }.toTypedArray()), ) } private val configDirectory: String = (Minecraft.getInstance().gameDirectory.absolutePath + File.separator + "config").also { File(it).mkdir() } private val file: File = File(configDirectory + File.separator + "snowy.conf") private val obtained: Config = run { var result: Config = empty() with(file) { if (!exists()) { createNewFile() setWritable(true) setReadable(true) } else try { result = parseFile(file) } catch (_: ConfigException) { Snowy.logs.warn("Could not parse the snowy configuration file, the default configuration will be used instead.") } } load(result).withFallback( parseString( """ |Snowy { | macros = [] | sortAlphabetically = true |} """.trimMargin() ) ).extract("Snowy") }.also { runBlocking { writeConfig(it) } } val enabledFeatures = mutableMapOf().apply { "enabledFeatures".let { obtained.run { if (hasPath(it)) { putAll(extract>(it).filterKeys { keyName -> ClickGui.clickBoxes.any { it.buttons.any { button -> button.info.shouldSave() && button.title == keyName } }}) } } } } internal val foundMacros: Set = run { obtained.getStringList("macros").map { Json.decodeFromString(it) }.toSet() } init { Runtime.getRuntime().addShutdownHook( Thread { runBlocking { ClickGui.boxContext { enabledFeatures.putAll(buttons.filter { it.info.shouldSave() }.map { it.title to it.toggled }) } writeConfig(obtained.withFullModifiableValues()).join() } } ) } private fun Config.withFullModifiableValues() = @Suppress("DEPRECATION_ERROR") ModifiableValues.entries.fold(this) { previous, entry -> previous.withValue(entry.key, entry.value.let { ConfigValueFactory.fromAnyRef( if (it !is Lazy<*>) it else it.value ) }) } private suspend fun writeConfig(c: Config) = coroutineScope { launch(start = CoroutineStart.UNDISPATCHED) { file.writeText( """ |Snowy { |${ c.root().render( ConfigRenderOptions.defaults() .setFormatted(true) .setJson(false) .setOriginComments(false) ).prependIndent(" ").trimEnd() } |} """.trimMargin() ) } } operator fun invoke() = obtained @JvmSynthetic @ApiStatus.Internal @Suppress("DeprecatedCallableAddReplaceWith") @Deprecated("This method should only be used in the getValue operator of the Configuration object", level = DeprecationLevel.ERROR) fun convertValue(type: KClass): T? = @Suppress("UNCHECKED_CAST") when { Number::class.isSuperclassOf(type) -> when (type) { Double::class -> 2.0 Int::class, UInt::class, Short::class, UShort::class, Long::class, ULong::class -> 2 Float::class -> 2F else -> null } as T type == Boolean::class -> false as T else -> null } inline operator fun getValue(ref: Any?, property: KProperty<*>): T = @Suppress("DEPRECATION_ERROR") if (ModifiableValues.containsKey(property.name)) ModifiableValues[property.name] as T else try { invoke().getValue(ref, property) } catch (e: ConfigException) { convertValue(T::class) ?: throw e } operator fun setValue(ref: Any?, property: KProperty<*>, value: T) = @Suppress("DEPRECATION_ERROR") ModifiableValues.setValue(ref, property, value) }