package com.borismus.webintent;

import java.util.HashMap;
import java.util.Map;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;

import org.apache.cordova.CordovaActivity;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.text.Html;
import android.util.Log;
import android.support.v4.content.FileProvider;
import android.os.Build;

import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaResourceApi;
import org.apache.cordova.PluginResult;

/**
 * WebIntent is a PhoneGap plugin that bridges Android intents and web
 * applications:
 *
 * 1. Web apps can spawn intents that call native Android applications.
 * 2. After setting up correct intent filters for Cordova applications,
 *    Android intents can be handled by Cordova web applications.
 *
 * @author Boris Smus (boris@borismus.com)
 * @author Chris E. Kelley (chris@vetula.com)
 *
 */
public class WebIntent extends CordovaPlugin {
    private static final String LOG_TAG = "WebIntent";
    private static String installReferrer = null;
    private CallbackContext onNewIntentCallbackContext = null;

    /**
     * @return true iff if the action was executed successfully, else false.
     */
    @Override
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
        try {
            if ("startActivity".equals(action)) {
                if (args.length() != 1) {
                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.INVALID_ACTION));
                    return false;
                }
                // Parse the arguments
                final CordovaResourceApi resourceApi = webView.getResourceApi();
                JSONObject obj = args.getJSONObject(0);
                String type = obj.has("type") ? obj.getString("type") : null;
                Uri uri = obj.has("url") ? resourceApi.remapUri(Uri.parse(obj.getString("url"))) : null;
                JSONObject extras = obj.has("extras") ? obj.getJSONObject("extras") : null;
                Map<String, String> extrasMap = new HashMap<String, String>();

                // Populate the extras if any exist
                if (extras != null) {
                    JSONArray extraNames = extras.names();
                    for (int i = 0; i < extraNames.length(); i++) {
                        String key = extraNames.getString(i);
                        String value = extras.getString(key);
                        extrasMap.put(key, value);
                    }
                }

