package ru.dbotthepony.kstarbound.lua.userdata

import org.classdump.luna.Table
import org.classdump.luna.Userdata
import org.classdump.luna.impl.ImmutableTable
import ru.dbotthepony.kstarbound.lua.luaFunction
import java.util.concurrent.CancellationException
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionException

/**
 * [isLocal] tells us whenever result() should block current thread if [future] is not yet fulfilled
 * (to retain original behavior for "locally async" calls)
 *
 * god damn it.
 */
class LuaFuture(val future: CompletableFuture<out Any?>, val isLocal: Boolean) : Userdata<CompletableFuture<out Any?>>() {
	override fun getMetatable(): Table {
		return metadata
	}

	override fun setMetatable(mt: Table?): Table {
		throw UnsupportedOperationException()
	}

	override fun getUserValue(): CompletableFuture<out Any?> {
		return future
	}

	override fun setUserValue(value: CompletableFuture<out Any?>?): CompletableFuture<out Any?> {
		throw UnsupportedOperationException()
	}

	companion object {
		private val metadata = ImmutableTable.Builder()
			.add("finished", luaFunction { self: LuaFuture ->
				returnBuffer.setTo(self.future.isDone)
			})
			.add("succeeded", luaFunction { self: LuaFuture ->
				returnBuffer.setTo(!self.future.isCompletedExceptionally)
			})
			.add("failed", luaFunction { self: LuaFuture ->
				returnBuffer.setTo(self.future.isCompletedExceptionally)
			})
			.add("result", luaFunction { self: LuaFuture ->
				try {
					if (self.future.isCompletedExceptionally) {
						returnBuffer.setTo()
					} else if (self.isLocal) {
						returnBuffer.setTo(self.future.join())
					} else {
						returnBuffer.setTo(self.future.getNow(null))
					}
				} catch (err: CompletionException) {
					returnBuffer.setTo()
				} catch (err: CancellationException) {
					returnBuffer.setTo()
				}
			})
			.add("error", luaFunction { self: LuaFuture ->
				// this is slow, but we can't get Exception out of CompletableFuture without latter throwing former
				try {
					if (self.isLocal) {
						returnBuffer.setTo(self.future.join())
					} else {
						returnBuffer.setTo(self.future.getNow(null))
					}
				} catch (err: CompletionException) {
					returnBuffer.setTo(err.message ?: "internal error")
				} catch (err: CancellationException) {
					returnBuffer.setTo(err.message ?: "internal error")
				}
			})
			.build()
	}
}
