/*
 * Copyright 2010-2018 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.
 */

package kotlin.random

import kotlin.internal.IMPLEMENTATIONS
import kotlin.internal.InlineOnly

/**
 * Creates a [java.util.Random][java.util.Random] instance that uses the specified Kotlin [Random] generator as a randomness source.
 */
@SinceKotlin("1.3")
public fun Random.asJavaRandom(): java.util.Random =
    (this as? AbstractPlatformRandom)?.impl ?: KotlinRandom(this)

/**
 * Creates a Kotlin [Random] instance that uses the specified [java.util.Random][java.util.Random] generator as a randomness source.
 */
@SinceKotlin("1.3")
public fun java.util.Random.asKotlinRandom(): Random =
    (this as? KotlinRandom)?.impl ?: PlatformRandom(this)



@InlineOnly
internal actual inline fun defaultPlatformRandom(): Random =
    IMPLEMENTATIONS.defaultPlatformRandom()

internal actual fun doubleFromParts(hi26: Int, low27: Int): Double =
    (hi26.toLong().shl(27) + low27) / (1L shl 53).toDouble()


internal abstract class AbstractPlatformRandom : Random() {
    abstract val impl: java.util.Random

    override fun nextBits(bitCount: Int): Int =
        impl.nextInt().takeUpperBits(bitCount)

    override fun nextInt(): Int = impl.nextInt()
    override fun nextInt(until: Int): Int = impl.nextInt(until)
    override fun nextLong(): Long = impl.nextLong()
    override fun nextBoolean(): Boolean = impl.nextBoolean()
    override fun nextDouble(): Double = impl.nextDouble()
    override fun nextFloat(): Float = impl.nextFloat()
    override fun nextBytes(array: ByteArray): ByteArray = array.also { impl.nextBytes(it) }
}

internal class FallbackThreadLocalRandom : AbstractPlatformRandom() {
    private val implStorage = object : ThreadLocal<java.util.Random>() {
        override fun initialValue(): java.util.Random = java.util.Random()
    }

    override val impl: java.util.Random
        get() = implStorage.get()
}

private class PlatformRandom(override val impl: java.util.Random) : AbstractPlatformRandom(), Serializable {
    private companion object {
        private const val serialVersionUID: Long = 0L
    }
}

private class KotlinRandom(val impl: Random) : java.util.Random() {
    override fun next(bits: Int): Int = impl.nextBits(bits)
    override fun nextInt(): Int = impl.nextInt()
    override fun nextInt(bound: Int): Int = impl.nextInt(bound)
    override fun nextBoolean(): Boolean = impl.nextBoolean()
    override fun nextLong(): Long = impl.nextLong()
    override fun nextFloat(): Float = impl.nextFloat()
    override fun nextDouble(): Double = impl.nextDouble()

    override fun nextBytes(bytes: ByteArray) {
        impl.nextBytes(bytes)
    }

    private var seedInitialized: Boolean = false

    override fun setSeed(seed: Long) {
        if (!seedInitialized) {
            // ignore seed value from constructor
            seedInitialized = true
        } else {
            throw UnsupportedOperationException("Setting seed is not supported.")
        }
    }

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