import Foundation
import CoreLocation

/// A `CLLocationManager` subclass allowing mocking in tests.
final class MockCLLocationManager: CLLocationManager {

    private var _heading: CLHeading?
    override var heading: CLHeading? { _heading }

    private var _location: CLLocation?
    override var location: CLLocation? { _location }

    override class func locationServicesEnabled() -> Bool { true }

    private var _authorizationStatus: CLAuthorizationStatus = .authorizedAlways
    override var authorizationStatus: CLAuthorizationStatus {
        return _authorizationStatus
    }

    override func startUpdatingLocation() {
        isUpdatingLocation = true
    }

    override func stopUpdatingLocation() {
        isUpdatingLocation = false
    }

    override func startUpdatingHeading() {
        isUpdatingHeading = true
    }

    override func stopUpdatingHeading() {
        isUpdatingHeading = false
    }

    override func requestWhenInUseAuthorization() {
        isRequestedForAuthorization = true
    }

    // Empty overrides preventing the real interaction with the superclass.
    override func requestAlwaysAuthorization() { }
    override func startMonitoringSignificantLocationChanges() { }
    override func stopMonitoringSignificantLocationChanges() { }

    // MARK: - Test properties

    var isUpdatingLocation: Bool = false
    var isUpdatingHeading: Bool = false
    var isRequestedForAuthorization: Bool?

    /// Simulates a new location being emitted. Updates the `location` property
    /// and notifies the delegate.
    ///
    /// - Parameter location: The new location used.
    ///
    func simulateUpdate(location: CLLocation) {
        _location = location
        delegate?.locationManager?(self, didUpdateLocations: [location])
    }


    /// Simulates a new heading being emitted. Updates the `heading` property
    /// and notifies the delegate.
    ///
    /// - Parameter heading: The new heading used.
    ///
    func simulateUpdate(heading: CLHeading) {
        _heading = heading
        delegate?.locationManager?(self, didUpdateHeading: heading)
    }

    /// Simulates the location manager failing with an error. Notifies the delegate with
    /// the provided error.
    ///
    /// - Parameter error: The error used for the delegate callback.
    ///
    func simulate(error: Error) {
        delegate?.locationManager?(self, didFailWithError: error)
    }

    /// Updates the `authorizationStatus` value and notifies the delegate.
    ///
    /// - Important: ⚠️ `authorizationStatus` is a static variable. Calling
    /// the `simulate(authorizationStatus:)` function will change the value for all
    /// instances of `MockCLLocationManager`. The `didChangeAuthorization` delegate
    /// method is called only on the delegate of this instance.
    ///
    /// - Parameter authorizationStatus: The new authorization status.
    ///
    func simulate(authorizationStatus: CLAuthorizationStatus) {
        _authorizationStatus = authorizationStatus
        delegate?.locationManagerDidChangeAuthorization?(self)
    }   
}
