/*
 * 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.sync

import std.time.DateTime

const MAX_TIMEOUT_NS: Int64 = 0x7FFF_FFFF_FFFF_FFFF
let MAX_TIMEOUT_DURATION: Duration = Duration.nanosecond * MAX_TIMEOUT_NS

class NotifyOnce {
    let m = Mutex()
    let condition: Condition
    var notified: Bool = false

    init() {
        synchronized(m) {
            condition = m.condition()
        }
    }

    func notify(): Unit {
        synchronized(m) {
            if (!notified) {
                notified = true
                condition.notify()
            }
        }
    }

    func wait(timeout!: Duration = Duration.Max): Bool {
        if (timeout >= MAX_TIMEOUT_DURATION) {
            waitWithoutTimeout()
        } else {
            waitWithTimeout(timeout)
        }
        return notified
    }

    private func waitWithoutTimeout(): Unit {
        synchronized(m) {
            // In case of spurious-wakeup, we should put `wait` inside a while loop.
            while (!notified) {
                condition.wait()
            }
        }
    }

    private func waitWithTimeout(timeout: Duration): Unit {
        var restOfTimeout = timeout
        synchronized(m) {
            // In case of spurious-wakeup, we should put `wait` inside a while loop.
            while (!notified && restOfTimeout > Duration.Zero) {
                let startTime = DateTime.nowUTC()
                condition.wait(timeout: restOfTimeout)
                let endTime = DateTime.nowUTC()
                restOfTimeout -= (endTime - startTime)
            }
        }
    }
}

// Linked-list node
class SyncNode {
    let n = NotifyOnce()
    var next: ?SyncNode = None
}

// SyncList is used to achieve "unpark-all" behaviour
class SyncList {
    private var head: ?SyncNode = None
    private let m = Mutex()

    private func addWaiter(node: SyncNode) {
        node.next = head
        head = node
    }

    func waitIf(pred: () -> Bool, timeout!: Duration = Duration.Max): Unit {
        if (!pred()) {
            return
        }
        let node = SyncNode()
        synchronized(m) {
            if (!pred()) {
                return
            }
            addWaiter(node)
        }
        node.n.wait(timeout: timeout)
    }

    func waitInLock(unlock: () -> Unit, timeout!: Duration = Duration.Max): Bool {
        let node = SyncNode()
        synchronized(m) {
            addWaiter(node)
        }
        unlock()
        node.n.wait(timeout: timeout)
    }

    // Release one sema and remove it from the head of queue
    func notifyOne(): Bool {
        synchronized(m) {
            if (let Some(current) <- head) {
                head = current.next
                current.n.notify()
                return true
            }
            return false
        }
    }

    // Notify all node in linked-list which head is the first node.
    // After this method, head node would be None
    func notifyAll(): Bool {
        synchronized(m) {
            if (let None <- head) {
                return false
            }
            while (let Some(current) <- head) {
                head = current.next
                current.n.notify()
            }
            return true
        }
    }
}