/**
 * Ionic Deeplinks Plugin.
 * License: MIT
 *
 * Thanks to Eddy Verbruggen and nordnet for the great custom URl scheme and
 * unviversal links plugins this plugin was inspired by.
 *
 * https://github.com/EddyVerbruggen/Custom-URL-scheme
 * https://github.com/nordnet/cordova-universal-links-plugin
 */
package io.ionic.links;

import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginResult;
import org.apache.cordova.PluginResult.Status;
import org.json.JSONObject;
import org.json.JSONArray;
import org.json.JSONException;

import java.util.Date;
import java.util.TimeZone;

import android.util.Log;
import android.content.Intent;
import android.content.Context;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.inputmethod.InputMethodManager;
import android.telephony.TelephonyManager;
import android.net.Uri;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.provider.Settings;

import java.util.ArrayList;
import java.util.Set;
import java.util.Collection;
import java.util.Map;

public class IonicDeeplink extends CordovaPlugin {
  private static final String TAG = "IonicDeeplinkPlugin";

  private JSONObject lastEvent;

  private ArrayList<CallbackContext> _handlers = new ArrayList<CallbackContext>();

  public void initialize(CordovaInterface cordova, CordovaWebView webView) {
    super.initialize(cordova, webView);
    Log.d(TAG, "IonicDeepLinkPlugin: firing up...");

    handleIntent(cordova.getActivity().getIntent());
  }

  @Override
  public void onNewIntent(Intent intent) {
    handleIntent(intent);
  }

  public void handleIntent(Intent intent) {

    Uri referrer = cordova.getActivity().getReferrer();
    if (referrer != null && referrer.getHost() != null){
      if (referrer.getHost().equals("com.salesforce.chatter")) {
        Intent appIntent = new Intent(Intent.ACTION_VIEW);
        appIntent.setData(intent.getData());
        cordova.getActivity().finishAndRemoveTask();
        cordova.getActivity().startActivity(appIntent);
        return;
      }
    }
    final String intentString = intent.getDataString();

    if (intentString != null && intentString.contains("aosp_dtach=true")) {
      Intent appIntent = new Intent(Intent.ACTION_VIEW);
      String dataString = intent.getDataString();
      dataString = dataString.replace("aosp_dtach=true", "aosp_dtach=false");
      appIntent.setData(Uri.parse(dataString));
      appIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
      cordova.getActivity().finishAndRemoveTask();
      cordova.getActivity().startActivity(appIntent);
      return;
    }

    // read intent
    String action = intent.getAction();
    Uri url = intent.getData();
    JSONObject bundleData = this._bundleToJson(intent.getExtras());
    Log.d(TAG, "Got a new intent: " + intentString + " " + intent.getScheme() + " " + action + " " + url);

    // if app was not launched by the url - ignore
    if (!Intent.ACTION_VIEW.equals(action) || url == null) {
      return;
    }

    // store message and try to consume it
    try {
      lastEvent = new JSONObject();
      lastEvent.put("url", url.toString());
      lastEvent.put("path", url.getPath());
      lastEvent.put("queryString", url.getQuery());
      lastEvent.put("scheme", url.getScheme());
      lastEvent.put("host", url.getHost());
      lastEvent.put("fragment", url.getFragment());
      lastEvent.put("extra", bundleData);
      consumeEvents();
    } catch(JSONException ex) {
      Log.e(TAG, "Unable to process URL scheme deeplink", ex);
    }
  }

