package ru.dbotthepony.kstarbound.util

import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Delay
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.Runnable
import kotlinx.coroutines.cancel
import ru.dbotthepony.kstarbound.Starbound
import java.util.concurrent.Executor
import java.util.concurrent.Future
import java.util.concurrent.RejectedExecutionException
import java.util.concurrent.TimeUnit
import kotlin.coroutines.CoroutineContext

/**
 * Uses [Starbound] as delay scheduler instead of Kotlin's one
 */
@OptIn(InternalCoroutinesApi::class)
class ScheduledCoroutineExecutor(val executor: Executor) : CoroutineDispatcher(), Delay {
	override fun dispatch(context: CoroutineContext, block: Runnable) {
		try {
			executor.execute(block)
		} catch (err: RejectedExecutionException) {
			context.cancel(CancellationException("The task was rejected", err))
			Dispatchers.IO.dispatch(context, block)
		}
	}

	@OptIn(ExperimentalCoroutinesApi::class)
	override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
		Starbound.schedule(java.lang.Runnable {
			try {
				executor.execute(java.lang.Runnable {
					with(continuation) { resumeUndispatched(Unit) }
				})
			} catch (err: RejectedExecutionException) {
				continuation.context.cancel(CancellationException("The task was rejected", err))
			}
		}, timeMillis, TimeUnit.MILLISECONDS)
	}

	override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
		return Handle(Starbound.schedule(block, timeMillis, TimeUnit.MILLISECONDS))
	}

	private data class Handle(private val future: Future<*>) : DisposableHandle {
		override fun dispose() {
			future.cancel(false)
		}
	}
}
