//
//  MultiThreadDictionary.swift
//  Tusker
//
//  Created by Shadowfacts on 5/6/20.
//  Copyright © 2020 Shadowfacts. All rights reserved.
//

import Foundation
import os

// once we target iOS 16, replace uses of this with OSAllocatedUnfairLock<[Key: Value]>
// to make the lock semantics more clear
@available(iOS, obsoleted: 16.0)
class MultiThreadDictionary<Key: Hashable & Sendable, Value: Sendable> {
    private let lock: any Lock<[Key: Value]>
    
    init() {
        if #available(iOS 16.0, *) {
            self.lock = OSAllocatedUnfairLock(initialState: [:])
        } else {
            self.lock = UnfairLock(initialState: [:])
        }
    }
    
    subscript(key: Key) -> Value? {
        get {
            return lock.withLock { dict in
                dict[key]
            }
        }
        set(value) {
            _ = lock.withLock { dict in
                dict[key] = value
            }
        }
    }
    
    /// If the result of this function is unused, it is preferable to use `removeValueWithoutReturning` as it executes asynchronously and doesn't block the calling thread.
    func removeValue(forKey key: Key) -> Value? {
        return lock.withLock { dict in
            dict.removeValue(forKey: key)
        }
    }
    
    func contains(key: Key) -> Bool {
        return lock.withLock { dict in
            dict.keys.contains(key)
        }
    }
    
    // TODO: this should really be throws/rethrows but the stupid type-erased lock makes that not possible
    func withLock<R>(_ body: @Sendable (inout [Key: Value]) throws -> R) rethrows -> R where R: Sendable {
        return try lock.withLock { dict in
            return try body(&dict)
        }
    }
}

// TODO: replace this only with OSAllocatedUnfairLock
@available(iOS, obsoleted: 16.0)
fileprivate protocol Lock<State> {
    associatedtype State
    func withLock<R>(_ body: @Sendable (inout State) throws -> R) rethrows -> R where R: Sendable
}

@available(iOS 16.0, *)
extension OSAllocatedUnfairLock: Lock {
}

// from http://www.russbishop.net/the-law
fileprivate class UnfairLock<State>: Lock {
    private var lock: UnsafeMutablePointer<os_unfair_lock>
    private var state: State
    init(initialState: State) {
        self.state = initialState
        self.lock = .allocate(capacity: 1)
        self.lock.initialize(to: os_unfair_lock())
    }
    deinit {
        self.lock.deinitialize(count: 1)
        self.lock.deallocate()
    }
    func withLock<R>(_ body: (inout State) throws -> R) rethrows -> R where R: Sendable {
        os_unfair_lock_lock(lock)
        defer { os_unfair_lock_unlock(lock) }
        return try body(&state)
    }
}
