package com.jens.automation2.location;

import android.content.Context;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.telephony.CellLocation;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.widget.Toast;

import com.jens.automation2.Action;
import com.jens.automation2.ActivityPermissions;
import com.jens.automation2.AutomationService;
import com.jens.automation2.Miscellaneous;
import com.jens.automation2.PointOfInterest;
import com.jens.automation2.R;
import com.jens.automation2.Rule;
import com.jens.automation2.Settings;
import com.jens.automation2.receivers.ConnectivityReceiver;

import java.util.Date;

public class CellLocationChangedReceiver extends PhoneStateListener
{
	private LocationManager myLocationManager;
	private Location currentLocation;
	public MyLocationListener myLocationListener = new MyLocationListener();
	public Boolean locationListenerArmed = false;
	public Date lastCellLocationUpdate;
	protected static boolean followUpdate = true;
	protected static TimeoutHandler timeoutHandler = null;
	protected static boolean timeoutHandlerActive = false;
	protected static boolean cellLocationListenerActive = false;
	protected static CellLocationChangedReceiver instance;
	protected static TelephonyManager telephonyManager;
	
	public static boolean isCellLocationListenerActive()
	{
		return cellLocationListenerActive;
	}

	protected static CellLocationChangedReceiver getInstance()
	{
		if(instance == null)
			instance = new CellLocationChangedReceiver();
		
		return instance;
	}

	@Override
	public void onCellLocationChanged(CellLocation location)
	{
		super.onCellLocationChanged(location);
		
		if(Settings.useAccelerometerForPositioning)
			SensorActivity.startAccelerometerTimer();
		
		if(followUpdate)
		{
			Date currentDate = new Date();
			
			Miscellaneous.logEvent("i", "CellLocation", String.format(Miscellaneous.getAnyContext().getResources().getString(R.string.cellMastChanged), location.toString()), 3);
			
			if(Settings.useAccelerometerForPositioning) //and last cell mast change longer than x minutes in the past
			{
				PointOfInterest possiblyActivePoi = PointOfInterest.getActivePoi();
				if(	possiblyActivePoi != null ) //if any poi is active
				{
					// Did the last activated rule activate wifi? Then we don't need accelerometer, we'll use wifiReceiver
					try
					{
						for(Action action : Rule.getLastActivatedRule().getActionSet())
						{
							if(action.getAction() == Action.Action_Enum.setWifi && action.getParameter1())
							{
								// we will be using wifiReceiver, deactivate AccelerometerTimer if applicable
								SensorActivity.stopAccelerometerTimer();
							}
						}					
					}
					catch(NullPointerException ne)
					{
						// Nothing to do, there is no last activated rule. Wifi hasn't been activated so we don't
						// deactivate accelerometer receiver.
					}
				}
				else
				{
					if(lastCellLocationUpdate == null)
						SensorActivity.startAccelerometerTimer();
					else
					{
						long timeSinceLastUpdate = currentDate.getTime() - lastCellLocationUpdate.getTime(); //in milliseconds
						if(timeSinceLastUpdate > Settings.useAccelerometerAfterIdleTime*60*1000)
						{
							SensorActivity.startAccelerometerTimer();
						}
						else
						{
							//reset timer
							SensorActivity.resetAccelerometerTimer();
						}
					}
				}
			}
			lastCellLocationUpdate = currentDate;
			
			myLocationManager = (LocationManager) AutomationService.getInstance().getSystemService(Context.LOCATION_SERVICE);
			currentLocation = getLocation("coarse");
			try
			{
				AutomationService.getInstance().getLocationProvider().setCurrentLocation(currentLocation, false);
			}
			catch(NullPointerException e)
			{
				Miscellaneous.logEvent("e", "LocationProvider", "Location provider is null: " + Log.getStackTraceString(e), 1);
			}
		}
		else
		{
			Miscellaneous.logEvent("i", "CellLocation", "Cell mast changed, but only initial update, ignoring this one.", 4);
			followUpdate = true; //for next run
		}
	}

	public static boolean isCellLocationChangedReceiverPossible()
	{
		if(telephonyManager == null)
			telephonyManager = (TelephonyManager) AutomationService.getInstance().getSystemService(Context.TELEPHONY_SERVICE);

		if(
			ConnectivityReceiver.isAirplaneMode(AutomationService.getInstance())
										||
			telephonyManager.getSimState() != TelephonyManager.SIM_STATE_READY
		)
			return false;
		else
			return true;
	}

