import Foundation

// The `haveCount` matchers do not print the full string representation of the collection value,
// instead they only print the type name and the expected count. This makes it easier to understand
// the reason for failed expectations. See: https://github.com/Quick/Nimble/issues/308.
// The representation of the collection content is provided in a new line as an `extendedMessage`.

/// A Nimble matcher that succeeds when the actual CollectionType's count equals
/// the expected value
public func haveCount<T: CollectionType>(expectedValue: T.Index.Distance) -> NonNilMatcherFunc<T> {
    return NonNilMatcherFunc { actualExpression, failureMessage in
        if let actualValue = try actualExpression.evaluate() {
            failureMessage.postfixMessage = "have \(prettyCollectionType(actualValue)) with count \(stringify(expectedValue))"
            let result = expectedValue == actualValue.count
            failureMessage.actualValue = "\(actualValue.count)"
            failureMessage.extendedMessage = "Actual Value: \(stringify(actualValue))"
            return result
        } else {
            return false
        }
    }
}

/// A Nimble matcher that succeeds when the actual collection's count equals
/// the expected value
public func haveCount(expectedValue: Int) -> MatcherFunc<NMBCollection> {
    return MatcherFunc { actualExpression, failureMessage in
        if let actualValue = try actualExpression.evaluate() {
            failureMessage.postfixMessage = "have \(prettyCollectionType(actualValue)) with count \(stringify(expectedValue))"
            let result = expectedValue == actualValue.count
            failureMessage.actualValue = "\(actualValue.count)"
            failureMessage.extendedMessage = "Actual Value: \(stringify(actualValue))"
            return result
        } else {
            return false
        }
    }
}

#if _runtime(_ObjC)
extension NMBObjCMatcher {
    public class func haveCountMatcher(expected: NSNumber) -> NMBObjCMatcher {
        return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
            let location = actualExpression.location
            let actualValue = try! actualExpression.evaluate()
            if let value = actualValue as? NMBCollection {
                let expr = Expression(expression: ({ value as NMBCollection}), location: location)
                return try! haveCount(expected.integerValue).matches(expr, failureMessage: failureMessage)
            } else if let actualValue = actualValue {
                failureMessage.postfixMessage = "get type of NSArray, NSSet, NSDictionary, or NSHashTable"
                failureMessage.actualValue = "\(classAsString(actualValue.dynamicType))"
            }
            return false
        }
    }
}
#endif
