package org.thosp.yourlocalweather.service;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.Location;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.SystemClock;
import android.telephony.TelephonyManager;

import java.util.Calendar;
import java.util.List;

import static org.thosp.yourlocalweather.utils.LogToFile.appendLog;

public class NetworkLocationProvider extends Service {

    public static final String TAG = "NetworkLocationProvider";

    public enum NetworkLocationProviderActions {
        START_LOCATION_UPDATE, LOCATION_UPDATE_CELLS_ONLY
    }

    private final IBinder binder = new NetworkLocationProviderBinder();

    public TelephonyManager mTelephonyManager;
    WifiManager.WifiLock mWifiLock;
    private WifiManager wifiManager;
    private volatile boolean scanning;
    private static volatile Calendar nextScanningAllowedFrom;
    private volatile PendingIntent intentToCancel;
    private volatile Integer jobId;
    private AlarmManager alarmManager;

    private WifiScanCallback mWifiScanResults = new WifiScanCallback() {

        @Override
        public void onWifiResultsAvailable() {
            appendLog(getBaseContext(), TAG, "Wifi results are available now:", scanning);
            if (!scanning) {
                return;
            }
            nextScanningAllowedFrom = null;
            scanning = false;
            if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.M) {
                if (jobId != null) {
                    JobScheduler jobScheduler = getSystemService(JobScheduler.class);
                    jobScheduler.cancel(jobId);
                }
            } else {
                if (intentToCancel != null) {
                    alarmManager.cancel(intentToCancel);
                }
            }
            List<ScanResult> scans = null;
            try {
                appendLog(getBaseContext(), TAG, "Wifi results are available now - going to get wifi results");
                scans = wifiManager.getScanResults();
            } catch (Throwable exception) {
                appendLog(getBaseContext(), TAG, "Exception occured getting wifi results:", exception);
            }
            if (scans == null) {
                appendLog(getBaseContext(), TAG, "WifiManager.getScanResults returned null");
            }
            getLocationFromWifisAndCells(scans);
        }
    };

    /**
     * Receives location updates as well as wifi scan result updates
     */
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(final Context context, final Intent intent) {
            if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(intent.getAction())) {
                mWifiScanResults.onWifiResultsAvailable();
            }
        }
    };

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

    @Override
    public boolean onUnbind(Intent intent) {
        return false;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mTelephonyManager = ((TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE));
        alarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
        wifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);

        try {
            mWifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_SCAN_ONLY, "SCAN_LOCK");
            if (!mWifiLock.isHeld()) {
                mWifiLock.acquire();
            }
        } catch (UnsupportedOperationException uoe) {
            appendLog(getBaseContext(), TAG,
                    "Unable to acquire wifi lock.", uoe);
        }
        registerReceiver(mReceiver, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
    }

    @Override
    public void onDestroy() {
        unregisterReceiver(mReceiver);
        super.onDestroy();
    }

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

        if (intent == null) {
            return ret;
        }

        if (null != intent.getAction()) switch (intent.getAction()) {
            case "org.openbmap.unifiedNlp.LOCATION_UPDATE_CELLS_ONLY":
                startLocationUpdateCellsOnly();
                return ret;
            default:
                break;
        }
        return START_STICKY;
    }

    public void startLocationUpdateCellsOnly() {
        appendLog(getBaseContext(), TAG,
                "LOCATION_UPDATE_CELLS_ONLY:nextScanningAllowedFrom:",
                nextScanningAllowedFrom);
        if (nextScanningAllowedFrom == null) {
            return;
        }
        nextScanningAllowedFrom = null;
        scanning = false;
        getLocationFromWifisAndCells(null);
    }

    public void startLocationUpdate(Location inputLocation) {
        if (nextScanningAllowedFrom != null) {
            Calendar now = Calendar.getInstance();
            if (now.before(nextScanningAllowedFrom)) {
                return;
            }
        }
        if (inputLocation != null) {
            MozillaLocationService.getInstance(getBaseContext()).processUpdateOfLocation(getBaseContext(), inputLocation);
        } else {
            sendUpdateToLocationBackends();
        }
    }

    private void sendUpdateToLocationBackends() {
        appendLog(getBaseContext(), TAG,
                "update():nextScanningAllowedFrom:",
                nextScanningAllowedFrom);
        if(nextScanningAllowedFrom == null) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
                scanning = wifiManager.startScan();
            } else {
                scanning = true;
            }
            if (scanning) {
                nextScanningAllowedFrom = Calendar.getInstance();
                nextScanningAllowedFrom.add(Calendar.MINUTE, 15);
            }
        }
        if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.M) {
            ComponentName serviceComponent = new ComponentName(this, NetworkLocationCellsOnlyJob.class);
            JobInfo.Builder builder = new JobInfo.Builder(NetworkLocationCellsOnlyJob.JOB_ID, serviceComponent);
            builder.setMinimumLatency(8000); // wait at least
            builder.setOverrideDeadline(10000); // maximum delay
            JobInfo jobInfo = builder.build();
            jobId= jobInfo.getId();
            JobScheduler jobScheduler = getSystemService(JobScheduler.class);
            jobScheduler.schedule(jobInfo);
        } else {
            intentToCancel = getIntentToGetCellsOnly();
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                        SystemClock.elapsedRealtime() + 8000,
                        intentToCancel);
            } else {
                alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                        SystemClock.elapsedRealtime() + 8000,
                        intentToCancel);
            }
        }
        appendLog(getBaseContext(), TAG, "update():cells only task scheduled");
    }

    private void getLocationFromWifisAndCells(List<ScanResult> scans) {
        appendLog(getBaseContext(), TAG,
                "getLocationFromWifisAndCells(), scans=",
                        scans);
        MozillaLocationService.getInstance(getBaseContext()).getLocationFromCellsAndWifis(getBaseContext(),
                                                                          LocationNetworkSourcesService.getInstance().getCells(getBaseContext(),
                                                                          mTelephonyManager),
                                                                          scans);
    }

    private PendingIntent getIntentToGetCellsOnly() {
        Intent intent = new Intent(getBaseContext(), NetworkLocationProvider.class);
        intent.setAction("org.openbmap.unifiedNlp.LOCATION_UPDATE_CELLS_ONLY");
        return PendingIntent.getService(getBaseContext(),
                0,
                intent,
                PendingIntent.FLAG_CANCEL_CURRENT);
    }

    public class NetworkLocationProviderBinder extends Binder {
        NetworkLocationProvider getService() {
            return NetworkLocationProvider.this;
        }
    }
}