package ru.dbotthepony.kstarbound.util.random

import ru.dbotthepony.kstarbound.getValue
import java.util.random.RandomGenerator

// MWC Random (multiply with carry), exact replica from original code
// (for purpose of giving exactly the same results for same seed provided)
@OptIn(ExperimentalUnsignedTypes::class)
class MWCRandom(seed: ULong = System.nanoTime().toULong(), cycle: Int = 256, windupIterations: Int = 32) : RandomGenerator {
	private val data = UIntArray(cycle)
	private var carry = 0u
	private var dataIndex = 0

	var seed: ULong = seed
		private set

	init {
		require(windupIterations >= 0) { "Negative amount of windup iterations: $windupIterations" }
		init(seed, windupIterations)
	}

	/**
	 * re-initialize this MWC generator
	 */
	fun init(seed: ULong, windupIterations: Int = 0) {
		this.seed = seed
		carry = (seed % MAGIC).toUInt()

		data[0] = seed.toUInt()
		data[1] = seed.shr(32).toUInt()

		for (i in 2 until data.size) {
			data[i] = 69069u * data[i - 2] + 362437u
		}

		dataIndex = data.size - 1

		// initial windup
		for (i in 0 until windupIterations) {
			nextInt()
		}
	}

	fun addEntropy(seed: ULong = System.nanoTime().toULong()) {
		// Same algo as init, but bitwise xor with existing data
		carry = ((carry.toULong().xor(seed)) % MAGIC).toUInt()

		data[0] = data[0].xor(seed.toUInt())
		data[1] = data[1].xor(seed.shr(32).xor(seed).toUInt())

		for (i in 2 until data.size) {
			data[i] = data[i].xor(69069u * data[i - 2] + 362437u)
		}
	}

	override fun nextInt(): Int {
		dataIndex = (dataIndex + 1) % data.size
		val t = MAGIC.toULong() * data[dataIndex] + carry
		//val t = MAGIC.toLong() * data[dataIndex].toLong() + carry.toLong()

		carry = t.shr(32).toUInt()
		data[dataIndex] = t.toUInt()

		return t.toInt()
	}

	override fun nextLong(): Long {
		val a = nextInt().toLong() and 0xFFFFFFFFL
		val b = nextInt().toLong() and 0xFFFFFFFFL
		return a.shl(32) or b
	}

	override fun nextFloat(): Float {
		return (nextInt() and 0x7fffffff) / 2.14748365E9f
	}

	override fun nextDouble(): Double {
		return (nextLong() and 0x7fffffffffffffffL) / 9.223372036854776E18
	}

	override fun nextDouble(origin: Double, bound: Double): Double {
		return nextDouble() * (bound - origin) + origin
	}

	override fun nextFloat(origin: Float, bound: Float): Float {
		return nextFloat() * (bound - origin) + origin
	}

	companion object {
		const val MAGIC = 809430660u

		val GLOBAL: MWCRandom by ThreadLocal.withInitial { MWCRandom() }
	}
}