	public Location getLocation(String accuracy)
	{
		Criteria crit = new Criteria();
		
		String myProviderName;
		
		// If privacy mode or no data connection available
		if(Settings.privacyLocationing || !ConnectivityReceiver.isDataConnectionAvailable(AutomationService.getInstance()))
		{
			Miscellaneous.logEvent("i", "CellLocation", Miscellaneous.getAnyContext().getResources().getString(R.string.enforcingGps), 4);
			myProviderName = LocationManager.GPS_PROVIDER;
		}
		else
		{
			Miscellaneous.logEvent("i", "CellLocation", Miscellaneous.getAnyContext().getResources().getString(R.string.notEnforcingGps), 4);
			
			if(accuracy.equals("coarse"))
			{
				crit.setPowerRequirement(Criteria.POWER_LOW);
				crit.setAltitudeRequired(false);
				crit.setSpeedRequired(false);
				crit.setBearingRequired(false);
				crit.setCostAllowed(false);
				crit.setAccuracy(Criteria.ACCURACY_COARSE);
			}
			else //equals "fine"
			{
				crit.setPowerRequirement(Criteria.POWER_LOW);
				crit.setAltitudeRequired(false);
				crit.setSpeedRequired(false);
				crit.setBearingRequired(false);
				//crit.setCostAllowed(false);
				crit.setAccuracy(Criteria.ACCURACY_FINE);
			}
			
			myProviderName = myLocationManager.getBestProvider(crit, true);
		}
		
		if(myProviderName == null)
		{
			Toast.makeText(Miscellaneous.getAnyContext(), "No suitable location provider could be used.", Toast.LENGTH_LONG).show();
			return null;
		}
		else
		{
			if(myLocationManager == null)
				myLocationManager = (LocationManager) AutomationService.getInstance().getSystemService(Context.LOCATION_SERVICE);

			if(!myLocationManager.isProviderEnabled(myProviderName))
			{
				if(myProviderName.equals(LocationManager.NETWORK_PROVIDER))
					myProviderName = LocationManager.GPS_PROVIDER;
			}

			if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
				myProviderName = LocationManager.GPS_PROVIDER;

			// Arm location updates
			if(!locationListenerArmed)
				startLocationListener(myProviderName);
			
			try
			{
				return myLocationManager.getLastKnownLocation(myProviderName);
			}
			catch(NullPointerException e)
			{
				Toast.makeText(Miscellaneous.getAnyContext(), "No last known location. Aborting...", Toast.LENGTH_LONG).show();
				return null;
			}
		}
	}
	
	private void startLocationListener(String providerToBeUsed)
	{		
		Miscellaneous.logEvent("i", "LocationListener", "Arming location listener, Provider " + providerToBeUsed, 4);
		myLocationManager.requestLocationUpdates(providerToBeUsed, Settings.minimumTimeBetweenUpdate, Settings.minimumDistanceChangeForNetworkUpdate, myLocationListener);
		locationListenerArmed = true;

		// (re)set timeout
		if(timeoutHandlerActive)
			stopTimeOutHandler();
		startTimeOutHandler();
	}
	private void stopLocationListener()
	{
		Miscellaneous.logEvent("i", "LocationListener", "Disarming location listener.", 4);
		myLocationManager.removeUpdates(myLocationListener);
		locationListenerArmed = false;

		if(timeoutHandlerActive)
			stopTimeOutHandler();
	}
	
	public Location getCurrentLocation()
	{
		return currentLocation;
	}

	public void setCurrentLocation(Location currentLocation)
	{
		this.currentLocation = currentLocation;
	}

	public class MyLocationListener implements LocationListener
	{
		@Override
		public void onLocationChanged(Location up2DateLocation)
		{	
			if(timeoutHandlerActive)
			{
				stopTimeOutHandler();
			}
			
			setCurrentLocation(up2DateLocation);
			AutomationService.getInstance().getLocationProvider().setCurrentLocation(up2DateLocation, false);
			// This is relevant if the program just started, knows where it is, but hasn't reached any POI.
			// The below PointOfInterest.positionUpdate() will not update the notification in that case.
//			if(!currentLocation.equals(up2DateLocation))
//				parentLocationProvider.parentService.updateNotification();
			
			if(up2DateLocation.getAccuracy() < Settings.satisfactoryAccuracyNetwork)
			{
				myLocationManager.removeUpdates(this);
				locationListenerArmed = false;
				Miscellaneous.logEvent("i", "LocationListener", "Disarmed location listener, accuracy reached", 4);
			}
		}
	
		@Override
		public void onProviderDisabled(String provider)
		{
			
		}
	
		@Override
		public void onProviderEnabled(String provider)
		{
			
		}
	
		@Override
		public void onStatusChanged(String provider, int status, Bundle extras)
		{
			
		}
	}
	
