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

public class ReadWriteLock {
    private let _fair: Bool
    private let _readLock: Lock
    private let _writeLock: UniqueLock

    public prop readLock: Lock {
        get() {
            _readLock
        }
    }
    public prop writeLock: UniqueLock {
        get() {
            _writeLock
        }
    }

    /**
     * @param fair - Set up the fair mode.
     */
    public init(fair!: Bool = false) {
        _fair = fair
        let readWriteLockImpl = ReadWriteLockImpl(isFair: fair)
        _readLock = ReadLock(readWriteLockImpl)
        _writeLock = WriteLock(readWriteLockImpl)
    }

    public func isFair(): Bool {
        _fair
    }
}

class ReadLock <: Lock {
    ReadLock(private let _readWriteLockImpl: ReadWriteLockImpl) {}

    public func lock(): Unit {
        _readWriteLockImpl.readLock()
    }
    public func tryLock(): Bool {
        _readWriteLockImpl.tryReadLock()
    }
    public func unlock(): Unit {
        _readWriteLockImpl.readUnlock()
    }
}

class WriteLock <: UniqueLock {
    WriteLock(private let _readWriteLockImpl: ReadWriteLockImpl) {}

    public func lock(): Unit {
        _readWriteLockImpl.writeLock()
    }
    public func tryLock(): Bool {
        _readWriteLockImpl.tryWriteLock()
    }
    public func unlock(): Unit {
        _readWriteLockImpl.writeUnlock()
    }

    public func condition(): Condition {
        _readWriteLockImpl.checkWriteLockStatus()
        return ReadWriteLockCondition(_readWriteLockImpl)
    }
}

class ReadWriteLockCondition <: Condition {
    private let waitQueue = SyncList()

    ReadWriteLockCondition(private let lock: ReadWriteLockImpl) {
    }

    public func wait(): Unit {
        wait(timeout: Duration.Max)
    }

    public func wait(timeout!: Duration): Bool {
        if (timeout <= Duration.Zero) {
            throw IllegalArgumentException("Timeout cannot be less than or equal to `Duration.Zero`.")
        }

        lock.checkWriteLockStatus()
        let writeCount = lock.getWriteCount()
        let success = waitQueue.waitInLock({=> lock.writeUnlock(writeCount: writeCount)}, timeout: timeout) // release the lock and wait for `notify`
        lock.writeLock(writeCount: writeCount)
        return success
    }

    public func waitUntil(predicate: () -> Bool): Unit {
        lock.checkWriteLockStatus()
        while (!predicate()) {
            wait()
        }
    }

    public func waitUntil(predicate: () -> Bool, timeout!: Duration): Bool {
        if (timeout <= Duration.Zero) {
            throw IllegalArgumentException("Timeout cannot be less than or equal to `Duration.Zero`.")
        }
        lock.checkWriteLockStatus()

        let startTime = MonoTime.now()
        var spentTime = Duration.Zero
        while (!predicate()) {
            if (spentTime >= timeout) { // timeout
                return false
            }

            let success = wait(timeout: timeout - spentTime)
            if (!success) { // timeout
                return false
            }
            spentTime = MonoTime.now() - startTime
        }
        return true
    }

    public func notify(): Unit {
        lock.checkWriteLockStatus()
        waitQueue.notifyOne()
    }

    public func notifyAll(): Unit {
        lock.checkWriteLockStatus()
        waitQueue.notifyAll()
    }
}