//
//  AccelerateSolvePerfTest.swift
//  PerspectiveTransform
//
//  Created by Paul Zabelin on 2/23/16.
//  Copyright © 2016 CocoaPods. All rights reserved.
//

import XCTest
import Accelerate
@testable import PerspectiveTransform

@discardableResult
func solve(_ A: [Double], _ B: [Double] ) -> [Double] {
    var inMatrix: [Double]       = A
    var solution: [Double]       = B
    // Get the dimensions of the matrix. An NxN matrix has N^2
    // elements, so sqrt( N^2 ) will return N, the dimension
    var N: __CLPK_integer        = __CLPK_integer( sqrt( Double( A.count ) ) )
    // Number of columns on the RHS
    var NRHS: __CLPK_integer     = 1
    // Leading dimension of A and B
    var LDA: __CLPK_integer      = N
    var LDB: __CLPK_integer      = N
    // Initialize some arrays for the dgetrf_(), and dgetri_() functions
    var pivots: [__CLPK_integer] = [__CLPK_integer](repeating: 0, count: Int(N))
    var error: __CLPK_integer   = -1
    // Perform LU factorization
    dgetrf_(&LDA, &LDB, &inMatrix, &N, &pivots, &error)
    XCTAssertEqual(0, error, "should be successful")
    // Calculate solution from LU factorization
    _ = "T".withCString {
        // Solves a general system of linear equations, after factorization
        dgetrs_( UnsafeMutablePointer(mutating: $0), &N, &NRHS, &inMatrix, &LDA, &pivots, &solution, &LDB, &error )
    }
    XCTAssertEqual(0, error, "should be successful")
    return solution
}

class AccelerateSolvePerfTest: XCTestCase {
    static let reasonableTestDurationMs = 100
    var repeatTimes = AccelerateSolvePerfTest.reasonableTestDurationMs * 200

    var A: [Double]?
    var B: [Double]?

    func testSolveCorrectness() {
        A = [ 1, 1, 4, 6 ]
        B = [ 383, 2034 ]
        XCTAssertEqual([ 132.0, 251.0 ], solve(A!, B!))
    }

    func preparePerspective() {
        let start = Quadrilateral(CGRect(origin: CGPoint.zero, size: CGSize(width: 152, height: 122)))
        let destination = Quadrilateral(
            CGRect(
                origin: CGPoint(x: 100, y: 100),
                size: CGSize(width: 200, height: 200)
            )
        )
        print(start.corners)
        print(destination.corners)

        func row1(p1: CGPoint, p2: CGPoint) -> [Double] {
            return [p1.x, p1.y, 1, 0, 0, 0, -p2.x*p1.x, -p2.x*p1.y].map {Double($0)}
        }

        func row2(p1: CGPoint, p2: CGPoint) -> [Double] {
            return [0, 0, 0, p1.x, p1.y, 1, -p2.y*p1.x, -p2.y*p1.y].map {Double($0)}
        }

        //        let empty: [Double] = []
        A = stride(from: 0, through: 3, by: 1).map {
            row1(p1: start.corners[$0], p2: destination.corners[$0]) +
                row2(p1: start.corners[$0], p2: destination.corners[$0])
            }.reduce([], {$0! + $1})

        B = stride(from: 0, through: 3, by: 1).map {[destination.corners[$0].x, destination.corners[$0].y]}.reduce([], +).map {Double($0)}
    }

    func testSolvePerspectiveCorrectness() {
        preparePerspective()
        let solution = solve(A!, B!)
        let accuracy = 0.000000001
        XCTAssertEqual(solution[0], 1.31578947368421, accuracy: accuracy)
        XCTAssertEqual(solution[1], 0, accuracy: accuracy)
        XCTAssertEqual(solution[2], 100, accuracy: accuracy)
        XCTAssertEqual(solution[3], 0, accuracy: accuracy)
        XCTAssertEqual(solution[4], 1.63934426229508, accuracy: accuracy)
        XCTAssertEqual(solution[5], 100, accuracy: accuracy)
        XCTAssertEqual(solution[6], 0, accuracy: accuracy)
        XCTAssertEqual(solution[7], 0, accuracy: accuracy)
    }

    /** shows that accelerate is about 5-10 times slower then other methods
     compare repeatTimes with PerformanceTest.repeatTimes
     it's not surprising as it uses multiple iterations for approximate solution
     */
    func testSolvePerformance() {
        preparePerspective()
        measure {
            repeatTimes.times {
                solve( self.A!, self.B! )
            }
        }
    }
}
