//
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import Foundation

public extension CGFloat {
    func clamp(_ minValue: CGFloat, _ maxValue: CGFloat) -> CGFloat {
        return CGFloat.clamp(self, min: minValue, max: maxValue)
    }

    func clamp01() -> CGFloat {
        return CGFloat.clamp01(self)
    }

    /// Returns a random value within the specified range with a fixed number of discrete choices.
    ///
    /// ```
    /// CGFloat.random(in: 0..10, choices: 2)  // => 5
    /// CGFloat.random(in: 0..10, choices: 2)  // => 0
    /// CGFloat.random(in: 0..10, choices: 2)  // => 5
    ///
    /// CGFloat.random(in: 0..10, choices: 10)  // => 8
    /// CGFloat.random(in: 0..10, choices: 10)  // => 4
    /// CGFloat.random(in: 0..10, choices: 10)  // => 0
    /// ```
    ///
    /// - Parameters:
    ///   - range: The range in which to create a random value.
    ///     `range` must be finite and nonempty.
    ///   - choices: The number of discrete choices for the result.
    /// - Returns: A random value within the bounds of `range`, constrained to the number of `choices`.
    static func random(in range: Range<CGFloat>, choices: UInt) -> CGFloat {
        let rangeSize = range.upperBound - range.lowerBound
        let choice = UInt.random(in: 0..<choices)
        return range.lowerBound + (rangeSize * CGFloat(choice) / CGFloat(choices))
    }

    // Linear interpolation
    func lerp(_ minValue: CGFloat, _ maxValue: CGFloat) -> CGFloat {
        return CGFloat.lerp(left: minValue, right: maxValue, alpha: self)
    }

    // Inverse linear interpolation
    func inverseLerp(_ minValue: CGFloat, _ maxValue: CGFloat, shouldClamp: Bool = false) -> CGFloat {
        let value = CGFloat.inverseLerp(self, min: minValue, max: maxValue)
        return (shouldClamp ? CGFloat.clamp01(value) : value)
    }

    static let halfPi: CGFloat = CGFloat.pi * 0.5

    func fuzzyEquals(_ other: CGFloat, tolerance: CGFloat = 0.001) -> Bool {
        return abs(self - other) < tolerance
    }

    var square: CGFloat {
        return self * self
    }

    func average(_ other: CGFloat) -> CGFloat {
        (self + other) * 0.5
    }
}

// MARK: -

public extension Double {
    func clamp(_ minValue: Double, _ maxValue: Double) -> Double {
        return max(minValue, min(maxValue, self))
    }

    func clamp01() -> Double {
        return clamp(0, 1)
    }

    // Linear interpolation
    func lerp(_ minValue: Double, _ maxValue: Double) -> Double {
        return (minValue * (1 - self)) + (maxValue * self)
    }

    // Inverse linear interpolation
    func inverseLerp(_ minValue: Double, _ maxValue: Double, shouldClamp: Bool = false) -> Double {
        let value = (self - minValue) / (maxValue - minValue)
        return (shouldClamp ? value.clamp01() : value)
    }
}

// MARK: -

public extension Float {
    func clamp(_ minValue: Float, _ maxValue: Float) -> Float {
        return max(minValue, min(maxValue, self))
    }

    func clamp01() -> Float {
        return clamp(0, 1)
    }

    // Linear interpolation
    func lerp(_ minValue: Float, _ maxValue: Float) -> Float {
        return (minValue * (1 - self)) + (maxValue * self)
    }

    // Inverse linear interpolation
    func inverseLerp(_ minValue: Float, _ maxValue: Float, shouldClamp: Bool = false) -> Float {
        let value = (self - minValue) / (maxValue - minValue)
        return (shouldClamp ? value.clamp01() : value)
    }
}

// MARK: -

public extension Int {
    func clamp(_ minValue: Int, _ maxValue: Int) -> Int {
        assert(minValue <= maxValue)

        return Swift.max(minValue, Swift.min(maxValue, self))
    }
}

// MARK: -

public extension UInt {
    func clamp(_ minValue: UInt, _ maxValue: UInt) -> UInt {
        assert(minValue <= maxValue)

        return Swift.max(minValue, Swift.min(maxValue, self))
    }
}

// MARK: -

public extension UInt64 {
    var asNSNumber: NSNumber {
        NSNumber(value: self)
    }
}

// MARK: -

public extension Bool {
    static func ^ (left: Bool, right: Bool) -> Bool {
        return left != right
    }
}
