//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2017 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

@_exported import Foundation // Clang module
import ObjectiveC

public protocol _KeyValueCodingAndObserving {}

public struct NSKeyValueObservedChange<Value> {
    public typealias Kind = NSKeyValueChange
    public let kind: Kind
    ///newValue and oldValue will only be non-nil if .new/.old is passed to `observe()`. In general, get the most up to date value by accessing it directly on the observed object instead.
    public let newValue: Value?
    public let oldValue: Value?
    ///indexes will be nil unless the observed KeyPath refers to an ordered to-many property
    public let indexes: IndexSet?
    ///'isPrior' will be true if this change observation is being sent before the change happens, due to .prior being passed to `observe()`
    public let isPrior:Bool
}

///Conforming to NSKeyValueObservingCustomization is not required to use Key-Value Observing. Provide an implementation of these functions if you need to disable auto-notifying for a key, or add dependent keys
public protocol NSKeyValueObservingCustomization : NSObjectProtocol {
    static func keyPathsAffectingValue(for key: AnyKeyPath) -> Set<AnyKeyPath>
    static func automaticallyNotifiesObservers(for key: AnyKeyPath) -> Bool
}

fileprivate extension NSObject {
    
    @objc class func _old_unswizzled_automaticallyNotifiesObservers(forKey key: String?) -> Bool {
        fatalError("Should never be reached")
    }
    
    @objc class func _old_unswizzled_keyPathsForValuesAffectingValue(forKey key: String?) -> Set<String> {
        fatalError("Should never be reached")
    }

}

@objc private class _KVOKeyPathBridgeMachinery : NSObject {
    @nonobjc static var keyPathTable: [String : AnyKeyPath] = {
        /*
         Move all our methods into place. We want the following:
         _KVOKeyPathBridgeMachinery's automaticallyNotifiesObserversForKey:, and keyPathsForValuesAffectingValueForKey: methods replaces NSObject's versions of them
         NSObject's automaticallyNotifiesObserversForKey:, and keyPathsForValuesAffectingValueForKey: methods replace NSObject's _old_unswizzled_* methods
         NSObject's _old_unswizzled_* methods replace _KVOKeyPathBridgeMachinery's methods, and are never invoked
         */
        let rootClass: AnyClass = NSObject.self
        let bridgeClass: AnyClass = _KVOKeyPathBridgeMachinery.self
        
        let dependentSel = #selector(NSObject.keyPathsForValuesAffectingValue(forKey:))
        let rootDependentImpl = class_getClassMethod(rootClass, dependentSel)!
        let bridgeDependentImpl = class_getClassMethod(bridgeClass, dependentSel)!
        method_exchangeImplementations(rootDependentImpl, bridgeDependentImpl) // NSObject <-> Us
        
        let originalDependentImpl = class_getClassMethod(bridgeClass, dependentSel)! //we swizzled it onto this class, so this is actually NSObject's old implementation
        let originalDependentSel = #selector(NSObject._old_unswizzled_keyPathsForValuesAffectingValue(forKey:))
        let dummyDependentImpl = class_getClassMethod(rootClass, originalDependentSel)!
        method_exchangeImplementations(originalDependentImpl, dummyDependentImpl) // NSObject's original version <-> NSObject's _old_unswizzled_ version
        
        let autoSel = #selector(NSObject.automaticallyNotifiesObservers(forKey:))
        let rootAutoImpl = class_getClassMethod(rootClass, autoSel)!
        let bridgeAutoImpl = class_getClassMethod(bridgeClass, autoSel)!
        method_exchangeImplementations(rootAutoImpl, bridgeAutoImpl) // NSObject <-> Us
        
        let originalAutoImpl = class_getClassMethod(bridgeClass, autoSel)! //we swizzled it onto this class, so this is actually NSObject's old implementation
        let originalAutoSel = #selector(NSObject._old_unswizzled_automaticallyNotifiesObservers(forKey:))
        let dummyAutoImpl = class_getClassMethod(rootClass, originalAutoSel)!
        method_exchangeImplementations(originalAutoImpl, dummyAutoImpl) // NSObject's original version <-> NSObject's _old_unswizzled_ version
        
        return [:]
    }()
    
