package com.jens.automation2.receivers;

import android.app.IntentService;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.ActivityRecognition;
import com.google.android.gms.location.ActivityRecognitionApi;
import com.google.android.gms.location.ActivityRecognitionResult;
import com.google.android.gms.location.DetectedActivity;
import com.jens.automation2.ActivityDisplayLongMessage;
import com.jens.automation2.ActivityPermissions;
import com.jens.automation2.AutomationService;
import com.jens.automation2.Miscellaneous;
import com.jens.automation2.R;
import com.jens.automation2.Rule;
import com.jens.automation2.Settings;
import com.jens.automation2.Trigger.Trigger_Enum;

import java.util.ArrayList;
import java.util.Date;

// See also: http://developer.android.com/reference/com/google/android/gms/location/ActivityRecognitionClient.html
// https://www.sitepoint.com/google-play-services-location-activity-recognition/

public class ActivityDetectionReceiver extends IntentService implements AutomationListenerInterface, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener
{
	protected static ActivityRecognitionApi activityRecognitionClient = null;
	protected static boolean connected = false;
	protected static enum LastRequestEnum { start, stop, restart };
	protected static LastRequestEnum lastRequest = null;
	protected static GoogleApiClient googleApiClient = null;
	protected static ActivityRecognitionResult activityDetectionLastResult = null;
	protected static long lastUpdate = 0;
	protected static Date currentTime;
	protected static ActivityDetectionReceiver instance = null;

	protected static ActivityDetectionReceiver getInstance()
	{
		if(instance == null)
			instance = new ActivityDetectionReceiver();

		return instance;
	}

	protected static boolean activityDetectionReceiverRunning = false;
	protected static ActivityDetectionReceiver activityDetectionReceiverInstance = null;

	public static boolean isActivityDetectionReceiverRunning()
	{
		return activityDetectionReceiverRunning;
	}

	public static ActivityRecognitionResult getActivityDetectionLastResult()
	{
		return activityDetectionLastResult;
	}

	public static GoogleApiClient getApiClient()
	{
		if(googleApiClient == null)
		{
			googleApiClient = new GoogleApiClient.Builder(AutomationService.getInstance())
					.addConnectionCallbacks(getInstance())
					.addOnConnectionFailedListener(getInstance())
					.addApi(ActivityRecognition.API)
					.build();
		}

		return googleApiClient;
	}

	private static void requestUpdates()
	{
		long frequency = Settings.activityDetectionFrequency * 1000;
		Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Requesting ActivityDetection updates with frequency " + String.valueOf(frequency) + " milliseconds.", 4);


		ActivityRecognition.ActivityRecognitionApi.requestActivityUpdates(getApiClient(), 1000, getInstance().getActivityDetectionPendingIntent());
	}
	private void reloadUpdates()
	{
		long frequency = Settings.activityDetectionFrequency * 1000;
		Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Re-requesting ActivityDetection updates with frequency " + String.valueOf(frequency) + " milliseconds.", 4);

		activityRecognitionClient.removeActivityUpdates(getApiClient(), getInstance().getActivityDetectionPendingIntent());
		try
		{
			Thread.sleep(1000);
		}
		catch (InterruptedException e)
		{
			Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Error reloading updates for ActivityDetectionReceiver: " + Log.getStackTraceString(e), 5);
		}

		ActivityRecognition.ActivityRecognitionApi.requestActivityUpdates(getApiClient(), frequency, getInstance().getActivityDetectionPendingIntent());
	}

	private static void stopUpdates()
	{
		Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Unsubscribing from ActivityDetection-updates.", 4);

		ActivityRecognition.ActivityRecognitionApi.removeActivityUpdates(getApiClient(), getInstance().getActivityDetectionPendingIntent());
//		activityRecognitionClient.removeActivityUpdates(getApiClient(), getInstance().getActivityDetectionPendingIntent());
//		activityRecognitionClient.disconnect();
	}
	public static void startActivityDetectionReceiver()
	{
		if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
		{
			if(!ActivityPermissions.havePermission("android.permission.ACTIVITY_RECOGNITION", Miscellaneous.getAnyContext()))
			{
				Miscellaneous.logEvent("w", "Activity Detection", "Don't have android.permission.ACTIVITY_RECOGNITION. Aborting receiver start..", 2);
				return;
			}
		}
		try
		{
			Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Starting ActivityDetectionReceiver", 3);

			if(activityDetectionReceiverInstance == null)
				activityDetectionReceiverInstance = new ActivityDetectionReceiver();

			if(!activityDetectionReceiverRunning && Rule.isAnyRuleUsing(Trigger_Enum.activityDetection))
			{
				if(isPlayServiceAvailable())
				{
					/*if(activityRecognitionClient == null)
						activityRecognitionClient = new ActivityRecognitionClient(Miscellaneous.getAnyContext(), activityDetectionReceiverInstance, activityDetectionReceiverInstance);*/

					lastRequest = LastRequestEnum.start;

					if(!connected)
						getApiClient().connect();
					else
						requestUpdates();

					activityDetectionReceiverRunning = true;
				}
			}
		}
		catch(Exception ex)
		{
			Miscellaneous.logEvent("e", "ActivityDetectionReceiver", "Error starting ActivityDetectionReceiver: " + Log.getStackTraceString(ex), 3);
		}
	}

