import Foundation
import Capacitor
import CoreLocation

/**
 * Please read the Capacitor iOS Plugin Development Guide
 * here: https://capacitorjs.com/docs/plugins/ios
 */
struct NativeGeocoderOptions: Decodable {
    var locale: Locale = Locale.current
    var maxResults: Int = 1
}

@objc(NativeGeocoder)
public class NativeGeocoder: CAPPlugin {
    
    @objc func forwardGeocode(_ call: CAPPluginCall) {
        guard let address = call.getString("address", nil) else {
            call.reject("Address string must be provided")
            return
        }
        
        if self.isGeocoderBusy(call) {
            return
        }
        
        let options = self.getOptions(call.getObject("options") ?? [:])
        
        CLGeocoder().geocodeAddressString(address, in: nil, preferredLocale: options.locale) {
            (places, error) in
            self.processResults(places, error, options.maxResults, call)
        }
    }
    
    @objc func reverseGeocode(_ call: CAPPluginCall) {
        guard let longitude = call.getDouble("longitude") ?? nil, let latitude = call.getDouble("latitude") ?? nil else {
            call.reject("Latitude and Longitude must be provided")
            return
        }
        
        if self.isGeocoderBusy(call) {
            return
        }
        
        let options = self.getOptions(call.getObject("options") ?? [:])
        
        let location = self.buildCLLocation(latitude, longitude)

        CLGeocoder().reverseGeocodeLocation(location, preferredLocale: options.locale) { (places, error) in
            self.processResults(places, error, options.maxResults, call)
        }
    }
    
    private func buildCLLocation(_ latitude: Double, _ longitude: Double) -> CLLocation {
        return CLLocation.init(latitude: CLLocationDegrees.init(latitude), longitude: CLLocationDegrees.init(longitude))
    }
    
    private func getOptions(_ options: [String: Any]) -> NativeGeocoderOptions {
        var geocodeOptions = NativeGeocoderOptions()
        if let locale = options["locale"] as? String {
            geocodeOptions.locale = Locale.init(identifier: locale)
        }
        if let maxResults = options["maxResults"] as? Int {
            geocodeOptions.maxResults = maxResults
        }
        
        return geocodeOptions
    }
    
    private func processResults(_ places: [CLPlacemark]?, _ error: Error?, _ maxResults: Int, _ call: CAPPluginCall) {
        guard error == nil else {
            call.reject(error!.localizedDescription)
            return
        }
        if let places = places {
            var resultList = [[String:Any]]()
            let sizeResult = places.count >= maxResults ? maxResults : places.count
            for index in 0..<sizeResult {
                var latitude = ""
                if let lat = places[index].location?.coordinate.latitude {
                    latitude = "\(lat)"
                }
                var longitude = ""
                if let lon = places[index].location?.coordinate.longitude {
                    longitude = "\(lon)"
                }
                var place = [String:Any]()
                place["latitude"] = latitude
                place["longitude"] = longitude
                place["countryCode"] = places[index].isoCountryCode ?? ""
                place["countryName"] = places[index].country ?? ""
                place["postalCode"] = places[index].postalCode ?? ""
                place["administrativeArea"] = places[index].administrativeArea ?? ""
                place["subAdministrativeArea"] = places[index].subAdministrativeArea ?? ""
                place["locality"] = places[index].locality ?? ""
                place["subLocality"] = places[index].subLocality ?? ""
                place["thoroughfare"] = places[index].thoroughfare ?? ""
                place["subThoroughfare"] = places[index].subThoroughfare ?? ""
                place["areasOfInterest"] = places[index].areasOfInterest ?? []
                resultList.append(place)
            }
            call.resolve([
                "result": resultList
            ])
        } else {
            call.reject("Unable to find a place")
        }
    }
    
    private func isGeocoderBusy(_ call: CAPPluginCall) -> Bool {
        if CLGeocoder().isGeocoding {
            call.reject("Geocoder is busy, you can only make one call at a time")
            return true
        }
        return false
    }
}
