// EXERCISE 04 — BROKEN STARTING POINT.  Do not edit this file; copy it.
//
// ============================ UNSAFE CODE WARNING ============================
// The two consumers below use a condition variable incorrectly, on purpose.
// Both mistakes are bounded here by finite deadlines and a watchdog. In real
// code the second one is a trap on an empty array, i.e. a crash.
// =============================================================================
//
// THE SCENARIO
//   A settings loader publishes a configuration and announces it. A consumer
//   waits for that announcement. In the field: "the app occasionally sits on
//   the launch screen for a second and a half, then carries on normally", and
//   separately, "a background reader crashes taking an item from an empty
//   queue, about once a week".
//
// Build: swiftc -swift-version 6 -O handoff.swift -o handoff_broken
// Run:   ./handoff_broken
//
// Expected: partA reports that the consumer slept for its full deadline even
// though the data was already published, and partB reports a nonzero
// wouldHaveCrashed count. Exit status 0 — neither mistake is a crash HERE,
// only because this fixture counts the failure instead of taking the item.
//
// Bounded: every wait has a finite deadline, every loop has an iteration cap,
// and a watchdog force-exits after WATCHDOG_S.

import Foundation
import Synchronization

let WATCHDOG_S: Double = 60
let EXIT_WATCHDOG: Int32 = 75

func armWatchdog(_ seconds: Double, _ label: String) {
    let t = Thread {
        Thread.sleep(forTimeInterval: seconds)
        fputs("\nWATCHDOG: \(label) exceeded \(seconds)s — forcing _exit(\(EXIT_WATCHDOG)).\n", stderr)
        fflush(stderr); _exit(EXIT_WATCHDOG)
    }
    t.stackSize = 512 * 1024; t.start()
}

// MARK: - Part A — the announcement that arrived before anyone was listening
//
// The loader publishes the configuration and signals. No consumer has started
// yet. The consumer then begins, and waits.

func partA() -> (waitedMs: Int, dataWasAlreadyPublished: Bool) {
    let cond = NSCondition()
    var configuration: String? = nil

    // The loader finishes entirely before the consumer starts.
    cond.lock()
    configuration = "{\"theme\":\"dark\"}"
    cond.signal()
    cond.unlock()

    let deadline = Date().addingTimeInterval(1.5)
    let start = Date()

    cond.lock()
    // BUG: the consumer waits for an announcement without ever asking whether
    // the thing being announced has already happened.
    _ = cond.wait(until: deadline)
    let published = configuration != nil
    cond.unlock()

    let waitedMs = Int(Date().timeIntervalSince(start) * 1000)
    print("  partA waitedMs=\(waitedMs) dataWasAlreadyPublished=\(published)")
    return (waitedMs, published)
}

// MARK: - Part B — a wakeup treated as a promise

/// A bounded queue guarded by one NSCondition. The condition's lock protects
/// `items`; the condition itself only announces that `items` may have changed.
final class WorkQueue: @unchecked Sendable {
    private let cond = NSCondition()
    private var items: [Int] = []
    private var closed = false
    private let stats = Mutex(Stats())

    struct Stats { var wouldHaveCrashed = 0; var spuriousAbsorbed = 0 }
    var snapshot: Stats { stats.withLock { $0 } }

    func publish(_ v: Int) {
        cond.lock(); defer { cond.unlock() }
        items.append(v)
        cond.signal()
    }

    func close() {
        cond.lock(); defer { cond.unlock() }
        closed = true
        cond.broadcast()          // termination must wake EVERYONE
    }

    /// A wakeup that changes no state. Real spurious wakeups come from the
    /// kernel and are rare; injecting one makes the defect reproducible instead
    /// of a once-a-week production mystery.
    func rattle() {
        cond.lock(); defer { cond.unlock() }
        cond.broadcast()
    }

    /// Returns the next item, nil if the queue closed, or -1 to mean "in real
    /// code this line would have been `items.removeFirst()` on an empty array,
    /// i.e. a trap".
    func take(deadline: Date) -> Int? {
        cond.lock(); defer { cond.unlock() }
        if items.isEmpty && !closed {     // BUG: checked once, never re-checked
            _ = cond.wait(until: deadline)
        }
        // The line below ASSUMES the wakeup meant "an item is ready".
        guard !items.isEmpty else {
            if closed { return nil }
            stats.withLock { $0.wouldHaveCrashed += 1 }
            return -1
        }
        return items.removeFirst()
    }
}

func partB() -> (consumed: Int, expected: Int, wouldHaveCrashed: Int, absorbed: Int) {
    let queue = WorkQueue()
    let consumers = 3
    let items = 120
    let consumed = Mutex(0)
    let deadline = Date().addingTimeInterval(8)
    let group = DispatchGroup()

    for _ in 0..<consumers {
        DispatchQueue.global(qos: .utility).async(group: group) {
            var iterations = 0
            while Date() < deadline && iterations < items * 4 {
                iterations += 1
                guard let v = queue.take(deadline: deadline) else { break }   // nil = closed
                if v >= 0 { consumed.withLock { $0 += 1 } }
            }
        }
    }

    // Every consumer is now parked on an empty queue. Rattle the condition
    // without changing any state, three times, once per consumer.
    Thread.sleep(forTimeInterval: 0.4)
    for _ in 0..<consumers { queue.rattle(); Thread.sleep(forTimeInterval: 0.05) }

    for i in 0..<items { queue.publish(i); if i % 16 == 15 { Thread.sleep(forTimeInterval: 0.002) } }
    Thread.sleep(forTimeInterval: 0.5)
    queue.close()
    group.wait()

    let s = queue.snapshot
    let got = consumed.withLock { $0 }
    print("  partB consumed=\(got) expected=\(items) "
        + "wouldHaveCrashed=\(s.wouldHaveCrashed) spuriousAbsorbed=\(s.spuriousAbsorbed)")
    return (got, items, s.wouldHaveCrashed, s.spuriousAbsorbed)
}

// MARK: - Part C — the reference contrast, identical in both builds
//
// A DispatchSemaphore is a COUNTER. Signal it with nobody waiting and the
// permit is stored; the next waiter takes it and proceeds immediately.
// An NSCondition stores NOTHING. Signal it with nobody waiting and the signal
// is gone. That single difference is the whole exercise: a permit is durable,
// an announcement is not, and neither of them is your predicate.

func partC() -> Bool {
    let sem = DispatchSemaphore(value: 0)
    sem.signal()                                      // nobody is waiting yet
    let start = Date()
    let result = sem.wait(timeout: .now() + 1.5)      // takes the stored permit
    let waitedMs = Int(Date().timeIntervalSince(start) * 1000)
    let survived = result == .success && waitedMs < 500
    print("  partC semaphorePermitSurvivedTheGap=\(survived) waitedMs=\(waitedMs)")
    return survived
}

// MARK: - Entry

armWatchdog(WATCHDOG_S, "EX04 build=broken")

let a = partA()
let b = partB()
let c = partC()

let sleptThroughPublishedData = a.waitedMs >= 1000 && a.dataWasAlreadyPublished
print("EX04 build=broken "
    + "sleptThroughPublishedData=\(sleptThroughPublishedData) "
    + "partAWaitedMs=\(a.waitedMs) "
    + "wouldHaveCrashed=\(b.wouldHaveCrashed) "
    + "spuriousAbsorbed=\(b.absorbed) "
    + "consumed=\(b.consumed) expected=\(b.expected) "
    + "semaphorePermitDurable=\(c) "
    + "correct=\(!sleptThroughPublishedData && b.wouldHaveCrashed == 0 && b.consumed == b.expected)")
