////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

import Foundation
import Realm

#if BUILDING_REALM_SWIFT_TESTS
import RealmSwift
#endif

// MARK: Internal Helpers

// Swift 3.1 provides fixits for some of our uses of unsafeBitCast
// to use unsafeDowncast instead, but the bitcast is required.
internal func noWarnUnsafeBitCast<T, U>(_ x: T, to type: U.Type) -> U {
    return unsafeBitCast(x, to: type)
}

/// Given a list of `Any`-typed varargs, unwrap any optionals and
/// replace them with the underlying value or NSNull.
internal func unwrapOptionals(in varargs: [Any]) -> [Any] {
    return varargs.map { arg in
        if let someArg = arg as Any? {
            return someArg
        }
        return NSNull()
    }
}

internal func notFoundToNil(index: UInt) -> Int? {
    if index == UInt(NSNotFound) {
        return nil
    }
    return Int(index)
}

internal func throwRealmException(_ message: String, userInfo: [AnyHashable: Any]? = nil) -> Never {
    NSException(name: NSExceptionName(rawValue: RLMExceptionName), reason: message, userInfo: userInfo).raise()
    fatalError() // unreachable
}

internal func throwForNegativeIndex(_ int: Int, parameterName: String = "index") {
    if int < 0 {
        throwRealmException("Cannot pass a negative value for '\(parameterName)'.")
    }
}

internal func gsub(pattern: String, template: String, string: String, error: NSErrorPointer = nil) -> String? {
    let regex = try? NSRegularExpression(pattern: pattern, options: [])
    return regex?.stringByReplacingMatches(in: string, options: [],
                                           range: NSRange(location: 0, length: string.utf16.count),
                                           withTemplate: template)
}

extension ObjectBase {
    // Must *only* be used to call Realm Objective-C APIs that are exposed on `RLMObject`
    // but actually operate on `RLMObjectBase`. Do not expose cast value to user.
    internal func unsafeCastToRLMObject() -> RLMObject {
        return noWarnUnsafeBitCast(self, to: RLMObject.self)
    }
}

internal func coerceToNil(_ value: Any) -> Any? {
    if value is NSNull {
        return nil
    }
    // nil in Any is bridged to obj-c as NSNull. In the obj-c code we usually
    // convert NSNull back to nil, which ends up as Optional<Any>.none
    if case Optional<Any>.none = value {
        return nil
    }
    return value
}

// MARK: CustomObjectiveCBridgeable

/// :nodoc:
public func dynamicBridgeCast<T>(fromObjectiveC x: Any) -> T {
    return failableDynamicBridgeCast(fromObjectiveC: x)!
}

/// :nodoc:
@usableFromInline
internal func failableDynamicBridgeCast<T>(fromObjectiveC x: Any) -> T? {
    if let value = x as? T {
        return value
    } else if T.self == DynamicObject.self {
        return unsafeBitCast(x as AnyObject, to: T.self)
    } else if let bridgeableType = T.self as? CustomObjectiveCBridgeable.Type {
        return bridgeableType.bridging(objCValue: x) as? T
    } else if let bridgeableType = T.self as? RealmEnum.Type {
        return bridgeableType._rlmFromRawValue(x).flatMap { $0 as? T }
    } else {
        return x as? T
    }
}

/// :nodoc:
public func dynamicBridgeCast<T>(fromSwift x: T) -> Any {
    if let x = x as? CustomObjectiveCBridgeable {
        return x.objCValue
    } else if let bridgeableType = T.self as? RealmEnum.Type {
        return bridgeableType._rlmToRawValue(x)
    } else {
        return x
    }
}

// Used for conversion from Objective-C types to Swift types
internal protocol CustomObjectiveCBridgeable {
    static func bridging(objCValue: Any) -> Self
    var objCValue: Any { get }
}

// `NSNumber as? Float` fails if the value can't be exactly represented as a float,
// unlike the other NSNumber conversions
extension Float: CustomObjectiveCBridgeable {
    internal static func bridging(objCValue: Any) -> Float {
        return (objCValue as! NSNumber).floatValue
    }
    internal var objCValue: Any {
        return NSNumber(value: self)
    }
}

extension Int8: CustomObjectiveCBridgeable {
    internal static func bridging(objCValue: Any) -> Int8 {
        return (objCValue as! NSNumber).int8Value
    }
    internal var objCValue: Any {
        return NSNumber(value: self)
    }
}
extension Int16: CustomObjectiveCBridgeable {
    internal static func bridging(objCValue: Any) -> Int16 {
        return (objCValue as! NSNumber).int16Value
    }
    internal var objCValue: Any {
        return NSNumber(value: self)
    }
}
extension Int32: CustomObjectiveCBridgeable {
    internal static func bridging(objCValue: Any) -> Int32 {
        return (objCValue as! NSNumber).int32Value
    }
    internal var objCValue: Any {
        return NSNumber(value: self)
    }
}
extension Int64: CustomObjectiveCBridgeable {
    internal static func bridging(objCValue: Any) -> Int64 {
        return (objCValue as! NSNumber).int64Value
    }
    internal var objCValue: Any {
        return NSNumber(value: self)
    }
}
extension Optional: CustomObjectiveCBridgeable {
    internal static func bridging(objCValue: Any) -> Optional {
        if objCValue as AnyObject is NSNull {
            return nil
        }
        return failableDynamicBridgeCast(fromObjectiveC: objCValue)
    }
    internal var objCValue: Any {
        if let value = self {
            return dynamicBridgeCast(fromSwift: value)
        } else {
            return NSNull()
        }
    }
}
extension Decimal128: CustomObjectiveCBridgeable {
    static func bridging(objCValue: Any) -> Decimal128 {
        if let number = objCValue as? NSNumber {
            return Decimal128(number: number)
        }
        if let str = objCValue as? String {
            return (try? Decimal128(string: str)) ?? Decimal128("nan")
        }
        return objCValue as! Decimal128
    }
    var objCValue: Any {
        return self
    }
}
extension AnyRealmValue: CustomObjectiveCBridgeable {
    static func bridging(objCValue: Any) -> AnyRealmValue {
        if let any = objCValue as? RLMValue {
            return ObjectiveCSupport.convert(value: any)
        }
        throwRealmException("objCValue is not bridgeable to AnyRealmValue")
    }
    var objCValue: Any {
        return ObjectiveCSupport.convert(value: self) ?? NSNull()
    }
}
