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


// Compound predicates are predicates which act on the results of evaluating other operators. We provide the basic boolean operators: AND, OR, and NOT.

extension NSCompoundPredicate {
    public enum LogicalType : UInt {
        case not
        case and
        case or
    }
}

open class NSCompoundPredicate : NSPredicate {
    public init(type: LogicalType, subpredicates: [NSPredicate]) {
        if type == .not && subpredicates.isEmpty {
            preconditionFailure("Unsupported predicate count of \(subpredicates.count) for \(type)")
        }

        self.compoundPredicateType = type
        self.subpredicates = subpredicates
        super.init(value: false)
    }
    
    open var compoundPredicateType: LogicalType
    open var subpredicates: [NSPredicate]

    /*** Convenience Methods ***/
    public convenience init(andPredicateWithSubpredicates subpredicates: [NSPredicate]) {
        self.init(type: .and, subpredicates: subpredicates)
    }
    public convenience init(orPredicateWithSubpredicates subpredicates: [NSPredicate]) {
        self.init(type: .or, subpredicates: subpredicates)
    }
    public convenience init(notPredicateWithSubpredicate predicate: NSPredicate) {
        self.init(type: .not, subpredicates: [predicate])
    }

    override open func evaluate(with object: Any?, substitutionVariables bindings: [String : Any]?) -> Bool {
        switch compoundPredicateType {
        case .and:
            return subpredicates.reduce(true, {
                $0 && $1.evaluate(with: object, substitutionVariables: bindings)
            })
        case .or:
            return subpredicates.reduce(false, {
                $0 || $1.evaluate(with: object, substitutionVariables: bindings)
            })
        case .not:
            // safe to get the 0th item here since we trap if there's not at least one on init
            return !(subpredicates[0].evaluate(with: object, substitutionVariables: bindings))
        }
    }
}