package br.com.minhavia.minhavia.service;

import android.Manifest;
import android.app.Notification;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.util.Log;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

import br.com.minhavia.minhavia.dao.PavimentoIrregularDatabase;
import br.com.minhavia.minhavia.model.PavimentoIrregular;
import br.com.minhavia.minhavia.util.Constantes;


public class PavimentoIrregularBackgroundService extends Service implements SensorEventListener, LocationListener {

    private static final String TAG = "MV-PavimentoBackground";
    public static String str_receiver = "servicetutorial.service.receiver";


    private Context context;
    private Intent intent;
    private Handler mHandler = new Handler();
    private Timer mTimer = null;
    private TimerTaskToGetLocation timerTaskToGetLocation = new TimerTaskToGetLocation();

    private LocationManager locationManager;
    private SensorManager sensorManager;
    private Sensor sensor;

    // Arrays to store gravity and linear acceleration values
    private float[] mGravity;
    private float[] mLinearAcceleration;
    private float[] mLinearAccelerationLast;
    // Start time for the shake detection
    private long tempoInicialAcelerometro;

    //GPS
    private long timeGPS;
    private long tempoInicialGPS;

    //Tempo parado no mesmo lugar por 1 minuto e 35 segundos
    private static final long TEMPO_PARADO_GPS = 95000;
    //Velocidade mínima de 10km/h para identificar que houve movimento com o veículo.
    private static final double VELOCIDADE_MINIMA_MOVIMENTO = 2.78;
    //Threshold
    private static final double THRESHOLD = 0.738054425;
    // Maximum time (in milliseconds) for the whole shake to occur
    private static final int MAX_SHAKE_DURATION = 350;
    //Value alpha
    private static final float ALPHA = 0.9f;

    // Indexes for x, y, and z values
    private static final int INDICE_ACELEROMETRO_X = 0;
    private static final int INDICE_ACELEROMETRO_Y = 1;
    private static final int INDICE_ACELEROMETRO_Z = 2;


    private PavimentoIrregularDatabase db;


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(1, new Notification());

        locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            return;
        }
        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);

        sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mGravity = new float[]{0.0f, 0.0f, 0.0f};
        mLinearAcceleration = new float[]{0.0f, 0.0f, 0.0f};
        mLinearAccelerationLast = new float[]{0.0f, 0.0f, 0.0f};

        db = PavimentoIrregularDatabase.getDATABASE(PavimentoIrregularBackgroundService.this);

        mTimer = new Timer();
        mTimer.schedule(new TimerTaskToGetLocation(), 5, Constantes.TIMER_REPEAT_LOCATION);
        intent = new Intent(str_receiver);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand()");
        this.context = this;
        timerTaskToGetLocation.doStop();
        timerTaskToGetLocation.cancel();
        timerTaskToGetLocation.setExecutandoThread(false);
        resetShakeDetection();
        timerTaskToGetLocation.doRun();
        return START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        stopLocationUpdates();
        Log.e(TAG, "onDestroy()");
        if (mTimer != null) {
            mTimer.cancel();
        }
        resetShakeDetection();
        encerrarServico();
    }

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        Log.e(TAG, "onTaskRemoved()");
        stopLocationUpdates();
        encerrarServico();
        resetShakeDetection();
        super.onTaskRemoved(rootIntent);
    }


    @Override
    public void onLocationChanged(Location location) {

        Log.i(TAG, "onLocationChanged()");
        long tmpInit = System.currentTimeMillis();
        String latitude = String.valueOf(location.getLatitude());
        String longitude = String.valueOf(location.getLongitude());
        timeGPS = location.getTime();
        float velocidade = location.getSpeed();
        Log.e("Latitude", ""+latitude);
        Log.e("Longitude", ""+longitude);
        if (tempoInicialGPS == 0l && velocidade <= VELOCIDADE_MINIMA_MOVIMENTO) {
            tempoInicialGPS = timeGPS;
        } else {
            if (velocidade > VELOCIDADE_MINIMA_MOVIMENTO) {
                tempoInicialGPS = 0l;
            }
        }
        if (velocidade <= VELOCIDADE_MINIMA_MOVIMENTO && tempoInicialGPS != 0l && (timeGPS > (tempoInicialGPS + TEMPO_PARADO_GPS))) {
            stopLocationUpdates();
        } else {
            if (velocidade > VELOCIDADE_MINIMA_MOVIMENTO && existeIrregularidade()) {
                insertPavimentoIrregular(latitude, longitude, velocidade, true);
            } else {
                if(velocidade > VELOCIDADE_MINIMA_MOVIMENTO){
                    insertPavimentoIrregular(latitude, longitude, velocidade, false);
                }
                resetShakeDetection();
            }
        }

        Log.e(TAG, "TEMPO onLocationChanged: "+(System.currentTimeMillis() - tmpInit));


    }

    private void insertPavimentoIrregular(String latitude, String longitude, float velocidade, boolean irregular) {
        PavimentoIrregular pavimentoIrregular = new PavimentoIrregular();
        pavimentoIrregular.setLatitude(latitude);
        pavimentoIrregular.setLongitude(longitude);
        pavimentoIrregular.setVelocidade(velocidade);
        pavimentoIrregular.setTimestamp(new Date().getTime());
        pavimentoIrregular.setIrregular(irregular);
        db.pavimentoIrregularDAO().insert(pavimentoIrregular);
    }

    private void setAceleracaoCorrente(SensorEvent event) {
        /*
         *  BEGIN SECTION from Android developer site. This code accounts for
         *  gravity using a high-pass filter
         */

        // alpha is calculated as t / (t + dT)
        // with t, the low-pass filter's time-constant
        // and dT, the event delivery rate

        // Gravity components of x, y, and z acceleration
        mGravity[INDICE_ACELEROMETRO_X] = ALPHA * mGravity[INDICE_ACELEROMETRO_X] + (1 - ALPHA) * event.values[INDICE_ACELEROMETRO_X];
        mGravity[INDICE_ACELEROMETRO_Y] = ALPHA * mGravity[INDICE_ACELEROMETRO_Y] + (1 - ALPHA) * event.values[INDICE_ACELEROMETRO_Y];
        mGravity[INDICE_ACELEROMETRO_Z] = ALPHA * mGravity[INDICE_ACELEROMETRO_Z] + (1 - ALPHA) * event.values[INDICE_ACELEROMETRO_Z];


        // Linear acceleration along the x, y, and z axes (gravity effects removed)
        mLinearAccelerationLast = mLinearAcceleration.clone();
        mLinearAcceleration[INDICE_ACELEROMETRO_X] = event.values[INDICE_ACELEROMETRO_X] - mGravity[INDICE_ACELEROMETRO_X];
        mLinearAcceleration[INDICE_ACELEROMETRO_Y] = event.values[INDICE_ACELEROMETRO_Y] - mGravity[INDICE_ACELEROMETRO_Y];
        mLinearAcceleration[INDICE_ACELEROMETRO_Z] = event.values[INDICE_ACELEROMETRO_Z] - mGravity[INDICE_ACELEROMETRO_Z];

        //Log.i("Aceleração", "X: "+mLinearAcceleration[INDICE_ACELEROMETRO_X]+" Y: "+mLinearAcceleration[INDICE_ACELEROMETRO_Y]+" Z: "+mLinearAcceleration[INDICE_ACELEROMETRO_Z]);
        /*
         *  END SECTION from Android developer site
         */
    }

    @Override
    public void onStatusChanged(String s, int i, Bundle bundle) {

    }

    @Override
    public void onProviderEnabled(String s) {
        Log.e(TAG, "onProviderEnabled()");

        timerTaskToGetLocation.doStop();
        timerTaskToGetLocation.cancel();
        timerTaskToGetLocation.setExecutandoThread(false);
        resetShakeDetection();
        timerTaskToGetLocation.doRun();
    }

    @Override
    public void onProviderDisabled(String s) {
        Log.e(TAG, "onProviderDisabled()");
        timerTaskToGetLocation.doStop();
        timerTaskToGetLocation.cancel();
        timerTaskToGetLocation.setExecutandoThread(false);
        resetShakeDetection();
        encerrarServico();
    }

    @Override
    public void onLowMemory() {
        Log.e(TAG, "onLowMemory()");
        timerTaskToGetLocation.doStop();
        timerTaskToGetLocation.cancel();
        timerTaskToGetLocation.setExecutandoThread(false);
        resetShakeDetection();
        stopLocationUpdates();
        encerrarServico();
        super.onLowMemory();

    }

    @Override
    public void onSensorChanged(SensorEvent sensorEvent) {
        Log.e(TAG, "onSensorChanged()");
        setAceleracaoCorrente(sensorEvent);
        long tempoAgora = System.currentTimeMillis();
        // Set the startTime if it was reset to zero
        if (tempoInicialAcelerometro == 0) {
            tempoInicialAcelerometro = tempoAgora;
        }
        // Check if we're still in the shake window we defined
        if (tempoAgora - tempoInicialAcelerometro > MAX_SHAKE_DURATION) {
            // Too much time has passed. Start over!
            resetShakeDetection();
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int i) {

    }



    protected void startLocationUpdates() {
        if (locationManager == null) {
            locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                return;
            }
            locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
        }
        if (sensor != null) {
            sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);
        }
    }

    protected void stopLocationUpdates() {
        if (locationManager != null) {
            locationManager.removeUpdates(this);
            sensorManager.unregisterListener(this);
        }
        encerrarServico();
    }


    private boolean existeIrregularidade() {
        return existeIrregularidadePorEixo(INDICE_ACELEROMETRO_X) || existeIrregularidadePorEixo(INDICE_ACELEROMETRO_Y) || existeIrregularidadePorEixo(INDICE_ACELEROMETRO_Z);

    }

    private boolean existeIrregularidadePorEixo(int indiceEixo) {
        float variacao = 0.0f;
        variacao = mLinearAcceleration[indiceEixo] - mLinearAccelerationLast[indiceEixo];
        return variacao >= THRESHOLD;
    }

    private void resetShakeDetection() {
        tempoInicialAcelerometro = 0;
        mLinearAccelerationLast = new float[]{0.0f, 0.0f, 0.0f};
    }

    private void encerrarServico() {
        if(mTimer != null) {
            mTimer.cancel();
        }
        timerTaskToGetLocation.doStop();
        timerTaskToGetLocation.cancel();
        timerTaskToGetLocation.setExecutandoThread(false);
        stopSelf();
    }

    protected class TimerTaskToGetLocation extends TimerTask {

        private boolean doStop = false;
        private boolean executandoThread = false;

        public synchronized void doStop() {
            this.doStop = true;
        }

        public synchronized void doRun() {
            this.doStop = false;
        }

        private synchronized void setExecutandoThread(boolean executandoThread){
            this.executandoThread = executandoThread;
        }

        private synchronized boolean getExecutandoThread(){
            return this.executandoThread;
        }

        private synchronized boolean keepRunning() {
            return this.doStop == false;
        }
        @Override
        public void run() {
            while(keepRunning() && !getExecutandoThread()) {
                setExecutandoThread(true);
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.e(TAG, "TimerTaskToGetLocation()");
                        startLocationUpdates();
                    }
                });
            }
        }
    }
}