	class TimeoutHandler extends Handler
	{		
		@Override
		public void handleMessage(Message msg)
		{
			super.handleMessage(msg);
			
			if(msg.what == 1)
			{
				Context context = Miscellaneous.getAnyContext();
				Miscellaneous.logEvent("i", context.getResources().getString(R.string.gpsMeasurement), context.getResources().getString(R.string.gpsMeasurementTimeout), 4);
				stopLocationListener();
			}
		}		
	}
	
	private void startTimeOutHandler()
	{
		if(timeoutHandler == null)
			timeoutHandler = new TimeoutHandler();
		
		Message message = new Message();
		message.what = 1;
		timeoutHandler.sendMessageDelayed(message, Settings.gpsTimeout * 1000);
		timeoutHandlerActive = true;
	}
	private void stopTimeOutHandler()
	{
		if(timeoutHandler == null)
			timeoutHandler = new TimeoutHandler();
		
		timeoutHandler.removeMessages(1);
		timeoutHandlerActive = false;
	}	

	public static void startCellLocationChangedReceiver()
	{
		if(telephonyManager == null)
			telephonyManager = (TelephonyManager) AutomationService.getInstance().getSystemService(Context.TELEPHONY_SERVICE);

		try
		{
			if(!cellLocationListenerActive)
			{				
				if(!ConnectivityReceiver.isAirplaneMode(AutomationService.getInstance()) && telephonyManager.getSimState() == TelephonyManager.SIM_STATE_READY)
				{
					if(WifiBroadcastReceiver.mayCellLocationReceiverBeActivated())
					{
	//					if(!ConnectivityReceiver.isDataConnectionAvailable(parentService))
	//					{
							telephonyManager.listen(getInstance(), PhoneStateListener.LISTEN_CELL_LOCATION);
							cellLocationListenerActive = true;
							Miscellaneous.logEvent("i", "cellReceiver", "Starting cellLocationListener", 4);
							
							SensorActivity.stopAccelerometerTimer();
							SensorActivity.stopAccelerometerReceiver();
		//					this.stopWifiReceiver();

                         /*
                            We could now set a timer when we could activate a location check.
                            If that fires we need to check if maybe another location check has been performed.
                         */

                        if(!LocationProvider.speedTimerActive)
                            LocationProvider.startSpeedTimer(LocationProvider.getEtaAtNextPoi());
	//					}
	//					else
	//						Miscellaneous.logEvent("i", "cellReceiver", "Not starting cellLocationListener because we have no data connection.", 4);
					}
					else
						Miscellaneous.logEvent("w", "cellReceiver", "Wanted to activate CellLocationChangedReceiver, but Wifi-Receiver says not to.", 5);
				}
				else
					Miscellaneous.logEvent("i", "cellReceiver", "Not starting cellLocationListener because Airplane mode is active or SIM_STATE is not ready.", 5);
			}
		}
		catch(Exception ex)
		{
			Miscellaneous.logEvent("e", "Wifi Listener", "Error starting cellLocationListener: " + Log.getStackTraceString(ex), 3);
		}
	}
	
	public static void stopCellLocationChangedReceiver()
	{
		try
		{
			if(cellLocationListenerActive)
			{
				Miscellaneous.logEvent("i", "cellReceiver", "Stopping cellLocationListener", 4);
				
				getInstance().stopTimeOutHandler();
				getInstance().stopLocationListener();

				if(LocationProvider.speedTimerActive)
				    LocationProvider.stopSpeedTimer();
				
				telephonyManager.listen(instance, PhoneStateListener.LISTEN_NONE);
				cellLocationListenerActive = false;
				
				// May have comparison measurements active.
				PointOfInterest.stopRoutine();
			}
		}
		catch(Exception ex)
		{
			Miscellaneous.logEvent("e", "Wifi Listener", "Error stopping cellLocationListener: " + Log.getStackTraceString(ex), 3);
		}
	}

	public static void resetFollowUpdate()
	{
		followUpdate = false;
	}

	public static boolean haveAllPermission()
	{
		return ActivityPermissions.havePermission("android.permission.ACCESS_FINE_LOCATION", Miscellaneous.getAnyContext())
				&&
				ActivityPermissions.havePermission("android.permission.ACCESS_COARSE_LOCATION", Miscellaneous.getAnyContext())
				&&
				ActivityPermissions.havePermission("android.permission.ACCESS_NETWORK_STATE", Miscellaneous.getAnyContext())
				&&
				ActivityPermissions.havePermission("android.permission.INTERNET", Miscellaneous.getAnyContext())
				&&
				ActivityPermissions.havePermission("android.permission.ACCESS_WIFI_STATE", Miscellaneous.getAnyContext());
	}
}