	public static void restartActivityDetectionReceiver()
	{
		try
		{
			if(!activityDetectionReceiverRunning && Rule.isAnyRuleUsing(Trigger_Enum.activityDetection))
			{
				Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Restarting ActivityDetectionReceiver", 3);

				if(activityDetectionReceiverInstance == null)
					activityDetectionReceiverInstance = new ActivityDetectionReceiver();

				if(isPlayServiceAvailable())
				{
//					if(activityRecognitionClient == null)
//						activityRecognitionClient = new ActivityRecognitionClient(Miscellaneous.getAnyContext(), activityDetectionReceiverInstance, activityDetectionReceiverInstance);

					lastRequest = LastRequestEnum.restart;

					if(!connected)
						getApiClient().connect();
					else
						requestUpdates();
				}
			}
		}
		catch(Exception ex)
		{
			Miscellaneous.logEvent("e", "ActivityDetectionReceiver", "Error starting ActivityDetectionReceiver: " + Log.getStackTraceString(ex), 3);
		}

	}

	public static void stopActivityDetectionReceiver()
	{
		try
		{
			if(activityDetectionReceiverRunning)
			{
				Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Stopping ActivityDetectionReceiver", 3);

				if(isPlayServiceAvailable())
				{
					lastRequest = LastRequestEnum.stop;

					if(!connected)
						getApiClient().connect();
					else
						stopUpdates();

					activityDetectionReceiverRunning = false;
				}
			}
		}
		catch(Exception ex)
		{
			Miscellaneous.logEvent("e", "ActivityDetectionReceiver", "Error stopping ActivityDetectionReceiver: " + Log.getStackTraceString(ex), 3);
		}
	}

	public static boolean isPlayServiceAvailable()
	{
		if(GooglePlayServicesUtil.isGooglePlayServicesAvailable(Miscellaneous.getAnyContext()) == ConnectionResult.SUCCESS)
			return true;
		else
			return false;
	}


	@Override
	public void onConnectionFailed(ConnectionResult arg0)
	{
		Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Connection to Play Services failed.", 4);
		if(connected && getApiClient().isConnected())
		{
			connected = false;
		}
	}

	@Override
	public void onConnected(Bundle arg0)
	{
		Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Connected to Play Services.", 4);

		connected = true;

		if(lastRequest == null)
		{
			Miscellaneous.logEvent("w", "ActivityDetectionReceiver", "Request type not specified. Start or stop listening to activity detection updates?", 4);
			return;
		}

		if(lastRequest.equals(LastRequestEnum.start))
			requestUpdates();
		else if(lastRequest.equals(LastRequestEnum.stop))
			stopUpdates();
		else //reload, e.g. to set a new update time
			reloadUpdates();
	}

	@Override
	public void onConnectionSuspended(int arg0)
	{
		Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Connection to Play Services suspended.", 4);
//		activityRecognitionClient.disconnect();
		connected = false;
	}

	public ActivityDetectionReceiver()
	{
		super("ActivityDetectionIntentService");
		if(instance == null)
			instance = this;
	}

	@Override
	protected void onHandleIntent(Intent intent)
	{
		Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "onHandleIntent(): Received some status.", 5);

