package ru.dbotthepony.kstarbound.io import ru.dbotthepony.kommons.io.readBigDecimal import ru.dbotthepony.kommons.io.readBinaryString import ru.dbotthepony.kommons.io.readByteArray import ru.dbotthepony.kommons.io.readKOptional import ru.dbotthepony.kommons.io.readMap import ru.dbotthepony.kommons.io.readOptional import ru.dbotthepony.kommons.io.readSignedVarInt import ru.dbotthepony.kommons.io.readSignedVarLong import ru.dbotthepony.kommons.io.readVarInt import ru.dbotthepony.kommons.io.readVarLong import ru.dbotthepony.kommons.io.writeBigDecimal import ru.dbotthepony.kommons.io.writeBinaryString import ru.dbotthepony.kommons.io.writeByteArray import ru.dbotthepony.kommons.io.writeKOptional import ru.dbotthepony.kommons.io.writeMap import ru.dbotthepony.kommons.io.writeOptional import ru.dbotthepony.kommons.io.writeSignedVarInt import ru.dbotthepony.kommons.io.writeSignedVarLong import ru.dbotthepony.kommons.io.writeVarInt import ru.dbotthepony.kommons.io.writeVarLong import ru.dbotthepony.kommons.math.RGBAColor import ru.dbotthepony.kommons.util.KOptional import java.io.DataInputStream import java.io.DataOutputStream import java.util.* import kotlin.reflect.KClass /** * Represents value which can be encoded onto or decoded from stream. */ interface StreamCodec { fun read(stream: DataInputStream): V fun write(stream: DataOutputStream, value: V) /** * Defensive copy */ fun copy(value: V): V /** * Optional custom equality check */ fun compare(a: V, b: V): Boolean { return a == b } class Impl( private val reader: (stream: DataInputStream) -> V, private val writer: (stream: DataOutputStream, value: V) -> Unit, private val copier: ((value: V) -> V) = { it }, private val comparator: ((a: V, b: V) -> Boolean) = { a, b -> a == b } ) : StreamCodec { constructor( reader: (stream: DataInputStream) -> V, payloadSize: Long, writer: (stream: DataOutputStream, value: V) -> Unit, copier: ((value: V) -> V) = { it }, comparator: ((a: V, b: V) -> Boolean) = { a, b -> a == b } ) : this({ stream -> reader.invoke(stream) }, writer, copier, comparator) override fun read(stream: DataInputStream): V { return reader.invoke(stream) } override fun write(stream: DataOutputStream, value: V) { writer.invoke(stream, value) } override fun copy(value: V): V { return copier.invoke(value) } override fun compare(a: V, b: V): Boolean { return comparator.invoke(a, b) } } class Nullable(val parent: StreamCodec) : StreamCodec { override fun read(stream: DataInputStream): V? { return if (stream.read() == 0) null else parent.read(stream) } override fun write(stream: DataOutputStream, value: V?) { if (value === null) stream.write(0) else { stream.write(1) parent.write(stream, value) } } override fun copy(value: V?): V? { return if (value === null) null else parent.copy(value) } override fun compare(a: V?, b: V?): Boolean { if (a === null && b === null) return true if (a === null || b === null) return false return parent.compare(a, b) } } class Collection>(val elementCodec: StreamCodec, val collectionFactory: (Int) -> C) : StreamCodec { override fun read(stream: DataInputStream): C { val size = stream.readVarInt() if (size <= 0) { return collectionFactory.invoke(0) } val collection = collectionFactory.invoke(size) for (i in 0 until size) { collection.add(elementCodec.read(stream)) } return collection } override fun write(stream: DataOutputStream, value: C) { stream.writeVarInt(value.size) value.forEach { elementCodec.write(stream, it) } } override fun copy(value: C): C { val new = collectionFactory.invoke(value.size) value.forEach { new.add(elementCodec.copy(it)) } return new } } class Map>(val keyCodec: StreamCodec, val valueCodec: StreamCodec, val factory: (Int) -> C): StreamCodec { override fun read(stream: DataInputStream): C { return stream.readMap(keyCodec::read, valueCodec::read, factory) } override fun write(stream: DataOutputStream, value: C) { stream.writeMap(value, keyCodec::write, valueCodec::write) } override fun copy(value: C): C { val new = factory.invoke(value.size) for ((k, v) in value.entries) { new[keyCodec.copy(k)] = valueCodec.copy(v) } return new } } class Enum>(clazz: Class) : StreamCodec { val clazz = searchClass(clazz) val values: List = listOf(*this.clazz.enumConstants!!) val valuesMap = values.associateBy { it.name } override fun read(stream: DataInputStream): V { val id = stream.readVarInt() return values.getOrNull(id) ?: throw NoSuchElementException("No such enum with index $id") } override fun write(stream: DataOutputStream, value: V) { stream.writeVarInt(value.ordinal) } override fun copy(value: V): V { return value } override fun compare(a: V, b: V): Boolean { return a === b } companion object { /** * FIXME: enums with abstract methods which get compiled to subclasses, whose DO NOT expose "parent's" enum constants array * * is there an already existing solution? */ fun > searchClass(clazz: Class): Class { var search: Class<*> = clazz while (search.enumConstants == null && search.superclass != null) { search = search.superclass } if (search.enumConstants == null) { throw ClassCastException("$clazz does not represent an enum or enum subclass") } return search as Class } } } class KOptional(val codec: StreamCodec) : StreamCodec> { override fun read(stream: DataInputStream): ru.dbotthepony.kommons.util.KOptional { return stream.readKOptional(codec::read) } override fun write(stream: DataOutputStream, value: ru.dbotthepony.kommons.util.KOptional) { stream.writeKOptional(value, codec::write) } override fun copy(value: ru.dbotthepony.kommons.util.KOptional): ru.dbotthepony.kommons.util.KOptional { return value.map(codec::copy) } } class Optional(val codec: StreamCodec) : StreamCodec> { override fun read(stream: DataInputStream): java.util.Optional { return stream.readOptional(codec::read) } override fun write(stream: DataOutputStream, value: java.util.Optional) { stream.writeOptional(value, codec::write) } override fun copy(value: java.util.Optional): java.util.Optional { return value.map(codec::copy) } } class Mapping(private val codec: StreamCodec, private val from: T.() -> R, private val to: R.() -> T, private val copy: R.() -> R = { this }) : StreamCodec { override fun read(stream: DataInputStream): R { return from(codec.read(stream)) } override fun write(stream: DataOutputStream, value: R) { codec.write(stream, to(value)) } override fun copy(value: R): R { return copy.invoke(value) } } class Pair(private val left: StreamCodec, private val right: StreamCodec) : StreamCodec> { override fun read(stream: DataInputStream): kotlin.Pair { val left = left.read(stream) val right = right.read(stream) return left to right } override fun write(stream: DataOutputStream, value: kotlin.Pair) { left.write(stream, value.first) right.write(stream, value.second) } override fun copy(value: kotlin.Pair): kotlin.Pair { val left = left.copy(value.first) val right = right.copy(value.second) if (left !== this.left && right !== this.right) { return left to right } else { return value } } } } fun StreamCodec.map(from: T.() -> R, to: R.() -> T): StreamCodec { return StreamCodec.Mapping(this, from, to) } fun StreamCodec.map(from: T.() -> R, to: R.() -> T, copy: R.() -> R): StreamCodec { return StreamCodec.Mapping(this, from, to, copy) } fun StreamCodec.optional(): StreamCodec> = StreamCodec.Optional(this) fun StreamCodec.koptional(): StreamCodec> = StreamCodec.KOptional(this) fun StreamCodec.nullable(): StreamCodec = StreamCodec.Nullable(this) val NullValueCodec = StreamCodec.Impl({ _ -> null }, { _, _ -> }) val BooleanValueCodec = StreamCodec.Impl(DataInputStream::readBoolean, 1L, DataOutputStream::writeBoolean) val ByteValueCodec = StreamCodec.Impl(DataInputStream::readByte, 1L, { s, v -> s.writeByte(v.toInt()) }) val ShortValueCodec = StreamCodec.Impl(DataInputStream::readShort, 2L, { s, v -> s.writeShort(v.toInt()) }) val CharValueCodec = StreamCodec.Impl(DataInputStream::readChar, 2L, { s, v -> s.writeShort(v.code) }) val IntValueCodec = StreamCodec.Impl(DataInputStream::readInt, 4L, DataOutputStream::writeInt) val LongValueCodec = StreamCodec.Impl(DataInputStream::readLong, 8L, DataOutputStream::writeLong) val FloatValueCodec = StreamCodec.Impl(DataInputStream::readFloat, 4L, DataOutputStream::writeFloat) val DoubleValueCodec = StreamCodec.Impl(DataInputStream::readDouble, 8L, DataOutputStream::writeDouble) val BigDecimalValueCodec = StreamCodec.Impl(DataInputStream::readBigDecimal, DataOutputStream::writeBigDecimal) val UUIDValueCodec = StreamCodec.Impl({ s -> UUID(s.readLong(), s.readLong()) }, { s, v -> s.writeLong(v.mostSignificantBits); s.writeLong(v.leastSignificantBits) }) val VarIntValueCodec = StreamCodec.Impl(DataInputStream::readSignedVarInt, DataOutputStream::writeSignedVarInt) val VarLongValueCodec = StreamCodec.Impl(DataInputStream::readSignedVarLong, DataOutputStream::writeSignedVarLong) val BinaryStringCodec = StreamCodec.Impl(DataInputStream::readBinaryString, DataOutputStream::writeBinaryString) val UnsignedVarLongCodec = StreamCodec.Impl(DataInputStream::readVarLong, DataOutputStream::writeVarLong) val UnsignedVarIntCodec = StreamCodec.Impl(DataInputStream::readVarInt, DataOutputStream::writeVarInt) val ByteArrayCodec = StreamCodec.Impl(DataInputStream::readByteArray, DataOutputStream::writeByteArray) val RGBCodec = StreamCodec.Impl( { s -> RGBAColor(s.readFloat(), s.readFloat(), s.readFloat()) }, { s, v -> s.writeFloat(v.red); s.writeFloat(v.green); s.writeFloat(v.blue) }) val RGBACodec = StreamCodec.Impl( { s -> RGBAColor(s.readFloat(), s.readFloat(), s.readFloat(), s.readFloat()) }, { s, v -> s.writeFloat(v.red); s.writeFloat(v.green); s.writeFloat(v.blue); s.writeFloat(v.alpha) }) val OptionalBooleanValueCodec = StreamCodec.Optional(BooleanValueCodec) val OptionalByteValueCodec = StreamCodec.Optional(ByteValueCodec) val OptionalShortValueCodec = StreamCodec.Optional(ShortValueCodec) val OptionalCharValueCodec = StreamCodec.Optional(CharValueCodec) val OptionalIntValueCodec = StreamCodec.Optional(IntValueCodec) val OptionalLongValueCodec = StreamCodec.Optional(LongValueCodec) val OptionalFloatValueCodec = StreamCodec.Optional(FloatValueCodec) val OptionalDoubleValueCodec = StreamCodec.Optional(DoubleValueCodec) val OptionalBigDecimalValueCodec = StreamCodec.Optional(BigDecimalValueCodec) val OptionalUUIDValueCodec = StreamCodec.Optional(UUIDValueCodec) val OptionalVarIntValueCodec = StreamCodec.Optional(VarIntValueCodec) val OptionalVarLongValueCodec = StreamCodec.Optional(VarLongValueCodec) val OptionalBinaryStringCodec = StreamCodec.Optional(BinaryStringCodec) val OptionalRGBCodec = StreamCodec.Optional(RGBCodec) val OptionalRGBACodec = StreamCodec.Optional(RGBACodec) val KOptionalBooleanValueCodec = StreamCodec.KOptional(BooleanValueCodec) val KOptionalByteValueCodec = StreamCodec.KOptional(ByteValueCodec) val KOptionalShortValueCodec = StreamCodec.KOptional(ShortValueCodec) val KOptionalCharValueCodec = StreamCodec.KOptional(CharValueCodec) val KOptionalIntValueCodec = StreamCodec.KOptional(IntValueCodec) val KOptionalLongValueCodec = StreamCodec.KOptional(LongValueCodec) val KOptionalFloatValueCodec = StreamCodec.KOptional(FloatValueCodec) val KOptionalDoubleValueCodec = StreamCodec.KOptional(DoubleValueCodec) val KOptionalBigDecimalValueCodec = StreamCodec.KOptional(BigDecimalValueCodec) val KOptionalUUIDValueCodec = StreamCodec.KOptional(UUIDValueCodec) val KOptionalVarIntValueCodec = StreamCodec.KOptional(VarIntValueCodec) val KOptionalVarLongValueCodec = StreamCodec.KOptional(VarLongValueCodec) val KOptionalBinaryStringCodec = StreamCodec.KOptional(BinaryStringCodec) val KOptionalRGBCodec = StreamCodec.KOptional(RGBCodec) val KOptionalRGBACodec = StreamCodec.KOptional(RGBACodec) fun > Class.codec() = StreamCodec.Enum(this) fun > KClass.codec() = StreamCodec.Enum(this.java)