package com.flybuy.cordova.location;

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.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.Manifest;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

import android.content.ServiceConnection;
import android.os.IBinder;
import android.content.ComponentName;

import com.google.android.gms.location.DetectedActivity;
import java.util.ArrayList;

public class BackgroundLocationServicesPlugin extends CordovaPlugin {
    private static final String TAG = "BackgroundLocationServicesPlugin";
    private static final String PLUGIN_VERSION = "1.0";

    public static final String ACTION_START = "start";
    public static final String ACTION_STOP = "stop";
    public static final String ACTION_CONFIGURE = "configure";
    public static final String ACTION_SET_CONFIG = "setConfig";
    public static final String ACTION_AGGRESSIVE_TRACKING = "startAggressiveTracking";
    public static final String ACTION_GET_VERSION = "getVersion";
    public static final String ACTION_REGISTER_FOR_LOCATION_UPDATES = "registerForLocationUpdates";
    public static final String ACTION_REGISTER_FOR_ACTIVITY_UPDATES = "registerForActivityUpdates";

    public static String APP_NAME = "";

    private Boolean isEnabled = false;
    private Boolean inBackground = false;
    private boolean isServiceBound = false;

    private String desiredAccuracy = "1000";

    private Intent updateServiceIntent;

    private String interval = "300000";
    private String fastestInterval = "60000";
    private String aggressiveInterval = "4000";
    private String activitiesInterval = "1000";

    private String distanceFilter = "30";
    private String isDebugging = "false";
    private String notificationTitle = "Location Tracking";
    private String notificationText = "ENABLED";
    private String stopOnTerminate = "false";
    private String useActivityDetection = "false";

    //Things I want to remove
    private String url;
    private String params;
    private String headers;

    private JSONArray fences = null;

    private CallbackContext locationUpdateCallback = null;
    private CallbackContext detectedActivitiesCallback = null;
    private CallbackContext startCallback = null;

    private BroadcastReceiver receiver = null;

