package ru.dbotthepony.kstarbound.lua.bindings

import org.apache.logging.log4j.LogManager
import org.classdump.luna.ByteString
import org.classdump.luna.Table
import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.get
import ru.dbotthepony.kstarbound.lua.luaFunction
import ru.dbotthepony.kstarbound.lua.luaFunctionArray
import ru.dbotthepony.kstarbound.lua.luaFunctionN
import ru.dbotthepony.kstarbound.lua.nextOptionalFloat
import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.toJson
import ru.dbotthepony.kstarbound.lua.toVector2d
import ru.dbotthepony.kstarbound.lua.userdata.LuaPerlinNoise
import ru.dbotthepony.kstarbound.lua.userdata.LuaRandomGenerator
import ru.dbotthepony.kstarbound.math.Interpolator
import ru.dbotthepony.kstarbound.util.SBPattern
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
import ru.dbotthepony.kstarbound.util.random.nextNormalDouble
import ru.dbotthepony.kstarbound.util.random.random
import ru.dbotthepony.kstarbound.util.random.staticRandom32
import ru.dbotthepony.kstarbound.util.random.staticRandomDouble
import ru.dbotthepony.kstarbound.util.random.staticRandomLong
import ru.dbotthepony.kstarbound.util.toStarboundString
import java.util.*
import java.util.random.RandomGenerator

private val LOGGER = LogManager.getLogger()

private val logInfo = luaFunctionN("logInfo") { args ->
	LOGGER.info(args.nextString().toString().format(*args.copyRemaining()))
}

private val logWarn = luaFunctionN("logWarn") { args ->
	LOGGER.warn(args.nextString().toString().format(*args.copyRemaining()))
}

private val logError = luaFunctionN("logError") { args ->
	LOGGER.error(args.nextString().toString().format(*args.copyRemaining()))
}

private val interpolateSinEase = luaFunctionArray { args ->
	if (args.size < 3)
		throw IllegalArgumentException("Invalid amount of arguments to interpolateSinEase: ${args.size}")

	val offset = args[0] as? Number ?: throw IllegalArgumentException("Invalid 'offset' argument: ${args[0]}")

	if (args[1] is Number && args[2] is Number) {
		returnBuffer.setTo(Interpolator.Sin.interpolate(offset.toDouble(), (args[1] as Number).toDouble(), (args[2] as Number).toDouble()))
	} else {
		// assume vectors
		val a = toVector2d(args[1]!!)
		val b = toVector2d(args[2]!!)

		val result = Vector2d(
			Interpolator.Sin.interpolate(offset.toDouble(), a.x, b.x),
			Interpolator.Sin.interpolate(offset.toDouble(), a.y, b.y),
		)

		returnBuffer.setTo(from(result))
	}
}

private val replaceTags = luaFunction { string: ByteString, tags: Table ->
	returnBuffer.setTo(SBPattern.of(string.toString()).resolveOrSkip({ tags[it]?.toString() }))
}

private val makePerlinSource = luaFunction { settings: Table ->
	returnBuffer.setTo(LuaPerlinNoise(AbstractPerlinNoise.of(Starbound.gson.fromJson(settings.toJson(), PerlinNoiseParameters::class.java))))
}

private val staticRandomI32 = luaFunctionArray {
	returnBuffer.setTo(staticRandom32(*it))
}

private val staticRandomDouble = luaFunctionArray {
	returnBuffer.setTo(staticRandomDouble(*it))
}

private val staticRandomDoubleRange = luaFunctionN("staticRandomDoubleRange") {
	val min = it.nextFloat()
	val max = it.nextFloat()
	returnBuffer.setTo(staticRandomDouble(*it.copyRemaining()) * (max - min) + min)
}

private val staticRandomI32Range = luaFunctionN("staticRandomI32Range") {
	val min = it.nextInteger()
	val max = it.nextInteger()
	returnBuffer.setTo(staticRandomLong(min, max, *it.copyRemaining()))
}

fun provideUtilityBindings(
	lua: LuaEnvironment,
	random: RandomGenerator = random()
) {
	val table = lua.newTable()
	lua.globals["sb"] = table

	table["makeUuid"] = luaFunction {
		returnBuffer.setTo(UUID(random.nextLong(), random.nextLong()).toStarboundString())
	}

	table["logInfo"] = logInfo
	table["logWarn"] = logWarn
	table["logError"] = logError

	table["nrand"] = luaFunctionN("nrand") { args ->
		val stdev = args.nextOptionalFloat() ?: 1.0
		val mean = args.nextOptionalFloat() ?: 0.0
		random.nextNormalDouble(stdev, mean)
	}

	table["print"] = lua.globals["tostring"]
	table["printJson"] = lua.globals["tostring"]
	table["interpolateSinEase"] = interpolateSinEase
	table["replaceTags"] = replaceTags
	table["makeRandomSource"] = luaFunction { seed: Long? ->
		returnBuffer.setTo(LuaRandomGenerator(random(seed ?: random.nextLong())))
	}

	table["makePerlinSource"] = makePerlinSource

	table["staticRandomI32"] = staticRandomI32
	table["staticRandomI64"] = staticRandomI32
	table["staticRandomDouble"] = staticRandomDouble

	table["staticRandomDoubleRange"] = staticRandomDoubleRange
	table["staticRandomI32Range"] = staticRandomI32Range
	table["staticRandomI64Range"] = staticRandomI32Range
}
