/*:#localized(key: "TakingOverTheStateMachine")
 # Taking over the state machine

 An `Operation` moves through a small **state machine**:
 `ready` → `executing` → `finished`.

 * **ready**: dependencies satisfied, waiting for a turn.
 * **executing**: currently running.
 * **finished**: done (whether it completed or was cancelled).

 For a normal operation, Foundation drives these transitions for you and flips to
 **finished** as soon as `main()` returns. For callback-based work, you need to keep
 the operation in **executing** until the callback fires, so you have to drive the
 transitions yourself.

 Foundation watches three read-only properties (`isReady`, `isExecuting`,
 `isFinished`) using **KVO** (Key-Value Observing), the Objective-C mechanism where
 an object announces "this property is about to change / just changed" so observers
 can react. An `OperationQueue` observes `isFinished`; that's how it learns an
 operation is done and can start the next dependent one. So when you flip these
 states by hand, you must **fire the KVO notifications** (`willChangeValue(forKey:)`
 before, `didChangeValue(forKey:)` after) or the queue never finds out.

 There's one more piece. You override `isAsynchronous` to return `true`. That's your
 promise to Foundation: *"don't assume I'm finished when `start()` returns; watch
 `isFinished`, I'll flip it myself."* Here's a small, reusable base class that does
 exactly that:
 */
//#-editable-code
import Foundation

class AsyncOperation: Operation {
    private var _isExecuting = false
    private var _isFinished = false

    override var isAsynchronous: Bool { true }

    override var isExecuting: Bool {
        get { _isExecuting }
        set {
            willChangeValue(forKey: "isExecuting")
            _isExecuting = newValue
            didChangeValue(forKey: "isExecuting")
        }
    }

    override var isFinished: Bool {
        get { _isFinished }
        set {
            willChangeValue(forKey: "isFinished")
            _isFinished = newValue
            didChangeValue(forKey: "isFinished")
        }
    }

    override func start() {
        // Respect a cancel that arrived before we even began.
        if isCancelled {
            isFinished = true
            return
        }
        isExecuting = true
        main() // subclasses start their async work here...
    }

    /// Subclasses MUST call this from their callback when the work is done.
    func finish() {
        isExecuting = false
        isFinished = true
    }
}
//#-end-editable-code
/*:#localized(key: "TakingOverTheStateMachine-2")
 Read `start()` closely. If the operation was cancelled before it began, we go
 straight to **finished** without running `main()`. Otherwise we move to
 **executing** and call `main()`, but we do *not* finish here. The operation stays in
 **executing** until someone calls `finish()`, which moves it to **finished** and
 fires the KVO notification the queue is waiting for.

 Now the upload becomes correct. A subclass overrides `main()`, starts the async
 work, and calls `finish()` from the callback:
 */
//#-editable-code
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

final class UploadToPrinterOperation: AsyncOperation {
    let jobName: String
    
    init(jobName: String) {
        self.jobName = jobName
    }

    override func main() {
        print("⬆️ Uploading \(jobName) to the print server...")
        DispatchQueue.global().asyncAfter(deadline: .now() + 1) { [weak self] in
            guard let self else { return }

            // A late cancel still has to end in `finished`, or the
            // queue waits forever.
            if self.isCancelled {
                print("🚫 Upload of \(self.jobName) was cancelled")
                self.finish()
                return
            }

            print("✅ \(self.jobName) reached the print server")
            self.finish()
        }
    }
}

let queue = OperationQueue()
let upload = UploadToPrinterOperation(jobName: "Poster")

upload.completionBlock = {
    print("🎉 completionBlock: Poster upload is done")
    PlaygroundPage.current.finishExecution()
}

queue.addOperation(upload)
print("Queued the upload; the page stays alive until it truly finishes")
//#-end-editable-code
/*:#localized(key: "TakingOverTheStateMachine-3")
 **Watch it run:** now the order is `⬆️ Uploading...`, then a second later
 `✅ ... reached the print server`, and *only then* `🎉 completionBlock`. The operation
 stayed in **executing** the whole second, exactly as it should. If this upload had a
 dependent operation, it would now wait for the real completion, not the premature
 one.

 * callout(Try it): Comment out the `self.finish()` calls in the callback and run:
 the `🎉 completionBlock` line never appears, because the operation never leaves
 **executing**.

 > **The one rule you can't break:** an asynchronous operation must
 > *always* reach **finished**, on every path: success *and* cancellation. That's why
 > the cancel branch above still calls `finish()`. Forget it and the operation is stuck
 > in **executing** forever, jamming the queue behind it like a paper jam that never
 > clears.

 [Previous](@previous)  |  page 5 of 7  |  [Next: completionBlock, Briefly](@next)
 */
