//
//  MainThreadOperation.swift
//  RSCore
//
//  Created by Brent Simmons on 1/10/20.
//  Copyright © 2020 Ranchero Software, LLC. All rights reserved.
//

import Foundation

/// Code to be run by MainThreadOperationQueue.
///
/// When finished, it must call operationDelegate.operationDidComplete(self).
/// If it’s canceled, it should not call the delegate.
/// When it’s canceled, it should do its best to stop
/// doing whatever it’s doing. However, it should not
/// leave data in an inconsistent state.
public protocol MainThreadOperation: AnyObject {

	// These three properties are set by MainThreadOperationQueue. Don’t set them.
	@MainActor var isCanceled: Bool { get set } // Check this at appropriate times in case the operation has been canceled.
	@MainActor var id: Int? { get set }
	@MainActor var operationDelegate: MainThreadOperationDelegate? { get set } // Make this weak.

	/// Name may be useful for debugging. Unused otherwise.
	@MainActor var name: String? { get set }

	typealias MainThreadOperationCompletionBlock = (MainThreadOperation) -> Void

	/// Called when the operation completes.
	///
	/// The completionBlock is called
	/// even if the operation was canceled. The completionBlock
	/// takes the operation as parameter, so you can inspect it as needed.
	///
	/// Implementations of MainThreadOperation are *not* responsible
	/// for calling the completionBlock — MainThreadOperationQueue
	/// handles that.
	///
	/// The completionBlock is always called on the main thread.
	/// The queue will clear the completionBlock after calling it.
	@MainActor var completionBlock: MainThreadOperationCompletionBlock? { get set }

	/// Do the thing this operation does.
	///
	/// This code runs on the main thread. If you want to run
	/// code off of the main thread, you can use the standard mechanisms:
	/// a DispatchQueue, most likely.
	///
	/// When this is called, you don’t need to check isCanceled:
	/// it’s guaranteed to not be canceled. However, if you run code
	/// in another thread, you should check isCanceled in that code.
	@MainActor func run()

	/// Cancel this operation.
	///
	/// Any operations dependent on this operation
	/// will also be canceled automatically.
	///
	/// This function has a default implementation. It’s super-rare
	/// to need to provide your own.
	@MainActor func cancel()

	/// Make this operation dependent on an other operation.
	///
	/// This means the other operation must complete before
	/// this operation gets run. If the other operation is canceled,
	/// this operation will automatically be canceled.
	/// Note: an operation can have multiple dependencies.
	///
	/// This function has a default implementation. It’s super-rare
	/// to need to provide your own.
	@MainActor func addDependency(_ parentOperation: MainThreadOperation)
}

public extension MainThreadOperation {

	@MainActor func cancel() {
		operationDelegate?.cancelOperation(self)
	}

	@MainActor func addDependency(_ parentOperation: MainThreadOperation) {
		operationDelegate?.make(self, dependOn: parentOperation)
	}

	@MainActor func informOperationDelegateOfCompletion() {
		guard !isCanceled else {
			return
		}
		if Thread.isMainThread {
			operationDelegate?.operationDidComplete(self)
		}
		else {
			DispatchQueue.main.async {
				self.informOperationDelegateOfCompletion()
			}
		}
	}
}