  public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
    if(action.equals("onDeepLink")) {
      addHandler(args, callbackContext);
    } else if(action.equals("canOpenApp")) {
      String uri = args.getString(0);
      canOpenApp(uri, callbackContext);
    } else if(action.equals("getHardwareInfo")) {
      getHardwareInfo(args, callbackContext);
    }
    return true;
  }

  /**
   * Try to consume any waiting intent events by sending them to our plugin
   * handlers. We will only do this if we have active handlers so the message isn't lost.
   */
  private void consumeEvents() {
    if(this._handlers.size() == 0 || lastEvent == null) {
      return;
    }

    for(CallbackContext callback : this._handlers) {
      sendToJs(lastEvent, callback);
    }
    lastEvent = null;
  }

  private void sendToJs(JSONObject event, CallbackContext callback) {
    final PluginResult result = new PluginResult(PluginResult.Status.OK, event);
    result.setKeepCallback(true);
    callback.sendPluginResult(result);
  }

  private void addHandler(JSONArray args, final CallbackContext callbackContext) {
    this._handlers.add(callbackContext);
    this.consumeEvents();
  }

  private JSONObject _bundleToJson(Bundle bundle) {
    if(bundle == null) {
      return new JSONObject();
    }

    JSONObject j = new JSONObject();
    Set<String> keys = bundle.keySet();
    for(String key : keys) {
      try {
        Class<?> jsonClass = j.getClass();
        Class[] cArg = new Class[1];
        cArg[0] = String.class;
        //Workaround for API < 19
        try{
          if(jsonClass.getDeclaredMethod("wrap", cArg) != null){
            j.put(key, JSONObject.wrap(bundle.get(key)));
          }
        }
        catch(NoSuchMethodException e) {
          j.put(key, this._wrap(bundle.get(key)));
        }
      } catch(JSONException ex) {}
    }

    return j;
  }
  //Wrap method not available in JSONObject API < 19
  private Object _wrap(Object o){
    if (o == null) {
      return null;
    }
    if (o instanceof JSONArray || o instanceof JSONObject) {
      return o;
    }
    if (o.equals(null)) {
      return o;
    }
    try {
      if (o instanceof Collection) {
        return new JSONArray((Collection) o);
      } else if (o.getClass().isArray()) {
        return new JSONArray(o);
      }
      if (o instanceof Map) {
        return new JSONObject((Map) o);
      }
      if (o instanceof Boolean ||
          o instanceof Byte ||
          o instanceof Character ||
          o instanceof Double ||
          o instanceof Float ||
          o instanceof Integer ||
          o instanceof Long ||
          o instanceof Short ||
          o instanceof String) {
        return o;
      }
      if (o.getClass().getPackage().getName().startsWith("java.")) {
        return o.toString();
      }
    } catch (Exception ignored) {}
    return null;
  }

  /**
   * Check if we can open an app with a given URI scheme.
   *
   * Thanks to https://github.com/ohh2ahh/AppAvailability/blob/master/src/android/AppAvailability.java
   */
  private void canOpenApp(String uri, final CallbackContext callbackContext) {
    Context ctx = this.cordova.getActivity().getApplicationContext();
    final PackageManager pm = ctx.getPackageManager();

    try {
      pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES);
      callbackContext.success();
    } catch(PackageManager.NameNotFoundException e) {}

    callbackContext.error("");
  }

  private void getHardwareInfo(JSONArray args, final CallbackContext callbackContext) {
    String uuid = Settings.Secure.getString(this.cordova.getActivity().getContentResolver(), android.provider.Settings.Secure.ANDROID_ID);

    JSONObject j = new JSONObject();
    try {
      j.put("uuid", uuid);
      j.put("platform", this.getPlatform());
      j.put("tz", this.getTimeZoneID());
      j.put("tz_offset", this.getTimeZoneOffset());
      j.put("os_version", this.getOSVersion());
      j.put("sdk_version", this.getSDKVersion());
    } catch(JSONException ex) {}

    final PluginResult result = new PluginResult(PluginResult.Status.OK, j);
    callbackContext.sendPluginResult(result);
  }

  private boolean isAmazonDevice() {
    if (android.os.Build.MANUFACTURER.equals("Amazon")) {
      return true;
    }
    return false;
  }
  private String getTimeZoneID() {
    TimeZone tz = TimeZone.getDefault();
    return (tz.getID());
  }

  private int getTimeZoneOffset() {
    TimeZone tz = TimeZone.getDefault();
    return tz.getOffset(new Date().getTime()) / 1000 / 60;
  }

  private String getSDKVersion() {
    @SuppressWarnings("deprecation")
    String sdkversion = android.os.Build.VERSION.SDK;
    return sdkversion;
  }
  private String getOSVersion() {
    String osversion = android.os.Build.VERSION.RELEASE;
    return osversion;
  }
  private String getPlatform() {
    String platform;
    if (isAmazonDevice()) {
      platform = "amazon-fireos";
    } else {
      platform = "android";
    }
    return platform;
  }
}