		try
		{
			if(isActivityDetectionReceiverRunning())
			{
				currentTime = new Date();

				if(lastUpdate == 0 | currentTime.getTime() >= lastUpdate + Settings.activityDetectionFrequency * 1000 - 1000) // -1000 to include updates only marginaly below the threshold
				{
					lastUpdate = currentTime.getTime();

					if(ActivityRecognitionResult.hasResult(intent))
					{
						activityDetectionLastResult = ActivityRecognitionResult.extractResult(intent);

						for(DetectedActivity activity : activityDetectionLastResult.getProbableActivities())
						{
							int loglevel = 3;
							if(activity.getConfidence() < Settings.activityDetectionRequiredProbability)
								loglevel = 4;

							Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Detected activity (probability " + String.valueOf(activity.getConfidence()) + "%): " + getDescription(activity.getType()), loglevel);
						}

						/*
						 * Returns the list of activities that where detected with the confidence value associated with each activity.
						 * The activities are sorted by most probable activity first.
			 			 * The sum of the confidences of all detected activities this method returns does not have to be <= 100
			 			 * since some activities are not mutually exclusive (for example, you can be walking while in a bus)
			 			 * and some activities are hierarchical (ON_FOOT is a generalization of WALKING and RUNNING).
						*/

						ArrayList<Rule> allRulesWithActivityDetection = Rule.findRuleCandidates(Trigger_Enum.activityDetection);
						for(int i=0; i<allRulesWithActivityDetection.size(); i++)
						{
							if(allRulesWithActivityDetection.get(i).applies(Miscellaneous.getAnyContext()))
								allRulesWithActivityDetection.get(i).activate(AutomationService.getInstance(), false);
						}
					}
				}
				else
					Miscellaneous.logEvent("w", "ActivityDetectionReceiver", String.format(getResources().getString(R.string.ignoringActivityDetectionUpdateTooSoon), String.valueOf(Settings.activityDetectionFrequency)), 5);
			}
			else
				Miscellaneous.logEvent("w", "ActivityDetectionReceiver", "I am not running. I shouldn't be getting updates. Ignoring it.", 5);
		}
		catch(Exception e)
		{
			// some error, don't care.
			Miscellaneous.logEvent("e", "ActivityDetectionReceiver", "onHandleIntent(): Error while receiving status: " + Log.getStackTraceString(e), 4);
		}
	}

	public static int[] getAllTypes()
	{
		return new int[] {
				DetectedActivity.IN_VEHICLE,
				DetectedActivity.ON_BICYCLE,
				DetectedActivity.ON_FOOT,
				DetectedActivity.STILL,
				DetectedActivity.TILTING,
				DetectedActivity.WALKING,
				DetectedActivity.RUNNING,
				DetectedActivity.UNKNOWN
		};
	}

	public static String getDescription(int type)
	{
		switch(type)
		{
			case(DetectedActivity.IN_VEHICLE):
				return Miscellaneous.getAnyContext().getResources().getString(R.string.detectedActivityInVehicle);
			case(DetectedActivity.ON_BICYCLE):
				return Miscellaneous.getAnyContext().getResources().getString(R.string.detectedActivityOnBicycle);
			case(DetectedActivity.ON_FOOT):
				return Miscellaneous.getAnyContext().getResources().getString(R.string.detectedActivityOnFoot);
			case(DetectedActivity.STILL):
				return Miscellaneous.getAnyContext().getResources().getString(R.string.detectedActivityStill);
			case(DetectedActivity.TILTING):
				return Miscellaneous.getAnyContext().getResources().getString(R.string.detectedActivityTilting);
			case(DetectedActivity.WALKING):
				return Miscellaneous.getAnyContext().getResources().getString(R.string.detectedActivityWalking);
			case(DetectedActivity.RUNNING):
				return Miscellaneous.getAnyContext().getResources().getString(R.string.detectedActivityRunning);
			case(DetectedActivity.UNKNOWN):
				return Miscellaneous.getAnyContext().getResources().getString(R.string.detectedActivityUnknown);
			default:
				return Miscellaneous.getAnyContext().getResources().getString(R.string.detectedActivityInvalidStatus);
		}
	}

	public static String[] getAllDescriptions()
	{
		ArrayList<String> types = new ArrayList<String>();

		for(int type : getAllTypes())
			types.add(getDescription(type));

		return types.toArray(new String[types.size()]);
	}

	@Override
	public void startListener(AutomationService automationService)
	{
		ActivityDetectionReceiver.startActivityDetectionReceiver();
	}

	@Override
	public void stopListener(AutomationService automationService)
	{
		ActivityDetectionReceiver.stopActivityDetectionReceiver();
	}

	public static boolean haveAllPermission()
	{
		return ActivityPermissions.havePermission("com.google.android.gms.permission.ACTIVITY_RECOGNITION", Miscellaneous.getAnyContext());
	}

	@Override
	public boolean isListenerRunning()
	{
		return ActivityDetectionReceiver.isActivityDetectionReceiverRunning();
	}

	@Override
	public Trigger_Enum[] getMonitoredTrigger()
	{
		return new Trigger_Enum[] { Trigger_Enum.activityDetection };
	}

	private PendingIntent getActivityDetectionPendingIntent()
	{
		Intent intent = new Intent(AutomationService.getInstance(), ActivityDetectionReceiver.class);
		PendingIntent returnValue = PendingIntent.getService(AutomationService.getInstance(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
		return returnValue;
	}
}