    public static final int START_REQ_CODE = 0;
    public static final int PERMISSION_DENIED_ERROR = 20;
    protected final static String[] permissions = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION };

    // Used to (un)bind the service to with the activity
    private final ServiceConnection serviceConnection = new ServiceConnection() {
      @Override
      public void onServiceConnected(ComponentName name, IBinder binder) {
          // Nothing to do here
          Log.i(TAG, "SERVICE CONNECTED TO MAIN ACTIVITY");
      }

      @Override
      public void onServiceDisconnected(ComponentName name) {
        // Nothing to do here
        Log.i(TAG, "SERVICE DISCONNECTED");
      }
    };

    private BroadcastReceiver detectedActivitiesReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, final Intent intent) {

          cordova.getThreadPool().execute(new Runnable() {
              public void run() {
                Log.i(TAG, "Received Detected Activities");
                ArrayList<DetectedActivity> updatedActivities =
                intent.getParcelableArrayListExtra(Constants.ACTIVITY_EXTRA);

                JSONObject daJSON = new JSONObject();

                for(DetectedActivity da: updatedActivities) {
                  try {
                    daJSON.put(Constants.getActivityString(da.getType()), da.getConfidence());
                  } catch(JSONException e) {
                    Log.e(TAG, "Error putting JSON value" + e);
                  }
                }

                if(detectedActivitiesCallback != null) {
                  PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, daJSON);
                  pluginResult.setKeepCallback(true);
                  detectedActivitiesCallback.sendPluginResult(pluginResult);
                }
              }
          });
      }
    };

    private BroadcastReceiver locationUpdateReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, final Intent intent) {
            if(debug()) {
                Log.d(TAG, "Location Received, ready for callback");
            }
            if (locationUpdateCallback != null) {

                if(debug()) {
                  Toast.makeText(context, "We received a location update", Toast.LENGTH_SHORT).show();
                }

                final Bundle b = intent.getExtras();
                final String errorString = b.getString("error");

                cordova.getThreadPool().execute(new Runnable() {
                    public void run() {
                        PluginResult pluginResult;

                        if(b == null) {
                            String unkownError = "An Unkown Error has occurred, there was no Location attached";
                            pluginResult = new PluginResult(PluginResult.Status.ERROR, unkownError);

                        } else if(errorString != null) {
                            Log.d(TAG, "ERROR " + errorString);
                            pluginResult = new PluginResult(PluginResult.Status.ERROR, errorString);

                        } else {
                            JSONObject data = locationToJSON(intent.getExtras());
                            pluginResult = new PluginResult(PluginResult.Status.OK, data);
                        }

                        if(pluginResult != null) {
                            pluginResult.setKeepCallback(true);
                            locationUpdateCallback.sendPluginResult(pluginResult);
                        }
                    }
                });
            } else {
                if(debug()) {
                  Toast.makeText(context, "We received a location update but locationUpdate was null", Toast.LENGTH_SHORT).show();
                }
                Log.w(TAG, "WARNING LOCATION UPDATE CALLBACK IS NULL, PLEASE RUN REGISTER LOCATION UPDATES");
            }
        }
    };

    @Override
    public void initialize(CordovaInterface cordova, CordovaWebView webView) {
      super.initialize(cordova, webView);

      Activity activity = this.cordova.getActivity();

      APP_NAME = getApplicationName(activity);
      //Need To namespace these in case more than one app is running this bg plugin
      Constants.LOCATION_UPDATE = APP_NAME + Constants.LOCATION_UPDATE;
      Constants.DETECTED_ACTIVITY_UPDATE = APP_NAME + Constants.DETECTED_ACTIVITY_UPDATE;
    }

    public boolean execute(String action, JSONArray data, CallbackContext callbackContext) {

        Activity activity = this.cordova.getActivity();

        Boolean result = false;
        updateServiceIntent = new Intent(activity, BackgroundLocationUpdateService.class);

        if (ACTION_START.equalsIgnoreCase(action) && !isEnabled) {
              result = true;

              updateServiceIntent.putExtra("desiredAccuracy", desiredAccuracy);
              updateServiceIntent.putExtra("distanceFilter", distanceFilter);
              updateServiceIntent.putExtra("desiredAccuracy", desiredAccuracy);
              updateServiceIntent.putExtra("isDebugging", isDebugging);
              updateServiceIntent.putExtra("notificationTitle", notificationTitle);
              updateServiceIntent.putExtra("notificationText", notificationText);
              updateServiceIntent.putExtra("interval", interval);
              updateServiceIntent.putExtra("fastestInterval", fastestInterval);
              updateServiceIntent.putExtra("aggressiveInterval", aggressiveInterval);
              updateServiceIntent.putExtra("activitiesInterval", activitiesInterval);
              updateServiceIntent.putExtra("useActivityDetection", useActivityDetection);

            if (hasPermisssion()) {
                isServiceBound = bindServiceToWebview(activity, updateServiceIntent);
                isEnabled = true;
                callbackContext.success();
            } else {
                startCallback = callbackContext;
                PermissionHelper.requestPermissions(this, START_REQ_CODE, permissions);
            }

        } else if (ACTION_STOP.equalsIgnoreCase(action)) {
            isEnabled = false;
            result = true;
            activity.stopService(updateServiceIntent);
            callbackContext.success();

            result = unbindServiceFromWebview(activity, updateServiceIntent);

            if(result) {
                callbackContext.success();
            } else {
                callbackContext.error("Failed To Stop The Service");
            }
        } else if (ACTION_CONFIGURE.equalsIgnoreCase(action)) {
            result = true;
            try {
                // [distanceFilter, desiredAccuracy, interval, fastestInterval, aggressiveInterval, debug, notificationTitle, notificationText, activityType, fences, url, params, headers]
                //  0               1                2         3                4                   5      6                   7                8              9
                this.distanceFilter = data.getString(0);
                this.desiredAccuracy = data.getString(1);
                this.interval = data.getString(2);
                this.fastestInterval = data.getString(3);
                this.aggressiveInterval = data.getString(4);
                this.isDebugging = data.getString(5);
                this.notificationTitle = data.getString(6);
                this.notificationText = data.getString(7);
                //this.activityType = data.getString(8);
                this.useActivityDetection = data.getString(9);
                this.activitiesInterval = data.getString(10);



            } catch (JSONException e) {
                Log.d(TAG, "Json Exception" + e);
                callbackContext.error("JSON Exception" + e.getMessage());
            }
        } else if (ACTION_SET_CONFIG.equalsIgnoreCase(action)) {
            result = true;
            // TODO reconfigure Service
            callbackContext.success();
        } else if(ACTION_GET_VERSION.equalsIgnoreCase(action)) {
            result = true;
            callbackContext.success(PLUGIN_VERSION);
        } else if(ACTION_REGISTER_FOR_LOCATION_UPDATES.equalsIgnoreCase(action)) {
            result = true;
            //Register the function for repeated location update
            locationUpdateCallback = callbackContext;
        } else if(ACTION_REGISTER_FOR_ACTIVITY_UPDATES.equalsIgnoreCase(action)) {
          result = true;
          detectedActivitiesCallback = callbackContext;
        } else if(ACTION_AGGRESSIVE_TRACKING.equalsIgnoreCase(action)) {
            result = true;
            if(isEnabled) {
                this.cordova.getActivity().sendBroadcast(new Intent(Constants.CHANGE_AGGRESSIVE));
                callbackContext.success();
            } else {
                callbackContext.error("Tracking not enabled, need to start tracking before starting aggressive tracking");
            }
        }

        return result;
    }

    public String getApplicationName(Context context) {
      return context.getApplicationInfo().loadLabel(context.getPackageManager()).toString();
    }

    public Boolean debug() {
        if(Boolean.parseBoolean(isDebugging)) {
            return true;
        } else {
            return false;
        }
    }

    private Boolean bindServiceToWebview(Context context, Intent intent) {
      Boolean didBind = false;

      try {
        context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
        context.startService(intent);

        webView.getContext().registerReceiver(locationUpdateReceiver, new IntentFilter(Constants.CALLBACK_LOCATION_UPDATE));
        webView.getContext().registerReceiver(detectedActivitiesReceiver, new IntentFilter(Constants.CALLBACK_ACTIVITY_UPDATE));

        didBind = true;
      } catch(Exception e) {
        Log.e(TAG, "ERROR BINDING SERVICE" + e);
      }

      return didBind;
    }

    private Boolean unbindServiceFromWebview(Context context, Intent intent) {
        Boolean didUnbind = false;

      try {
        context.unbindService(serviceConnection);
        context.stopService(intent);

        webView.getContext().unregisterReceiver(locationUpdateReceiver);
        webView.getContext().unregisterReceiver(detectedActivitiesReceiver);

        didUnbind = true;
      } catch(Exception e) {
        Log.e(TAG, "ERROR UNBINDING SERVICE" + e);
      }

      return didUnbind;
    }

    @Override
    public void onPause(boolean multitasking) {
        if(debug()) {
            Log.d(TAG, "- locationUpdateReceiver Paused (starting recording = " + String.valueOf(isEnabled) + ")");
        }
        if (isEnabled) {
            Activity activity = this.cordova.getActivity();
            activity.sendBroadcast(new Intent(Constants.START_RECORDING));
        }
    }

    @Override
    public void onResume(boolean multitasking) {
        if(debug()) {
            Log.d(TAG, "- locationUpdateReceiver Resumed (stopping recording)" + String.valueOf(isEnabled));
        }
        if (isEnabled) {
            Activity activity = this.cordova.getActivity();
            activity.sendBroadcast(new Intent(Constants.STOP_RECORDING));
        }
    }


    private void destroyLocationUpdateReceiver() {
        if (this.receiver != null) {
            try {
                webView.getContext().unregisterReceiver(this.receiver);
                this.receiver = null;
            } catch (IllegalArgumentException e) {
                Log.e(TAG, "Error unregistering location receiver: ", e);
            }
        }
    }

    private JSONObject locationToJSON(Bundle b) {
        JSONObject data = new JSONObject();
        try {
            data.put("latitude", b.getDouble("latitude"));
            data.put("longitude", b.getDouble("longitude"));
            data.put("accuracy", b.getDouble("accuracy"));
            data.put("altitude", b.getDouble("altitude"));
            data.put("timestamp", b.getDouble("timestamp"));
            data.put("speed", b.getDouble("speed"));
            data.put("heading", b.getDouble("heading"));
        } catch(JSONException e) {
            Log.d(TAG, "ERROR CREATING JSON" + e);
        }

        return data;
    }

    public boolean hasPermisssion() {
        for(String p : permissions)
        {
            if(!PermissionHelper.hasPermission(this, p))
            {
                return false;
            }
        }
        return true;
    }


    public void onRequestPermissionResult(int requestCode, String[] permissions,
                                          int[] grantResults) throws JSONException {
        for (int r : grantResults) {
            if (r == PackageManager.PERMISSION_DENIED) {
                Log.d(TAG, "Permission Denied!");
                PluginResult result = new PluginResult(PluginResult.Status.ERROR, PERMISSION_DENIED_ERROR);

                if(this.startCallback != null) {
                    startCallback.sendPluginResult(result);
                }
                return;
            }
        }
        switch (requestCode) {
            case START_REQ_CODE:
                isServiceBound = bindServiceToWebview(cordova.getActivity(), updateServiceIntent);
                isEnabled = true;
                break;
        }
    }


    /**
     * Override method in CordovaPlugin.
     * Checks to see if it should turn off
     */
    public void onDestroy() {
        Activity activity = this.cordova.getActivity();

        if(isEnabled) {
            activity.stopService(updateServiceIntent);
            unbindServiceFromWebview(activity, updateServiceIntent);
        }
    }
}