                startActivity(obj.getString("action"), uri, type, extrasMap);
                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK));
                return true;
            } else if ("hasExtra".equals(action)) {
                if (args.length() != 1) {
                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.INVALID_ACTION));
                    return false;
                }
                Intent i = ((CordovaActivity)this.cordova.getActivity()).getIntent();
                String extraName = args.getString(0);
                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, i.hasExtra(extraName)));
                return true;
            } else if ("getExtra".equals(action)) {
                if (args.length() != 1) {
                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.INVALID_ACTION));
                    return false;
                }
                Intent i = ((CordovaActivity)this.cordova.getActivity()).getIntent();
                String extraName = args.getString(0);
                if (i.hasExtra(extraName)) {
                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, i.getStringExtra(extraName)));
                    return true;
                } else {
                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR));
                    return false;
                }
            } else if ("getUri".equals(action)) {
                if (args.length() != 0) {
                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.INVALID_ACTION));
                    return false;
                }
                Intent i = ((CordovaActivity)this.cordova.getActivity()).getIntent();
                String uri = i.getDataString();
                if (uri == null && installReferrer != null) {
                    uri = installReferrer;  // App just installed, received play store referrer intent.
                    Log.i(LOG_TAG, String.format("URI is an install referrer: %s", installReferrer));
                }
                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, uri));
                return true;
            } else if ("onNewIntent".equals(action)) {
                // Save reference to the callback; will be called on "new intent" events.
                this.onNewIntentCallbackContext = callbackContext;

                if (args.length() != 0) {
                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.INVALID_ACTION));
                    return false;
                }

                PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT);
                result.setKeepCallback(true); // Reuse the callback on intent events.
                callbackContext.sendPluginResult(result);
                return true;
            } else if ("sendBroadcast".equals(action)) {
                if (args.length() != 1) {
                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.INVALID_ACTION));
                    return false;
                }

                // Parse the arguments
                JSONObject obj = args.getJSONObject(0);

                JSONObject extras = obj.has("extras") ? obj.getJSONObject("extras") : null;
                Map<String, String> extrasMap = new HashMap<String, String>();

                // Populate the extras if any exist
                if (extras != null) {
                    JSONArray extraNames = extras.names();
                    for (int i = 0; i < extraNames.length(); i++) {
                        String key = extraNames.getString(i);
                        String value = extras.getString(key);
                        extrasMap.put(key, value);
                    }
                }

                sendBroadcast(obj.getString("action"), extrasMap);
                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK));
                return true;
            }
            callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.INVALID_ACTION));
            return false;
        } catch (JSONException e) {
            final String errorMessage = e.getMessage();
            Log.e(LOG_TAG, errorMessage);
            callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, errorMessage));
            return false;
        }
    }

    @Override
    public void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        if (this.onNewIntentCallbackContext != null) {            
            //Kevin W: We only care about the Intent.EXTRA_STREAM
            Uri streamUri = (Uri)intent.getParcelableExtra(Intent.EXTRA_STREAM);
            if(streamUri!=null){
                PluginResult result = new PluginResult(PluginResult.Status.OK, streamUri.toString());
                result.setKeepCallback(true);
                this.onNewIntentCallbackContext.sendPluginResult(result);
            }
        }
    }

    void startActivity(String action, Uri uri, String type, Map<String, String> extras) {
        //Kevin W: Android 7.0 Nougat no longer allow file to be shared by file://
        //https://proandroiddev.com/sharing-files-though-intents-are-you-ready-for-nougat-70f7e9294a0b
        if(newApi() && uri.getScheme().equals("file")){
            try{
                File fileToShare =  new File(new URI(uri.toString()));
                Uri contentUri = FileProvider.getUriForFile(this.cordova.getActivity(),
                        cordova.getActivity().getPackageName() + ".provider",
                        fileToShare);
                
                Intent shareIntent = new Intent(action);
                shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
                shareIntent.setType(type);
                shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                shareIntent.setDataAndType(contentUri, type);
                
                ((CordovaActivity)this.cordova.getActivity()).startActivity(shareIntent);
                return;
            }catch(URISyntaxException e){
                final String errorMessage = e.getMessage();
                Log.e(LOG_TAG, errorMessage);
                this.onNewIntentCallbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, errorMessage));
            }
        }else{
            Intent i = uri != null ? new Intent(action, uri) : new Intent(action);
            if (type != null && uri != null) {
                i.setDataAndType(uri, type); //Fix the crash problem with android 2.3.6
            } else {
                if (type != null) {
                    i.setType(type);
                }
            }
    
            for (Map.Entry<String, String> entry : extras.entrySet()) {
                final String key = entry.getKey();
                final String value = entry.getValue();
                // If type is text/html, the extra text must be sent as HTML.
                if (key.equals(Intent.EXTRA_TEXT) && "text/html".equals(type)) {
                    i.putExtra(key, Html.fromHtml(value));
                } else if (key.equals(Intent.EXTRA_STREAM)) {
                    // Allows sharing of images as attachments.
                    // `value` in this case should be the URI of a file.
                    final CordovaResourceApi resourceApi = webView.getResourceApi();
                    i.putExtra(key, resourceApi.remapUri(Uri.parse(value)));
                } else if (key.equals(Intent.EXTRA_EMAIL)) {
                    // Allows adding the email address of the receiver.
                    i.putExtra(Intent.EXTRA_EMAIL, new String[] { value });
                } else {
                    i.putExtra(key, value);
                }
            }
            ((CordovaActivity)this.cordova.getActivity()).startActivity(i);
        }
    }

    void sendBroadcast(String action, Map<String, String> extras) {
        Intent intent = new Intent();
        intent.setAction(action);
        for (Map.Entry<String, String> entry : extras.entrySet()) {
            intent.putExtra(entry.getKey(), entry.getValue());
        }
        ((CordovaActivity)this.cordova.getActivity()).sendBroadcast(intent);
    }

    // Receiver that listens for com.android.vending.INSTALL_REFERRER, an intent sent by the
    // Play Store on installation when the referrer parameter of the install URL is populated:
    // https://play.google.com/store/apps/details?id=|APP_ID|&referrer=|REFERRER|
    public static class ReferralReceiver extends BroadcastReceiver {
        private static final String LOG_TAG = "ReferralReceiver";

        @Override
        public void onReceive(Context context, Intent intent) {
            // Store the install referrer so we can return it when getUri is called.
            installReferrer = intent.getStringExtra("referrer");
            Log.i(LOG_TAG, String.format("Install referrer: %s", installReferrer));
        }
    }
    
    private boolean newApi(){
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
    }
}
