/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * This source file is part of the Cangjie project, licensed under Apache-2.0
 * with Runtime Library Exception.
 *
 * See https://cangjie-lang.cn/pages/LICENSE for license information.
 */

package std.core

@Intrinsic
func futureInit<T>(future: Future<T>): Unit

@Intrinsic
func futureIsComplete<T>(future: Future<T>): Bool

@Intrinsic
func futureWait<T>(future: Future<T>, timeout: Int64): Unit

@Intrinsic
func futureNotifyAll<T>(future: Future<T>): Unit

@FastNative
foreign func CJ_CORE_PrintOomHint(): Unit

let INT64_MAX: Int64 = 0x7fff_ffff_ffff_ffffi64

private open class FutureResult {}

private class FutureResInit <: FutureResult {}

private class FutureOk<T> <: FutureResult {
    var value: T
    init(v: T) {
        value = v
    }
}

private class FutureErr <: FutureResult {
    static let cachedResult = FutureErr(globalOutOfMemoryError)
    var err: Error
    init(e: Error) {
        err = e
    }
}

private class FutureExcept <: FutureResult {
    var except: Exception
    init(e: Exception) {
        except = e
    }
}

public class Future<T> {
    // `_thread` must be the first field,
    // it will be accessed when spawning a runtime cj thread.
    private let _thread: Thread = Thread()
    private var result: FutureResult = FutureResInit()
    private var executeFn: () -> T

    private init(fn: () -> T) {
        this.executeFn = fn
        unsafe { futureInit(this) }
    }

    private func unwrapResult(): T {
        match (result) {
            case v: FutureOk<T> => return v.value
            case e: FutureExcept => throw e.except
            case e: FutureErr => throw e.err
            case _ => throw IllegalStateException("Future result is in an unexpected state.")
        }
    }

    public prop thread: Thread {
        get() {
            _thread
        }
    }

    /**
     * Blocking the current thread,
     * waiting for the result of the thread corresponding to this Future<T> object.
     * @throws Exception or Error if an exception occurs in the corresponding thread.
     */
    public func get(): T {
        let isComplete = unsafe { futureIsComplete(this) }
        if (!isComplete) {
            unsafe { futureWait(this, Int64.Max) }
        }
        return unwrapResult()
    }

    /**
     * Non-blocking method that immediately returns None if thread has not finished execution.
     * Returns the computed result otherwise.
     * @throws Exception or Error if an exception occurs in the corresponding thread.
     */
    public func tryGet(): Option<T> {
        let isComplete = unsafe { futureIsComplete(this) }
        if (!isComplete) {
            return Option<T>.None
        }
        return Some(unwrapResult())
    }

    /**
     * Blocking the current thread,
     * waiting for the result of the thread corresponding to this Future<T> object.
     * If the corresponding thread has not completed execution within `ns` nanoseconds,
     * the method will return `Option<T>.None`.
     * If `ns <= 0`, same as `get()`.
     * @throws Exception or Error if an exception occurs in the corresponding thread.
     */
    public func get(timeout: Duration): T {
        if (timeout <= Duration.Zero) {
            return get()
        }
        let isComplete = unsafe { futureIsComplete(this) }
        if (!isComplete) {
            // Wait for `ns` nanoseconds
            unsafe { futureWait(this, timeout.toNanoseconds()) }
            // Check whether the future is complete
            let isCompleteAfterWait = unsafe { futureIsComplete(this) }
            if (!isCompleteAfterWait) {
                throw TimeoutException()
            }
        }
        return unwrapResult()
    }

    /**
     * Mark the current future as cancelled.
     * Send a termination request to its executing thread.
     */
    public func cancel(): Unit {
        this._thread.setCancellationRequest()
    }

    /**
     * Execute the task (thread) and set its result.
     * This method is the entry function of a new thread with a future,
     * and it will be used by CodeGen directly and passed to Cangjie runtime.
     ** BE AWARE OF: **
     * This method is tied to the runtime implementation details,
     * and if you want to make any changes, you should consider
     * whether you need to update the runtime implementation as well.
     */
    func execute(): Unit {
        // Wait until `_thread` object is assigned with its CJThread handle,
        this._thread.waitForCJThreadHandle()
        // then, bind the object.
        setThreadObject(this._thread)
        try {
            this.result = FutureOk<T>(executeFn())
        } catch (ex: Exception) {
            this.result = FutureExcept(ex)
            handleException(ex)
        } catch (er: OutOfMemoryError) {
            this.result = FutureErr.cachedResult
            unsafe { CJ_CORE_PrintOomHint() }
            handleError(er)
        } catch (er: Error) {
            this.result = FutureErr(er)
            er.printStackTrace()
            handleError(er)
        } finally {
            unsafe { futureNotifyAll(this) }
        }
        this._thread.clearRuntimeCJThreadHandle()
        return
    }

    // ** BE AWARE OF: **
    // This method is tied to the runtime implementation details,
    // and if you want to make any changes, you should consider
    // whether you need to update the runtime implementation as well..
    static func executeClosure(fn: () -> T): Unit {
        try {
            fn()
        } catch (ex: Exception) {
            handleException(ex)
        } catch (_: OutOfMemoryError) {
            eprintln("An exception has occurred:")
            eprintln("    Out of memory")
        } catch (er: Error) {
            er.printStackTrace()
        }
        return
    }
}

/**
 * The optional argument type of the `spawn` expression.
 * Specific derived types of `ThreadContext` could affect the behavior of the thread at runtime.
 * Only for `MainThreadContext` by now.
 */
public interface ThreadContext {
    func end(): Unit
    func hasEnded(): Bool
}