package de.arxsilex.maptools.converter.core.pbf

import java.io.ByteArrayOutputStream

internal class PbfProtoWriter {
    private val output = ByteArrayOutputStream()

    fun bytes(field: Int, value: ByteArray) {
        tag(field, 2)
        varint(value.size.toLong())
        output.write(value)
    }

    fun string(field: Int, value: String) = bytes(field, value.toByteArray(Charsets.UTF_8))

    fun int32(field: Int, value: Int) {
        tag(field, 0)
        varint(value.toLong())
    }

    fun int64(field: Int, value: Long) {
        tag(field, 0)
        varint(value)
    }

    fun sint64(field: Int, value: Long) {
        tag(field, 0)
        varint(zigZag(value))
    }

    fun packedUInt32(field: Int, values: Iterable<Int>) = packed(field) { packed ->
        values.forEach { packed.varint(it.toLong()) }
    }

    fun packedInt32(field: Int, values: Iterable<Int>) = packedUInt32(field, values)

    fun packedSInt64(field: Int, values: Iterable<Long>) = packed(field) { packed ->
        values.forEach { packed.varint(zigZag(it)) }
    }

    fun toByteArray(): ByteArray = output.toByteArray()

    private fun packed(field: Int, writeValues: (PbfProtoWriter) -> Unit) {
        val packed = PbfProtoWriter().also(writeValues).toByteArray()
        if (packed.isNotEmpty()) bytes(field, packed)
    }

    private fun tag(field: Int, wireType: Int) {
        require(field > 0)
        varint((field shl 3 or wireType).toLong())
    }

    private fun varint(value: Long) {
        var remaining = value
        while (remaining and -128L != 0L) {
            output.write(((remaining and 127L) or 128L).toInt())
            remaining = remaining ushr 7
        }
        output.write(remaining.toInt())
    }

    private fun zigZag(value: Long): Long = (value shl 1) xor (value shr 63)
}
