package ru.dbotthepony.kstarbound.util

import ru.dbotthepony.kstarbound.io.readDouble
import ru.dbotthepony.kstarbound.io.writeDouble
import java.io.DataInputStream
import java.io.DataOutputStream

interface IClock {
	val time: Double
	val nanos: Long get() = (time * 1_000_000_000.0).toLong()
}

// this is stupid, but legacy protocol requires it
// Used for timing in-game events which must be persistent
// And some worlds are not persistent (they get their own clocks,
// such as instance worlds, e.g. Outpost, or Creon Embassy from Elithian Races mod)

// https://www.pcgamingwiki.com/wiki/Category:Persistent describes it as:
// Gameplay continues even when player is not playing the game,
// and the game state is either simulated on a remote server or
// changes over time are calculated when the player returns to the game.
class RelativeClock() : IClock {
	constructor(stream: DataInputStream, isLegacy: Boolean) : this() {
		read(stream, isLegacy)
	}

	private var pointOfReference = 0.0
	private var pointOfReferenceSet = false

	override var time: Double = 0.0
		private set

	fun set(age: Double) {
		pointOfReferenceSet = false
		time = age
	}

	fun update(newPointOfReference: Double) {
		if (pointOfReferenceSet) {
			val diff = newPointOfReference - pointOfReference

			if (diff > 0L)
				time += diff
		}

		pointOfReference = newPointOfReference
	}

	fun write(stream: DataOutputStream, isLegacy: Boolean) {
		stream.writeBoolean(pointOfReferenceSet)

		if (pointOfReferenceSet)
			stream.writeDouble(pointOfReference)

		stream.writeDouble(time)
	}

	fun read(stream: DataInputStream, isLegacy: Boolean) {
		pointOfReferenceSet = stream.readBoolean()

		if (pointOfReferenceSet)
			pointOfReference = stream.readDouble()

		time = stream.readDouble()
	}
}

class JVMClock : IClock {
	var origin = System.nanoTime()
		private set

	var baseline = 0L
		private set

	var isPaused = false
		private set

	fun set(nanos: Long) {
		origin = System.nanoTime()
		baseline = nanos
	}

	fun pause() {
		if (!isPaused) {
			baseline += System.nanoTime() - origin
			isPaused = true
		}
	}

	fun unpause() {
		if (isPaused) {
			origin = System.nanoTime()
			isPaused = false
		}
	}

	override val time: Double
		get() = if (isPaused) (baseline / 1_000_000_000.0) else ((System.nanoTime() - origin) + baseline) / 1_000_000_000.0
}

class GameTimer(val time: Double = 0.0) {
	constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readDouble(isLegacy)) {
		timer = stream.readDouble(isLegacy)
	}

	fun write(stream: DataOutputStream, isLegacy: Boolean) {
		stream.writeDouble(time, isLegacy)
		stream.writeDouble(timer, isLegacy)
	}

	var timer = time
		private set

	fun reset() {
		timer = time
	}

	var hasFinished: Boolean
		get() = timer <= 0.0
		set(value) {
			if (value)
				timer = 0.0
			else
				timer = time
		}

	val percent: Double
		get() = if (time != 0.0) timer / time else 0.0

	fun invert() {
		timer = time - timer
	}

	fun tick(delta: Double): Boolean {
		timer = (timer - delta).coerceAtLeast(0.0)
		return timer == 0.0
	}

	fun wrapTick(delta: Double): Boolean {
		val result = tick(delta)
		if (result) reset()
		return result
	}
}
