package com.aplusinformatique.aplusmanager.Services;

import android.Manifest;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
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.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;

import androidx.annotation.RequiresApi;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;

import com.aplusinformatique.aplusmanager.R;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.LocationSettingsRequest;
import com.google.android.gms.location.SettingsClient;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class DebutGeolocalisationV2 extends Service {

    private static final String TAG = "distrimobile_GeolocV2";

    //The desired interval for location updates. Inexact. Updates may be more or less frequent.
    private static final long UPDATE_INTERVAL_IN_MILLISECONDS = 10000;

    //The fastest rate for active location updates. Exact. Updates will never be more frequent
    //than this value.
    private static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = UPDATE_INTERVAL_IN_MILLISECONDS / 2;
    //Broadcast envoi géoposition
    private final String actionEnvoiPosition = "com.aplusinformatique.aplusmanager.geolocalisationreponsev2";
    //Broadcast receiver heartbeat
    private final String actionHeartBeat = "com.aplusinformatique.aplusmanager.heartbeat";
    private final String actionAccuseHeartBeat = "com.aplusinformatique.aplusmanager.accuseheartbeat";
    //Broadcast receiver Fin Service
    private final String actionFinService = "com.aplusinformatique.aplusmanager.fingeolocalisationv2";
    private final int ID_NOTIFICATION = 1002;
    Handler h = new Handler();
    Runnable r;
    //Provides access to the Fused Location Provider API.
    private FusedLocationProviderClient mFusedLocationClient;
    //Provides access to the Location Settings API.
    private SettingsClient mSettingsClient;
    //Stores parameters for requests to the FusedLocationProviderApi.
    private LocationRequest mLocationRequest;
    //Stores the types of location services the client is interested in using. Used for checking
    //settings to determine if the device has optimal location settings.
    private LocationSettingsRequest mLocationSettingsRequest;
    //Callback for Location events.
    private LocationCallback mLocationCallback;
    //Represents a geographical location.
    private Location mCurrentLocation;
    private Location mLastLocation = null;
    //Tracks the status of the location updates request. Value changes when the user presses the
    //Start Updates and Stop Updates buttons.
    private Boolean mRequestingLocationUpdates;
    //Time when the location was updated represented as a String.
    private String mLastUpdateTime;
    //Heartbeat
    private boolean DeclencherFinService = false;
    private int accuseHeartBeat = 0;
    private BroadcastReceiver receiverAccuseReception;
    private BroadcastReceiver receiverFinService;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);

        Log.i(TAG, "onStartCommand DebutGeolocalisationV2");

        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        String channelId = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? createNotificationChannel(notificationManager) : "";
        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, channelId);
        Notification notification = notificationBuilder.setOngoing(true)
                .setSmallIcon(R.drawable.ic_notification)
                .setContentTitle("Géolocalisation V2")
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setCategory(NotificationCompat.CATEGORY_SERVICE)
                .build();

        startForeground(ID_NOTIFICATION, notification);

        IntentFilter filterFinService = new IntentFilter();
        filterFinService.addAction(actionFinService);

        receiverFinService = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                FinService();
            }
        };
        registerReceiver(receiverFinService, filterFinService);

        mRequestingLocationUpdates = false;
        mLastUpdateTime = "";

        IntentFilter filter = new IntentFilter();
        filter.addAction(actionAccuseHeartBeat);

        receiverAccuseReception = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                accuseHeartBeat = 0;
            }
        };
        registerReceiver(receiverAccuseReception, filter);

        mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
        mSettingsClient = LocationServices.getSettingsClient(this);

        // Kick off the process of building the LocationCallback, LocationRequest, and
        // LocationSettingsRequest objects.
        createLocationCallback();
        createLocationRequest();
        buildLocationSettingsRequest();

        startLocationUpdates();

        //Si l'application Distrimobile ne réponds pas aux intent, on tue le service
        //Contrôle toutes les 5 secondes
        h.postDelayed(r = new Runnable() {
            public void run() {

                envoiHeartBeat();

                if (mRequestingLocationUpdates == false) {
                    startLocationUpdates();
                } else {
                    envoiPosition();
                }

                if (DeclencherFinService) {
                    FinService();
                } else {
                    h.postDelayed(this, 5 * 1000);
                }
            }
        }, 5 * 1000);

        return START_STICKY;
    }

    /**
     * Sets up the location request. Android has two location request settings:
     * {@code ACCESS_COARSE_LOCATION} and {@code ACCESS_FINE_LOCATION}. These settings control
     * the accuracy of the current location. This sample uses ACCESS_FINE_LOCATION, as defined in
     * the AndroidManifest.xml.
     * <p/>
     * When the ACCESS_FINE_LOCATION setting is specified, combined with a fast update
     * interval (5 seconds), the Fused Location Provider API returns location updates that are
     * accurate to within a few feet.
     * <p/>
     * These settings are appropriate for mapping applications that show real-time location
     * updates.
     */
    private void createLocationRequest() {
        mLocationRequest = new LocationRequest();

        // Sets the desired interval for active location updates. This interval is
        // inexact. You may not receive updates at all if no location sources are available, or
        // you may receive them slower than requested. You may also receive updates faster than
        // requested if other applications are requesting location at a faster interval.
        mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);

        // Sets the fastest rate for active location updates. This interval is exact, and your
        // application will never receive updates faster than this value.
        mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);

        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    }

    /**
     * Creates a callback for receiving location events.
     */
    private void createLocationCallback() {
        mLocationCallback = new LocationCallback() {
            @Override
            public void onLocationResult(LocationResult locationResult) {
                super.onLocationResult(locationResult);

                mCurrentLocation = locationResult.getLastLocation();
                Date currentTime = Calendar.getInstance().getTime();
                SimpleDateFormat mySimpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
                mLastUpdateTime = mySimpleDateFormat.format(currentTime);
            }
        };
    }

    /**
     * Uses a {@link com.google.android.gms.location.LocationSettingsRequest.Builder} to build
     * a {@link com.google.android.gms.location.LocationSettingsRequest} that is used for checking
     * if a device has the needed location settings.
     */
    private void buildLocationSettingsRequest() {
        LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
        builder.addLocationRequest(mLocationRequest);
        mLocationSettingsRequest = builder.build();
    }

    /**
     * Requests location updates from the FusedLocationApi. Note: we don't call this unless location
     * runtime permission has been granted.
     */
    private void startLocationUpdates() {

        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            Log.i(TAG, "DebutGeolocalisationV2 Settings ok.");
            mFusedLocationClient.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.myLooper());
            mRequestingLocationUpdates = true;
        } else {
            Log.i(TAG, "DebutGeolocalisationV2 Settings invalide.");
            mRequestingLocationUpdates = false;
        }

    }

    /**
     * Removes location updates from the FusedLocationApi.
     */
    private void stopLocationUpdates() {
        if (!mRequestingLocationUpdates) {
            return;
        }

        // It is a good practice to remove location requests when the activity is in a paused or
        // stopped state. Doing so helps battery performance and is especially
        // recommended in applications that request frequent location updates.
        mFusedLocationClient.removeLocationUpdates(mLocationCallback);
        mRequestingLocationUpdates = false;
    }

    private void envoiPosition() {

        Log.d(TAG, "envoiPosition DebutGeolocalisationV2");

        if (mCurrentLocation != null && mCurrentLocation.getLatitude() != 0 && mCurrentLocation.getLongitude() != 0) {

            if (mLastLocation == null || mLastLocation != mCurrentLocation) {

                mLastLocation = mCurrentLocation;

                Log.d(TAG, "position gps latitude : date : " + mLastUpdateTime + " " + mCurrentLocation.getLatitude() + " longitude : " + mCurrentLocation.getLongitude() + " precision : " + mCurrentLocation.getAccuracy() + " provider : " + mCurrentLocation.getProvider());

                Intent replyIntent = new Intent();
                replyIntent.setAction(actionEnvoiPosition);
                replyIntent.putExtra("latitude", String.valueOf(mCurrentLocation.getLatitude()));
                replyIntent.putExtra("longitude", String.valueOf(mCurrentLocation.getLongitude()));
                replyIntent.putExtra("speed", String.valueOf(mCurrentLocation.getSpeed()));
                replyIntent.putExtra("accuracy", String.valueOf(mCurrentLocation.getAccuracy()));
                replyIntent.putExtra("date", mLastUpdateTime);
                sendBroadcast(replyIntent);
            }
        }
    }

    private void envoiHeartBeat() {

        Log.d(TAG, "envoiHeartBeat DebutGeolocalisationV2");

        Intent replyIntent = new Intent();
        replyIntent.setAction(actionHeartBeat);
        sendBroadcast(replyIntent);

        accuseHeartBeat += 1;
        Log.d(TAG, "DebutGeolocalisationV2 compteur accusé heartbeat : " + String.valueOf(accuseHeartBeat));
        if (accuseHeartBeat > 4) {
            Log.d(TAG, "DebutGeolocalisationV2 accusés sans réponses");
            DeclencherFinService = true;
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        Log.d(TAG, "onDestroy DebutGeolocalisationV2");

        stopLocationUpdates();

        if (h != null) {
            h.removeCallbacks(r);
        }

        if (receiverAccuseReception != null) {
            unregisterReceiver(receiverAccuseReception);
            receiverAccuseReception = null;
        }

        if (receiverFinService != null) {
            unregisterReceiver(receiverFinService);
            receiverFinService = null;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @RequiresApi(Build.VERSION_CODES.O)
    private String createNotificationChannel(NotificationManager notificationManager) {
        String channelId = TAG;
        String channelName = "Géolocalisation V2";
        NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_LOW);
        // omitted the LED color
        channel.setImportance(NotificationManager.IMPORTANCE_LOW);
        channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
        notificationManager.createNotificationChannel(channel);
        return channelId;
    }

    private void FinService() {
        stopForeground(true);
        stopSelf();
    }
}
