package br.com.oniq.geocoder;

import com.getcapacitor.JSArray;
import com.getcapacitor.JSObject;
import com.getcapacitor.NativePlugin;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;

import android.Manifest;
import android.content.Context;
import android.location.Address;
import android.location.Geocoder;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.os.Build;

import org.json.JSONArray;
import java.util.List;
import java.util.Locale;

@NativePlugin(
        permissions = {
                Manifest.permission.INTERNET,
                Manifest.permission.ACCESS_NETWORK_STATE
        }
)
public class NativeGeocoder extends Plugin {

    @PluginMethod
    public void reverseGeocode(PluginCall call) {
        try {
            Double latitude = call.getDouble("latitude");
            Double longitude = call.getDouble("longitude");

            if(latitude == null || longitude == null) {
                call.reject("Latitude or Longitude must be provided");
                return;
            }

            if(!this.isGeocodePresent()) {
                call.reject("Geocode is not present on this device");
                return;
            }

            if(!this.isConnected()) {
                call.reject("No internet connection");
                return;
            }

            GeocodeOptions geocodeOptions = getOptions(call.getObject("options"));

            Geocoder geocode = new Geocoder(this.getContext(), geocodeOptions.getLocale());

            List<Address> addresses = geocode.getFromLocation(latitude, longitude, geocodeOptions.getMaxResults());

            call.resolve(this.getResultObject(addresses));
        } catch (Exception e) {
            call.reject(e.getMessage());
        }
    }

    @PluginMethod
    public void forwardGeocode(PluginCall call) {
        try {
            if (!call.getData().has("address")) {
                call.reject("Address string must not be null");
                return;
            }

            if(!this.isGeocodePresent()) {
                call.reject("Geocode is not present on this device");
                return;
            }

            if(!this.isConnected()) {
                call.reject("No internet connection");
                return;
            }

            String address = call.getString("address");

            GeocodeOptions geocodeOptions = getOptions(call.getObject("options"));

            Geocoder geocode = new Geocoder(this.getContext(), geocodeOptions.getLocale());

            List<Address> addresses = geocode.getFromLocationName(address, geocodeOptions.getMaxResults());

            call.resolve(this.getResultObject(addresses));
        } catch (Exception e) {
            call.reject(e.getMessage());
        }
    }

    private boolean isGeocodePresent() {
        return Geocoder.isPresent();
    }

    private GeocodeOptions getOptions(JSObject options) {
        GeocodeOptions geocodeOptions = new GeocodeOptions();
        if(options != null) {
            if(options.has("maxResults"))
                geocodeOptions.setMaxResults(options.getInteger("maxResults"));
            if(options.has("locale")) {
                Locale locale = Locale.forLanguageTag(options.getString("locale"));
                geocodeOptions.setLocale(locale);
            }
        }
        return geocodeOptions;
    }

    private JSObject getResultObject(List<Address> addresses) throws Exception {
        if(addresses.size() > 0) {
            JSArray results = new JSArray();
            for(Address address : addresses) {
                JSObject place = new JSObject();
                place.put("latitude", !String.valueOf(address.getLatitude()).isEmpty() ? address.getLatitude() : "");
                place.put("longitude", !String.valueOf(address.getLongitude()).isEmpty() ? address.getLongitude() : "");
                place.put("countryCode", address.getCountryCode() != null ? address.getCountryCode() : "");
                place.put("countryName", address.getCountryName() != null ? address.getCountryName() : "");
                place.put("postalCode", address.getPostalCode() != null ? address.getPostalCode() : "");
                place.put("administrativeArea", address.getAdminArea() != null ? address.getAdminArea() : "");
                place.put("subAdministrativeArea", address.getSubAdminArea() != null ? address.getSubAdminArea() : "");
                place.put("locality", address.getLocality() != null ? address.getLocality() : "");
                place.put("subLocality", address.getSubLocality() != null ? address.getSubLocality() : "");
                place.put("thoroughfare", address.getThoroughfare() != null ? address.getThoroughfare() : "");
                place.put("subThoroughfare", address.getSubThoroughfare() != null ? address.getSubThoroughfare() : "");
                place.put("areasOfInterest", address.getFeatureName() != null ? new JSONArray(new String[]{ address.getFeatureName()} ) : new JSONArray());

                results.put(place);
            }

            JSObject result = new JSObject();

            return result.put("result", results);
        } else {
            throw new Exception("No results founded");
        }
    }

    private boolean isConnected() {
        Context context = this.getContext();
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (cm != null) {
                NetworkCapabilities capabilities = cm.getNetworkCapabilities(cm.getActiveNetwork());
                return capabilities != null;
            }
        } else {
            if (cm != null) {
                NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
                return activeNetwork != null;
            }
        }
        return false;
    }
}
