/*
 * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

@file:JvmMultifileClass
@file:JvmName("UuidKt")

package kotlin.uuid

import java.io.*
import java.nio.*
import java.security.SecureRandom

private object SecureRandomHolder {
    val instance = SecureRandom()
}

@ExperimentalUuidApi
internal actual fun secureRandomUuid(): Uuid {
    val randomBytes = ByteArray(Uuid.SIZE_BYTES)
    SecureRandomHolder.instance.nextBytes(randomBytes)
    return uuidFromRandomBytes(randomBytes)
}

@ExperimentalUuidApi
private class UuidSerialized(
    var mostSignificantBits: Long,
    var leastSignificantBits: Long
) : Externalizable {

    constructor() : this(0L, 0L) // for deserialization

    override fun writeExternal(output: ObjectOutput) {
        output.writeLong(mostSignificantBits)
        output.writeLong(leastSignificantBits)
    }

    override fun readExternal(input: ObjectInput) {
        mostSignificantBits = input.readLong()
        leastSignificantBits = input.readLong()
    }

    private fun readResolve(): Any =
        Uuid.fromLongs(mostSignificantBits, leastSignificantBits)

    companion object {
        private const val serialVersionUID: Long = 0L
    }
}

@ExperimentalUuidApi
internal actual fun serializedUuid(uuid: Uuid): Any =
    UuidSerialized(uuid.mostSignificantBits, uuid.leastSignificantBits)

@ExperimentalUuidApi
internal actual fun ByteArray.getLongAt(index: Int): Long =
    getLongAtCommonImpl(index)

@ExperimentalUuidApi
internal actual fun Long.formatBytesInto(dst: ByteArray, dstOffset: Int, startIndex: Int, endIndex: Int) =
    formatBytesIntoCommonImpl(dst, dstOffset, startIndex, endIndex)

@ExperimentalUuidApi
internal actual fun ByteArray.setLongAt(index: Int, value: Long) =
    setLongAtCommonImpl(index, value)

@ExperimentalUuidApi
internal actual fun uuidParseHexDash(hexDashString: String): Uuid =
    uuidParseHexDashCommonImpl(hexDashString)

@ExperimentalUuidApi
internal actual fun uuidParseHex(hexString: String): Uuid =
    uuidParseHexCommonImpl(hexString)

/**
 * Converts this [java.util.UUID] value to the corresponding [kotlin.uuid.Uuid] value.
 *
 * This function is convenient when one has a Java uuid and needs to interact with an API
 * that accepts a Kotlin uuid. It can also be used to format or retrieve information that
 * the Java uuid does not provide, e.g., `javaUuid.toKotlinUuid().toByteArray()`.
 *
 * @sample samples.uuid.Uuids.toKotlinUuid
 */
@SinceKotlin("2.0")
@ExperimentalUuidApi
@Suppress("NOTHING_TO_INLINE")
public inline fun java.util.UUID.toKotlinUuid(): Uuid =
    Uuid.fromLongs(mostSignificantBits, leastSignificantBits)

/**
 * Converts this [kotlin.uuid.Uuid] value to the corresponding [java.util.UUID] value.
 *
 * This function is convenient when one has a Kotlin uuid and needs to interact with an
 * API that accepts a Java uuid.
 *
 * @sample samples.uuid.Uuids.toJavaUuid
 */
@SinceKotlin("2.0")
@ExperimentalUuidApi
@Suppress("NOTHING_TO_INLINE")
public inline fun Uuid.toJavaUuid(): java.util.UUID = toLongs { mostSignificantBits, leastSignificantBits ->
    java.util.UUID(mostSignificantBits, leastSignificantBits)
}

/**
 * Reads a [Uuid] value at this buffer's current position.
 *
 * This function reads the next 16 bytes at this buffer's current [position][Buffer.position]
 * and assembles a uuid from them. As a result, the buffer's position is incremented by 16.
 *
 * Note that this function ignores the buffer's [byte order][ByteBuffer.order].
 * The 16 bytes are read sequentially, with each byte representing the next 8 bits of the uuid,
 * starting from the first byte representing the most significant 8 bits to the last byte
 * representing the least significant 8 bits.
 *
 * The returned uuid is equivalent to:
 * ```kotlin
 * val bytes = ByteArray(16)
 * byteBuffer.get(bytes)
 * return Uuid.fromByteArray(bytes)
 * ```
 *
 * @return The uuid value read at this buffer's current position.
 * @throws BufferUnderflowException If there are fewer than 16 bytes remaining in this buffer.
 *
 * @see Uuid.toByteArray
 * @sample samples.uuid.Uuids.byteBufferGet
 */
@SinceKotlin("2.0")
@ExperimentalUuidApi
public fun ByteBuffer.getUuid(): Uuid {
    if (position() + 15 >= limit()) {
        throw BufferUnderflowException() // otherwise a partial read could occur
    }
    var msb = getLong()
    var lsb = getLong()
    if (order() == ByteOrder.LITTLE_ENDIAN) {
        msb = msb.reverseBytes()
        lsb = lsb.reverseBytes()
    }
    return Uuid.fromLongs(msb, lsb)
}

