package ru.dbotthepony.kstarbound.lua import com.google.gson.JsonArray import com.google.gson.JsonElement import com.google.gson.JsonNull import com.google.gson.JsonObject import com.google.gson.JsonPrimitive import it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap import org.classdump.luna.ByteString import org.classdump.luna.Conversions import org.classdump.luna.Table import org.classdump.luna.TableFactory import org.classdump.luna.impl.NonsuspendableFunctionException import org.classdump.luna.runtime.AbstractFunction3 import org.classdump.luna.runtime.ExecutionContext import ru.dbotthepony.kommons.gson.set import ru.dbotthepony.kommons.math.RGBAColor import ru.dbotthepony.kommons.util.AABB import ru.dbotthepony.kommons.util.IStruct2d import ru.dbotthepony.kommons.util.IStruct2i import ru.dbotthepony.kommons.util.IStruct3i import ru.dbotthepony.kommons.util.IStruct4i import ru.dbotthepony.kommons.vector.Vector2d import ru.dbotthepony.kommons.vector.Vector2f import ru.dbotthepony.kommons.vector.Vector2i import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter import ru.dbotthepony.kstarbound.world.physics.Poly fun ExecutionContext.toVector2i(table: Any): Vector2i { val x = indexNoYield(table, 1L) val y = indexNoYield(table, 2L) if (x !is Number) throw ClassCastException("Expected table representing a vector, but value at [1] is not a number: $x") if (y !is Number) throw ClassCastException("Expected table representing a vector, but value at [2] is not a number: $y") return Vector2i(x.toInt(), y.toInt()) } fun ExecutionContext.toVector2d(table: Any): Vector2d { val x = indexNoYield(table, 1L) val y = indexNoYield(table, 2L) if (x !is Number) throw ClassCastException("Expected table representing a vector, but value at [1] is not a number: $x") if (y !is Number) throw ClassCastException("Expected table representing a vector, but value at [2] is not a number: $y") return Vector2d(x.toDouble(), y.toDouble()) } fun ExecutionContext.toPoly(table: Table): Poly { val vertices = ArrayList() for ((_, v) in table) { vertices.add(toVector2d(v)) } return Poly(vertices) } fun ExecutionContext.toVector2f(table: Any): Vector2f { val x = indexNoYield(table, 1L) val y = indexNoYield(table, 2L) if (x !is Number) throw ClassCastException("Expected table representing a vector, but value at [1] is not a number: $x") if (y !is Number) throw ClassCastException("Expected table representing a vector, but value at [2] is not a number: $y") return Vector2f(x.toFloat(), y.toFloat()) } fun ExecutionContext.toColor(table: Any): RGBAColor { val x = indexNoYield(table, 1L) val y = indexNoYield(table, 2L) val z = indexNoYield(table, 3L) val w = indexNoYield(table, 4L) ?: 255 if (x !is Number) throw ClassCastException("Expected table representing a Color, but value at [1] is not a number: $x") if (y !is Number) throw ClassCastException("Expected table representing a Color, but value at [2] is not a number: $y") if (z !is Number) throw ClassCastException("Expected table representing a Color, but value at [3] is not a number: $z") if (w !is Number) throw ClassCastException("Expected table representing a Color, but value at [4] is not a number: $w") return RGBAColor(x.toInt(), y.toInt(), z.toInt(), w.toInt()) } fun ExecutionContext.toAABB(table: Any): AABB { val x = indexNoYield(table, 1L) val y = indexNoYield(table, 2L) val z = indexNoYield(table, 3L) val w = indexNoYield(table, 4L) if (x !is Number) throw ClassCastException("Expected table representing a AABB, but value at [1] is not a number: $x") if (y !is Number) throw ClassCastException("Expected table representing a AABB, but value at [2] is not a number: $y") if (z !is Number) throw ClassCastException("Expected table representing a AABB, but value at [3] is not a number: $z") if (w !is Number) throw ClassCastException("Expected table representing a AABB, but value at [4] is not a number: $w") return AABB(Vector2d(x.toDouble(), y.toDouble()), Vector2d(z.toDouble(), w.toDouble())) } fun toJsonFromLua(value: Any?): JsonElement { return when (value) { null, is JsonNull -> JsonNull.INSTANCE is String -> JsonPrimitive(value) is ByteString -> JsonPrimitive(value.decode()) is Number -> JsonPrimitive(value) is Boolean -> InternedJsonElementAdapter.of(value) is Table -> value.toJson() else -> throw IllegalArgumentException("Unable to translate $value into json!") } } fun Table.toJson(forceObject: Boolean = false): JsonElement { val arrayValues = Long2ObjectAVLTreeMap() val hashValues = HashMap() val meta = metatable var hint = LUA_HINT_NONE if (meta != null) { val getHint = meta["__typehint"] if (getHint is Number) { hint = getHint.toLong() } val nils = meta["__nils"] if (nils is Table) { // Nil entries just have a garbage integer as their value for ((k, v) in nils) { val ik = k.toLuaInteger() if (ik != null) { arrayValues[ik] = JsonNull.INSTANCE } else { hashValues[k.toString()] = JsonNull.INSTANCE } } } } for ((k, v) in this) { val ik = k.toLuaInteger() if (ik != null) { arrayValues[ik] = toJsonFromLua(v) } else { hashValues[k.toString()] = toJsonFromLua(v) } } val interpretAsList = !forceObject && hashValues.isEmpty() && hint != LUA_HINT_OBJECT if (interpretAsList) { val list = JsonArray() for ((k, v) in arrayValues) { val ik = k.toInt() - 1 while (list.size() <= ik) { list.add(JsonNull.INSTANCE) } list[ik] = v } return list } else { for ((k, v) in arrayValues) { hashValues[(k - 1L).toString()] = v } return JsonObject().apply { for ((k, v) in hashValues) { this[k] = v } } } } fun TableFactory.from(value: JsonElement?): Any? { when (value) { is JsonPrimitive -> { if (value.isNumber) { return value.asDouble } else if (value.isString) { return ByteString.of(value.asString) } else if (value.isBoolean) { return value.asBoolean } else { throw RuntimeException("unreachable code") } } is JsonArray -> return from(value) is JsonObject -> return from(value) null, is JsonNull -> return null else -> throw RuntimeException(value::class.qualifiedName) } } private data class JsonTable(val metatable: Table, val nils: Table, val data: Table) fun Any?.toLuaInteger(): Long? { if (this == null) return null else if (this is Long) return this else if (this is Double) { if (this % 1.0 == 0.0) { return this.toLong() } else { return null } } else if (this is ByteString) { val decoded = decode() if (decoded.contains('.')) return null return decoded.toLongOrNull() } else { return null } } const val LUA_HINT_NONE = 0L const val LUA_HINT_ARRAY = 1L const val LUA_HINT_OBJECT = 2L private object JsonTableIndex : AbstractFunction3() { override fun resume(context: ExecutionContext?, suspendedState: Any?) { throw NonsuspendableFunctionException(this::class.java) } private val __nils: ByteString = ByteString.of("__nils") override fun invoke(context: ExecutionContext, self: Table, key: Any, value: Any?) { val meta = self.metatable val nils = meta[__nils] as Table // If we are setting an entry to nil, need to add a bogus integer entry // to the __nils table, otherwise need to set the entry *in* the __nils // table to nil and remove it. // TODO: __newindex is called only when assigning non-existing keys to values, // TODO: as per Lua manual. // TODO: Chucklefish weren't aware of this? if (value == null) { nils[key] = 0L } else { nils[key] = null as Any? } self[key] = value } } private fun TableFactory.createJsonTable(typeHint: Long, size: Int, hash: Int): JsonTable { val metatable = newTable() val nils = newTable() val data = newTable(size, hash) metatable["__newindex"] = JsonTableIndex metatable["__nils"] = nils metatable["__typehint"] = typeHint data.metatable = metatable return JsonTable(metatable, nils, data) } fun TableFactory.from(value: JsonObject): Table { val (_, nils, data) = createJsonTable(LUA_HINT_OBJECT, 0, value.size()) for ((k, v) in value.entrySet()) { if (v.isJsonNull) { nils[k] = 0L } else { data[k] = from(v) } } return data } fun TableFactory.from(value: JsonArray): Table { val (_, nils, data) = createJsonTable(LUA_HINT_ARRAY, 0, value.size()) for ((i, v) in value.withIndex()) { if (v.isJsonNull) { nils[i + 1L] = 0L } else { data[i + 1L] = from(v) } } return data } fun TableFactory.createJsonObject(): Table { return createJsonTable(LUA_HINT_OBJECT, 0, 0).data } fun TableFactory.createJsonArray(): Table { return createJsonTable(LUA_HINT_ARRAY, 0, 0).data } fun TableFactory.from(value: IStruct2d): Table { return newTable(2, 0).apply { this[1L] = value.component1() this[2L] = value.component2() } } fun TableFactory.from(value: Poly): Table { return newTable(value.vertices.size, 0).apply { value.vertices.withIndex().forEach { (i, v) -> this[i + 1L] = from(v) } } } fun TableFactory.fromCollection(value: Collection): Table { val table = newTable(value.size, 0) for ((k, v) in value.withIndex()) { table.rawset(Conversions.normaliseKey(k + 1), from(v)) } return table } fun TableFactory.from(value: IStruct2i): Table { return newTable(2, 0).also { it.rawset(1L, value.component1()) it.rawset(2L, value.component2()) } } fun TableFactory.from(value: IStruct3i): Table { return newTable(3, 0).also { it.rawset(1L, value.component1()) it.rawset(2L, value.component2()) it.rawset(3L, value.component3()) } } fun TableFactory.from(value: IStruct4i): Table { return newTable(3, 0).also { it.rawset(1L, value.component1()) it.rawset(2L, value.component2()) it.rawset(3L, value.component3()) it.rawset(4L, value.component4()) } } fun TableFactory.from(value: RGBAColor): Table { return newTable(3, 0).also { it.rawset(1L, value.redInt.toLong()) it.rawset(2L, value.greenInt.toLong()) it.rawset(3L, value.blueInt.toLong()) it.rawset(4L, value.alphaInt.toLong()) } } fun TableFactory.from(value: Collection): Table { return newTable(value.size, 0).also { for ((i, v) in value.withIndex()) { it.rawset(i + 1L, v) } } } fun TableFactory.from(value: AABB): Table { return newTable(3, 0).also { it.rawset(1L, value.mins.x) it.rawset(2L, value.mins.y) it.rawset(3L, value.maxs.x) it.rawset(4L, value.maxs.y) } }