package ru.dbotthepony.mc.otm.client

import net.minecraftforge.client.event.ClientPlayerNetworkEvent
import net.minecraftforge.event.TickEvent
import net.minecraftforge.event.TickEvent.RenderTickEvent
import org.lwjgl.glfw.GLFW
import ru.dbotthepony.mc.otm.core.util.IConditionalTickable
import ru.dbotthepony.mc.otm.core.util.ITickable
import ru.dbotthepony.mc.otm.core.util.TickList
import ru.dbotthepony.mc.otm.core.util.WriteOnce
import ru.dbotthepony.mc.otm.isClient
import java.util.function.LongSupplier

private val preTickList = TickList()
private val postTickList = TickList()

var MODIFIED_CURSOR = false
	private set

private var MODIFIED_CURSOR_FRAMES = 3

private var ARROW_CURSOR by WriteOnce<Long>()
private var BEAM_CURSOR by WriteOnce<Long>()
private var HAND_CURSOR by WriteOnce<Long>()
private var NOT_ALLOWED_CURSOR by WriteOnce<Long>()
private var CROSSHAIR_CURSOR by WriteOnce<Long>()

fun createCursors() {
	ARROW_CURSOR = GLFW.glfwCreateStandardCursor(GLFW.GLFW_ARROW_CURSOR)
	BEAM_CURSOR = GLFW.glfwCreateStandardCursor(GLFW.GLFW_IBEAM_CURSOR)
	HAND_CURSOR = GLFW.glfwCreateStandardCursor(GLFW.GLFW_POINTING_HAND_CURSOR)
	NOT_ALLOWED_CURSOR = GLFW.glfwCreateStandardCursor(GLFW.GLFW_NOT_ALLOWED_CURSOR)
	CROSSHAIR_CURSOR = GLFW.glfwCreateStandardCursor(GLFW.GLFW_CROSSHAIR_CURSOR)

	check(ARROW_CURSOR != 0L) { "Failed to create ARROW_CURSOR. Are we not on main game thread?" }
	check(BEAM_CURSOR != 0L) { "Failed to create BEAM_CURSOR. Are we not on main game thread?" }
	check(HAND_CURSOR != 0L) { "Failed to create HAND_CURSOR. Are we not on main game thread?" }
	check(NOT_ALLOWED_CURSOR != 0L) { "Failed to create NOT_ALLOWED_CURSOR. Are we not on main game thread?" }
	check(CROSSHAIR_CURSOR != 0L) { "Failed to create CROSSHAIR_CURSOR. Are we not on main game thread?" }
}

enum class CursorType(val pointer: LongSupplier) {
	ARROW(::ARROW_CURSOR), BEAM(::BEAM_CURSOR), HAND(::HAND_CURSOR), NOT_ALLOWED(::NOT_ALLOWED_CURSOR), CROSSHAIR(::CROSSHAIR_CURSOR);

	fun setTo() {
		GLFW.glfwSetCursor(minecraft.window.window, pointer.asLong)
		MODIFIED_CURSOR = true
		MODIFIED_CURSOR_FRAMES = 2
	}
}

fun onClientPostRender(event: RenderTickEvent) {
	if (event.phase == TickEvent.Phase.END) {
		if (MODIFIED_CURSOR_FRAMES-- <= 0 && MODIFIED_CURSOR) {
			GLFW.glfwSetCursor(minecraft.window.window, ARROW_CURSOR)
			MODIFIED_CURSOR = false
		}
	}
}

var LOGGED_IN = false
	private set

fun onceClient(ticker: ITickable) {
	check(isClient) { "Illegal side" }
	postTickList.once(ticker, LOGGED_IN, "Not logged in")
}

fun onceClientPre(ticker: ITickable) {
	check(isClient) { "Illegal side" }
	preTickList.once(ticker, LOGGED_IN, "Not logged in")
}

fun onceClient(inTicks: Int, ticker: Runnable) {
	check(isClient) { "Illegal side" }
	postTickList.timer(inTicks, ticker, LOGGED_IN, "Not logged in")
}

fun onceClientPre(inTicks: Int, ticker: Runnable) {
	check(isClient) { "Illegal side" }
	preTickList.timer(inTicks, ticker, LOGGED_IN, "Not logged in")
}

fun tickClient(ticker: IConditionalTickable) {
	check(isClient) { "Illegal side" }
	postTickList.add(ticker, LOGGED_IN, "Not logged in")
}

fun tickClientPre(ticker: IConditionalTickable) {
	check(isClient) { "Illegal side" }
	preTickList.add(ticker, LOGGED_IN, "Not logged in")
}

fun tickUntilClient(ticker: () -> Boolean) {
	tickClient(IConditionalTickable.wrap(ticker))
}

fun tickUntilClientPre(ticker: () -> Boolean) {
	tickClientPre(IConditionalTickable.wrap(ticker))
}

fun tickWhileClient(condition: () -> Boolean, ticker: () -> Unit) {
	tickClient(IConditionalTickable.wrap(condition, ticker))
}

fun tickWhileClientPre(condition: () -> Boolean, ticker: () -> Unit) {
	tickClientPre(IConditionalTickable.wrap(condition, ticker))
}

fun onClientTick(event: TickEvent.ClientTickEvent) {
	if (event.phase == TickEvent.Phase.START) {
		preTickList.tick()
	} else {
		postTickList.tick()
	}
}

fun onClientDisconnected(event: ClientPlayerNetworkEvent.LoggingOut) {
	LOGGED_IN = false

	preTickList.clear()
	postTickList.clear()
}

fun onClientConnected(event: ClientPlayerNetworkEvent.LoggingIn) {
	LOGGED_IN = true

	preTickList.clear()
	postTickList.clear()
}