/**
 * Reads a [Uuid] value at the specified [index].
 *
 * This function reads the next 16 bytes from this buffer at the specified [index]
 * and assembles a uuid from them. The buffer's [position][ByteBuffer.position], however, is not updated.
 *
 * Note that this function ignores the buffer's [byte order][ByteBuffer.order].
 * The 16 bytes are read sequentially, with each byte representing the next 8 bits of the uuid,
 * starting from the first byte representing the most significant 8 bits to the last byte
 * representing the least significant 8 bits.
 *
 * The returned uuid is equivalent to:
 * ```kotlin
 * val bytes = ByteArray(16) { i ->
 *     byteBuffer.get(index + i)
 * }
 * return Uuid.fromByteArray(bytes)
 * ```
 * except that this function first checks that there are sufficient bytes in the buffer.
 *
 * @param index The index to read a uuid at.
 * @return The uuid value read at the specified [index].
 * @throws IndexOutOfBoundsException If [index] is negative or `index + 15` is not smaller than
 *   this buffer's [limit][Buffer.limit].
 *
 * @see Uuid.fromByteArray
 * @sample samples.uuid.Uuids.byteBufferGetByIndex
 */
@SinceKotlin("2.0")
@ExperimentalUuidApi
public fun ByteBuffer.getUuid(index: Int): Uuid {
    if (index < 0) {
        throw IndexOutOfBoundsException("Negative index: $index")
    } else if (index + 15 >= limit()) {
        throw IndexOutOfBoundsException("Not enough bytes to read a uuid at index: $index, with limit: ${limit()} ")
    }
    var msb = getLong(index)
    var lsb = getLong(index + 8)
    if (order() == ByteOrder.LITTLE_ENDIAN) {
        msb = msb.reverseBytes()
        lsb = lsb.reverseBytes()
    }
    return Uuid.fromLongs(msb, lsb)
}

/**
 * Writes the specified [uuid] value at this buffer's current position.
 *
 * This function writes 16 bytes containing the given uuid value into this buffer at the current
 * [position][Buffer.position]. As a result, the buffer's position is incremented by 16.
 *
 * Note that this function ignores the buffer's [byte order][ByteBuffer.order].
 * The 16 bytes are written sequentially, with each byte representing the next 8 bits of the uuid,
 * starting from the first byte representing the most significant 8 bits to the last byte
 * representing the least significant 8 bits.
 *
 * This function is equivalent to:
 * ```kotlin
 * byteBuffer.put(uuid.toByteArray())
 * ```
 *
 * @param uuid The uuid value to write.
 * @return This byte buffer.
 * @throws BufferOverflowException If there is insufficient space in this buffer for 16 bytes.
 * @throws ReadOnlyBufferException If this buffer is read-only.
 *
 * @see Uuid.toByteArray
 * @sample samples.uuid.Uuids.byteBufferPut
 */
@SinceKotlin("2.0")
@ExperimentalUuidApi
public fun ByteBuffer.putUuid(uuid: Uuid): ByteBuffer = uuid.toLongs { msb, lsb ->
    if (position() + 15 >= limit()) {
        throw BufferOverflowException() // otherwise a partial write could occur
    }
    if (order() == ByteOrder.BIG_ENDIAN) {
        putLong(msb)
        putLong(lsb)
    } else {
        putLong(msb.reverseBytes())
        putLong(lsb.reverseBytes())
    }
}

/**
 * Writes the specified [uuid] value at the specified [index].
 *
 * This function writes 16 bytes containing the given uuid value into this buffer at the
 * specified [index]. The buffer's [position][ByteBuffer.position], however, is not updated.
 *
 * Note that this function ignores the buffer's [byte order][ByteBuffer.order].
 * The 16 bytes are written sequentially, with each byte representing the next 8 bits of the uuid,
 * starting from the first byte representing the most significant 8 bits to the last byte
 * representing the least significant 8 bits.
 *
 * This function is equivalent to:
 * ```kotlin
 * val bytes = uuid.toByteArray()
 * bytes.forEachIndexed { i, byte ->
 *     byteBuffer.put(index + i, byte)
 * }
 * ```
 * except that this function first checks that there is sufficient space in the buffer.
 *
 * @param index The index to write the specified uuid value at.
 * @param uuid The uuid value to write.
 * @return This byte buffer.
 * @throws IndexOutOfBoundsException If [index] is negative or `index + 15` is not smaller than
 *   this buffer's [limit][Buffer.limit].
 * @throws ReadOnlyBufferException If this buffer is read-only.
 *
 * @see Uuid.toByteArray
 * @sample samples.uuid.Uuids.byteBufferPutAtIndex
 */
@SinceKotlin("2.0")
@ExperimentalUuidApi
public fun ByteBuffer.putUuid(index: Int, uuid: Uuid): ByteBuffer = uuid.toLongs { msb, lsb ->
    if (index < 0) {
        throw IndexOutOfBoundsException("Negative index: $index")
    } else if (index + 15 >= limit()) {
        throw IndexOutOfBoundsException("Not enough capacity to write a uuid at index: $index, with limit: ${limit()} ")
    }
    if (order() == ByteOrder.BIG_ENDIAN) {
        putLong(index, msb)
        putLong(index + 8, lsb)
    } else {
        putLong(index, msb.reverseBytes())
        putLong(index + 8, lsb.reverseBytes())
    }
}

@Suppress("NOTHING_TO_INLINE")
internal inline fun Long.reverseBytes(): Long = java.lang.Long.reverseBytes(this)