package ru.dbotthepony.mc.otm.core.util

import it.unimi.dsi.fastutil.HashCommon
import net.minecraft.util.Mth
import net.minecraft.util.RandomSource
import net.minecraft.world.level.levelgen.MarsagliaPolarGaussian
import net.minecraft.world.level.levelgen.PositionalRandomFactory
import net.minecraft.world.level.levelgen.RandomSupport
import ru.dbotthepony.kommons.random.LCG64Random
import ru.dbotthepony.kommons.random.Xoshiro256StarStarRandom

/**
 * Excellent number generator with guaranteed period of 2^255
 */
class Xoshiro256Random : Xoshiro256StarStarRandom, IRandomSourceGenerator {
	private val gaussian = MarsagliaPolarGaussian(this)

	// raw
	private constructor(s0: Long, s1: Long, s2: Long, s3: Long, marker: Nothing?): super(s0, s1, s2, s3, null)
	// normal
	constructor(s0: Long, s1: Long, s2: Long, s3: Long) : super(s0, s1, s2, s3)

	// 64-bit seeded
	constructor(seed: Long) : super(1L, 2L, 3L, 4L, null) {
		setSeed(seed)
	}

	// completely random
	constructor() : super(RandomSupport.generateUniqueSeed(), RandomSupport.generateUniqueSeed(), RandomSupport.generateUniqueSeed(), RandomSupport.generateUniqueSeed(), null)

	override fun setSeed(seed: Long) {
		val rng = LCG64Random(seed)
		s0 = rng.nextLong()
		s1 = rng.nextLong()
		s2 = rng.nextLong()
		s3 = rng.nextLong()
		gaussian.reset()
	}

	override fun nextInt(): Int {
		// sample upper bits
		return nextLong().ushr(32).toInt()
	}

	override fun nextGaussian(): Double {
		return gaussian.nextGaussian()
	}

	override fun fork(): RandomSource {
		return Xoshiro256Random(nextLong(), nextLong(), nextLong(), nextLong(), null)
	}

	override fun forkPositional(): PositionalRandomFactory {
		return Positional(nextLong(), nextLong(), nextLong(), nextLong())
	}

	class Positional(
		private val s0: Long,
		private val s1: Long,
		private val s2: Long,
		private val s3: Long,
	) : PositionalRandomFactory {
		override fun at(x: Int, y: Int, z: Int): RandomSource {
			val rng = LCG64RandomSource(Mth.getSeed(x, y, z))

			return Xoshiro256Random(
				s0.rotateLeft(11).xor(rng.nextLong()),
				s1.rotateLeft(22).xor(rng.nextLong()),
				s2.rotateLeft(33).xor(rng.nextLong()),
				s3.rotateLeft(44).xor(rng.nextLong()),
			)
		}

		override fun fromHashOf(name: String): RandomSource {
			return Xoshiro256Random(s0, HashCommon.murmurHash3(name.hashCode().toLong()).xor(s1), s2, s3)
		}

		override fun fromSeed(seed: Long): RandomSource {
			return Xoshiro256Random(seed)
		}

		override fun parityConfigString(builder: StringBuilder) {
			throw UnsupportedOperationException()
		}
	}

	companion object {
		@JvmStatic
		fun raw(s0: Long, s1: Long, s2: Long, s3: Long): Xoshiro256Random {
			return Xoshiro256Random(s0, s1, s2, s3, null)
		}
	}
}