    @nonobjc static var keyPathTableLock = NSLock()
    
    @nonobjc fileprivate static func _bridgeKeyPath(_ keyPath:AnyKeyPath) -> String {
        guard let keyPathString = keyPath._kvcKeyPathString else { fatalError("Could not extract a String from KeyPath \(keyPath)") }
        _KVOKeyPathBridgeMachinery.keyPathTableLock.lock()
        defer { _KVOKeyPathBridgeMachinery.keyPathTableLock.unlock() }
        _KVOKeyPathBridgeMachinery.keyPathTable[keyPathString] = keyPath
        return keyPathString
    }
    
    @nonobjc fileprivate static func _bridgeKeyPath(_ keyPath:String?) -> AnyKeyPath? {
        guard let keyPath = keyPath else { return nil }
        _KVOKeyPathBridgeMachinery.keyPathTableLock.lock()
        defer { _KVOKeyPathBridgeMachinery.keyPathTableLock.unlock() }
        let path = _KVOKeyPathBridgeMachinery.keyPathTable[keyPath]
        return path
    }
    
    @objc override class func automaticallyNotifiesObservers(forKey key: String) -> Bool {
        //This is swizzled so that it's -[NSObject automaticallyNotifiesObserversForKey:]
        if let customizingSelf = self as? NSKeyValueObservingCustomization.Type, let path = _KVOKeyPathBridgeMachinery._bridgeKeyPath(key) {
            return customizingSelf.automaticallyNotifiesObservers(for: path)
        } else {
            return self._old_unswizzled_automaticallyNotifiesObservers(forKey: key) //swizzled to be NSObject's original implementation
        }
    }
    
    @objc override class func keyPathsForValuesAffectingValue(forKey key: String?) -> Set<String> {
        //This is swizzled so that it's -[NSObject keyPathsForValuesAffectingValueForKey:]
        if let customizingSelf = self as? NSKeyValueObservingCustomization.Type, let path = _KVOKeyPathBridgeMachinery._bridgeKeyPath(key!) {
            let resultSet = customizingSelf.keyPathsAffectingValue(for: path)
            return Set(resultSet.lazy.map {
                guard let str = $0._kvcKeyPathString else { fatalError("Could not extract a String from KeyPath \($0)") }
                return str
            })
        } else {
            return self._old_unswizzled_keyPathsForValuesAffectingValue(forKey: key) //swizzled to be NSObject's original implementation
        }
    }
}

func _bridgeKeyPathToString(_ keyPath:AnyKeyPath) -> String {
    return _KVOKeyPathBridgeMachinery._bridgeKeyPath(keyPath)
}

func _bridgeStringToKeyPath(_ keyPath:String) -> AnyKeyPath? {
    return _KVOKeyPathBridgeMachinery._bridgeKeyPath(keyPath)
}

public class NSKeyValueObservation : NSObject {
    
    weak var object : NSObject?
    let callback : (NSObject, NSKeyValueObservedChange<Any>) -> Void
    let path : String
    
    //workaround for <rdar://problem/31640524> Erroneous (?) error when using bridging in the Foundation overlay
    static var swizzler : NSKeyValueObservation? = {
        let bridgeClass: AnyClass = NSKeyValueObservation.self
        let observeSel = #selector(NSObject.observeValue(forKeyPath:of:change:context:))
        let swapSel = #selector(NSKeyValueObservation._swizzle_me_observeValue(forKeyPath:of:change:context:))
        let rootObserveImpl = class_getInstanceMethod(bridgeClass, observeSel)
        let swapObserveImpl = class_getInstanceMethod(bridgeClass, swapSel)
        method_exchangeImplementations(rootObserveImpl, swapObserveImpl)
        return nil
    }()
    
    fileprivate init(object: NSObject, keyPath: AnyKeyPath, callback: @escaping (NSObject, NSKeyValueObservedChange<Any>) -> Void) {
        path = _bridgeKeyPathToString(keyPath)
        let _ = NSKeyValueObservation.swizzler
        self.object = object
        self.callback = callback
    }
    
