import simd

extension simd_float4 {
    var xyz: SIMD3<Float> {
        return SIMD3<Float>(x, y, z)
    }
}

public class AdamantiumMath {
    
    public static func lerp(start: Float, target: Float, t: Float) -> Float {
        return (1 - t) * start + t * target
    }

    public static func easeOutQuint(_ x: Float) -> Float {
        return 1 - pow(1 - x, 5)
    }

    public static func easeOutSine(_ x: Float) -> Float {
        return sin((x * .pi) / 2)
    }

    public static func easeInQuint(_ x: Float) -> Float {
        return x * x * x * x * x
    }

    public static func easeInSine(_ x: Float) -> Float {
        return 1 - cos((x * .pi) / 2)
    }
    
    public static func radians(degrees: Float) -> Float {
        return (degrees / 180.0) * Float.pi;
    }
    
    public static let identity4x4: simd_float4x4 = simd_float4x4(
        SIMD4<Float>(1.0, 0.0, 0.0, 0.0),
        SIMD4<Float>(0.0, 1.0, 0.0, 0.0),
        SIMD4<Float>(0.0, 0.0, 1.0, 0.0),
        SIMD4<Float>(0.0, 0.0, 0.0, 1.0)
    )
    
    public static func translation(_ translation: SIMD3<Float>) -> simd_float4x4 {
        var matrix = identity4x4;
        matrix.columns.3.x = translation.x
        matrix.columns.3.y = translation.y;
        matrix.columns.3.z = translation.z;
        return matrix;
    }
    
    public static func reverseTranslate(apply translate: SIMD3<Float>, to inputMatrix: simd_float4x4) -> simd_float4x4 {
        // Flip translation because everything is reverse in the camera.
        // i.e. If you want to move the cube down it is like moving the camera up
        let translationMatrix = translation(translate * -1.0)
        let translatedPosition = (translationMatrix * inputMatrix)
        return translatedPosition
    }
    
    public static func reflectedTranslate(apply translate: SIMD3<Float>, to inputMatrix: simd_float4x4) -> simd_float4x4 {
        let translationMatrix = translation(translate) * -1.0
        let translatedPosition = (translationMatrix * inputMatrix)
        return translatedPosition
    }
    
    public static func rotationX(_ angle: Float) -> simd_float4x4 {
        var matrix = identity4x4
        let cosAngle = cos(angle)
        let sinAngle = sin(angle)
        matrix.columns.1.y = cosAngle
        matrix.columns.1.z = sinAngle
        matrix.columns.2.y = -sinAngle
        matrix.columns.2.z = cosAngle
        return matrix
    }
    
    public static func rotationY(_ angle: Float) -> simd_float4x4 {
        var matrix = identity4x4
        let cosAngle = cos(angle)
        let sinAngle = sin(angle)
        matrix.columns.0.x = cosAngle
        matrix.columns.0.z = -sinAngle
        matrix.columns.2.x = sinAngle
        matrix.columns.2.z = cosAngle
        return matrix
    }
    
    public static func rotationZ(_ angle: Float) -> simd_float4x4 {
        var matrix = identity4x4
        let cosAngle = cos(angle)
        let sinAngle = sin(angle)
        matrix.columns.0.x = cosAngle
        matrix.columns.0.y = sinAngle
        matrix.columns.1.x = -sinAngle
        matrix.columns.1.y = cosAngle
        return matrix
    }
    
    public static func rotation(_ angle: SIMD3<Float>) -> simd_float4x4 {
        let rotX = rotationX(angle.x)
        let rotY = rotationY(angle.y)
        let rotZ = rotationZ(angle.z)
        return rotZ * rotY * rotX
    }
    
    /// Creates a Transform matrix for scaling. This does not apply to scaling SDF implicit surfaces
    /// - Parameter scale: Scaling vector - an individual scale for each dimension
    public static func scaling(scale: SIMD3<Float>) -> simd_float4x4 {
        var matrix = identity4x4
        matrix.columns.0.x = scale.x
        matrix.columns.1.y = scale.y
        matrix.columns.2.z = scale.z
        return matrix
    }
}