    fileprivate func start(_ options: NSKeyValueObservingOptions) {
        object?.addObserver(self, forKeyPath: path, options: options, context: nil)
    }
    
    ///invalidate() will be called automatically when an NSKeyValueObservation is deinited
    public func invalidate() {
        object?.removeObserver(self, forKeyPath: path, context: nil)
        object = nil
    }
    
    @objc func _swizzle_me_observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSString : Any]?, context: UnsafeMutableRawPointer?) {
        guard let ourObject = self.object, object as? NSObject == ourObject, let change = change else { return }
        let rawKind:UInt = change[NSKeyValueChangeKey.kindKey.rawValue as NSString] as! UInt
        let kind = NSKeyValueChange(rawValue: rawKind)!
        let notification = NSKeyValueObservedChange(kind: kind,
                                                    newValue: change[NSKeyValueChangeKey.newKey.rawValue as NSString],
                                                    oldValue: change[NSKeyValueChangeKey.oldKey.rawValue as NSString],
                                                    indexes: change[NSKeyValueChangeKey.indexesKey.rawValue as NSString] as! IndexSet?,
                                                    isPrior: change[NSKeyValueChangeKey.notificationIsPriorKey.rawValue as NSString] as? Bool ?? false)
        callback(ourObject, notification)
    }
    
    deinit {
        object?.removeObserver(self, forKeyPath: path, context: nil)
    }
}

public extension _KeyValueCodingAndObserving {
    
    ///when the returned NSKeyValueObservation is deinited or invalidated, it will stop observing
    public func observe<Value>(
            _ keyPath: KeyPath<Self, Value>,
            options: NSKeyValueObservingOptions = [],
            changeHandler: @escaping (Self, NSKeyValueObservedChange<Value>) -> Void)
        -> NSKeyValueObservation {
        let result = NSKeyValueObservation(object: self as! NSObject, keyPath: keyPath) { (obj, change) in
            let notification = NSKeyValueObservedChange(kind: change.kind,
                                                        newValue: change.newValue as? Value,
                                                        oldValue: change.oldValue as? Value,
                                                        indexes: change.indexes,
                                                        isPrior: change.isPrior)
            changeHandler(obj as! Self, notification)
        }
        result.start(options)
        return result
    }
    
    public func willChangeValue<Value>(for keyPath: KeyPath<Self, Value>) {
        (self as! NSObject).willChangeValue(forKey: _bridgeKeyPathToString(keyPath))
    }
    
    public func willChange<Value>(_ changeKind: NSKeyValueChange, valuesAt indexes: IndexSet, for keyPath: KeyPath<Self, Value>) {
        (self as! NSObject).willChange(changeKind, valuesAt: indexes, forKey: _bridgeKeyPathToString(keyPath))
    }
    
    public func willChangeValue<Value>(for keyPath: KeyPath<Self, Value>, withSetMutation mutation: NSKeyValueSetMutationKind, using set: Set<Value>) -> Void {
        (self as! NSObject).willChangeValue(forKey: _bridgeKeyPathToString(keyPath), withSetMutation: mutation, using: set)
    }
    
    public func didChangeValue<Value>(for keyPath: KeyPath<Self, Value>) {
        (self as! NSObject).didChangeValue(forKey: _bridgeKeyPathToString(keyPath))
    }
    
    public func didChange<Value>(_ changeKind: NSKeyValueChange, valuesAt indexes: IndexSet, for keyPath: KeyPath<Self, Value>) {
        (self as! NSObject).didChange(changeKind, valuesAt: indexes, forKey: _bridgeKeyPathToString(keyPath))
    }
    
    public func didChangeValue<Value>(for keyPath: KeyPath<Self, Value>, withSetMutation mutation: NSKeyValueSetMutationKind, using set: Set<Value>) -> Void {
        (self as! NSObject).didChangeValue(forKey: _bridgeKeyPathToString(keyPath), withSetMutation: mutation, using: set)
    }
}

extension NSObject : _KeyValueCodingAndObserving {}
