package org.thosp.yourlocalweather.service;

import android.annotation.TargetApi;
import android.app.job.JobParameters;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.IBinder;

import org.thosp.yourlocalweather.model.Location;
import org.thosp.yourlocalweather.model.LocationsDbHelper;
import org.thosp.yourlocalweather.utils.AppPreference;
import org.thosp.yourlocalweather.utils.Utils;

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

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

@TargetApi(Build.VERSION_CODES.M)
public class StartAutoLocationJob extends AbstractAppJob {
    private static final String TAG = "StartAutoLocationJob";
    public static final int JOB_ID = 1992056442;

    private enum Updated {
        REGULARLY,
        BY_NOTIFICATION,
        NOTHING
    }

    private LocationUpdateService locationUpdateService;
    private ScreenOnOffUpdateService screenOnOffUpdateService;
    private SensorLocationUpdateService sensorLocationUpdateService;
    private JobParameters params;
    private int connectedServicesCounter;

    @Override
    public boolean onStartJob(JobParameters params) {
        this.params = params;
        connectedServicesCounter = 0;
        appendLog(this, TAG, "onStartJob");
        try {
            Intent intent = new Intent(getApplicationContext(), LocationUpdateService.class);
            getApplicationContext().bindService(intent, locationUpdateServiceConnection, Context.BIND_AUTO_CREATE);
            intent = new Intent(getApplicationContext(), ScreenOnOffUpdateService.class);
            getApplicationContext().bindService(intent, screenOnOffUpdateServiceConnection, Context.BIND_AUTO_CREATE);
        } catch (Exception ie) {
            appendLog(getBaseContext(), TAG, "currentWeatherServiceIsNotBound interrupted:", ie);
        }
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        if (screenOnOffUpdateService != null) {
            getApplicationContext().unbindService(screenOnOffUpdateServiceConnection);
        }
        if (locationUpdateService != null) {
            getApplicationContext().unbindService(locationUpdateServiceConnection);
        }
        appendLog(this, TAG, "unbinding sensorLocationUpdate: ",
                sensorLocationUpdateServiceConnection);
        if (sensorLocationUpdateServiceConnection !=null) {
            try {
                getApplicationContext().unbindService(sensorLocationUpdateServiceConnection);
            } catch (Exception e) {
                appendLog(this, TAG, e.getMessage(), e);
            }
        }
        unbindAllServices();
        return true;
    }

    @Override
    protected void serviceConnected(ServiceConnection serviceConnection) {
        connectedServicesCounter++;
        if (connectedServicesCounter >= 5) {
            jobFinished(params, false);
        }
    }

    private void performUpdateOfLocation() {
        try {
            Intent intent = new Intent(getApplicationContext(), SensorLocationUpdateService.class);
            getApplicationContext().bindService(intent, sensorLocationUpdateServiceConnection, Context.BIND_AUTO_CREATE);
        } catch (Exception ie) {
            appendLog(getBaseContext(), TAG, "currentWeatherServiceIsNotBound interrupted:", ie);
        }
    }

    private Updated performUpdateOfAutolocation(Calendar now,
                                             Location location,
                                             String updateAutoPeriodStr,
                                             long updateAutoPeriodMills,
                                             boolean notificationForLocation) {
        long lastSensorServicesCheckTimeInMs = AppPreference.getLastSensorServicesCheckTimeInMs(getBaseContext());
        if ("0".equals(updateAutoPeriodStr)) {
            if ((now.getTimeInMillis() >= (lastSensorServicesCheckTimeInMs + AppAlarmService.START_SENSORS_CHECK_PERIOD))) {
                sensorLocationUpdateService.startSensorBasedUpdates(0);
                AppPreference.setLastSensorServicesCheckTimeInMs(getBaseContext(), now.getTimeInMillis());
            }
            if (notificationForLocation)  {
                locationUpdateService.startLocationAndWeatherUpdate("NOTIFICATION");
                AppPreference.setLastNotificationTimeInMs(getBaseContext(), now.getTimeInMillis());
                return Updated.BY_NOTIFICATION;
            }
        } else if ("OFF".equals(updateAutoPeriodStr)) {
            sensorLocationUpdateService.stopSensorBasedUpdates();
            if (notificationForLocation)  {
                locationUpdateService.startLocationAndWeatherUpdate("NOTIFICATION");
                AppPreference.setLastNotificationTimeInMs(getBaseContext(), now.getTimeInMillis());
                return Updated.BY_NOTIFICATION;
            }
        } else if (notificationForLocation || (now.getTimeInMillis() >= (location.getLastLocationUpdate() + updateAutoPeriodMills))) {
            sensorLocationUpdateService.stopSensorBasedUpdates();
            if (notificationForLocation) {
                locationUpdateService.startLocationAndWeatherUpdate("NOTIFICATION");
                AppPreference.setLastNotificationTimeInMs(getBaseContext(), now.getTimeInMillis());
                return Updated.BY_NOTIFICATION;
            } else {
                locationUpdateService.startLocationAndWeatherUpdate(false);
                return Updated.REGULARLY;
            }
        }
        return Updated.NOTHING;
    }

    private Updated performUpdateOfWeather(Calendar now,
                                        Location location,
                                        String updatePeriodStr,
                                        long updatePeriodMills,
                                        boolean notificationForLocation) {
        if (!notificationForLocation &&
                ("0".equals(updatePeriodStr) || (now.getTimeInMillis() < (location.getLastLocationUpdate() + updatePeriodMills)))) {
            return Updated.NOTHING;
        }
        if (notificationForLocation) {
            sendMessageToCurrentWeatherService(location, "NOTIFICATION", AppWakeUpManager.SOURCE_NOTIFICATION, true);
            AppPreference.setLastNotificationTimeInMs(getBaseContext(), now.getTimeInMillis());
            sendMessageToWeatherForecastService(location.getId());
            return Updated.BY_NOTIFICATION;
        } else {
            sendMessageToCurrentWeatherService(location, AppWakeUpManager.SOURCE_CURRENT_WEATHER, true);
            sendMessageToWeatherForecastService(location.getId());
            return Updated.REGULARLY;
        }
    }

    private Location checkNotificationAndReturnLocationForNotification(Calendar now,
                                                                       boolean notificationEnabled,
                                                                       long notificationPeriodMillis,
                                                                       long lastNotificationTimeInMs) {
        if (!notificationEnabled) {
            return null;
        }
        Location currentLocation = getLocationForNotification();
        if (currentLocation == null) {
            return null;
        }
        if (now.getTimeInMillis() < (lastNotificationTimeInMs + notificationPeriodMillis)) {
            return null;
        }
        return currentLocation;
    }

    private Location getLocationForNotification() {
        final LocationsDbHelper locationsDbHelper = LocationsDbHelper.getInstance(this);
        Location currentLocation = locationsDbHelper.getLocationByOrderId(0);
        if (!currentLocation.isEnabled()) {
            currentLocation = locationsDbHelper.getLocationByOrderId(1);
        }
        return currentLocation;
    }

    private long getNextTimeForNotification(long currentAlarmWakeup, Calendar now, long lastUpdate, long updatePeriod, boolean updatedNow) {
        long nextUpdateForLocation;
        appendLog(getBaseContext(), TAG,
                "updatedNow=",
                updatedNow,
                ", updatePeriod=",
                updatePeriod);
        if (updatedNow || (updatePeriod == 0)) {
            nextUpdateForLocation = updatePeriod;
        } else {
            nextUpdateForLocation = (lastUpdate + updatePeriod) - now.getTimeInMillis();
        }
        if ((nextUpdateForLocation <= 0) || (currentAlarmWakeup < nextUpdateForLocation)) {
            return currentAlarmWakeup;
        }
        return nextUpdateForLocation;
    }

    private ServiceConnection locationUpdateServiceConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className,
                                       IBinder service) {
            LocationUpdateService.LocationUpdateServiceBinder binder =
                    (LocationUpdateService.LocationUpdateServiceBinder) service;
            locationUpdateService = binder.getService();
            appendLog(getBaseContext(), TAG, "got locationUpdateServiceConnection");
            performUpdateOfLocation();
            serviceConnected(locationUpdateServiceConnection);
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            locationUpdateService = null;
        }
    };

    private ServiceConnection sensorLocationUpdateServiceConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className,
                                       IBinder service) {
            SensorLocationUpdateService.SensorLocationUpdateServiceBinder binder =
                    (SensorLocationUpdateService.SensorLocationUpdateServiceBinder) service;
            sensorLocationUpdateService = binder.getService();
            LocationsDbHelper locationsDbHelper = LocationsDbHelper.getInstance(getBaseContext());
            Calendar now = Calendar.getInstance();
            String updateAutoPeriodStr = AppPreference.getLocationAutoUpdatePeriod(getBaseContext());
            long updateAutoPeriodMills = Utils.intervalMillisForAlarm(updateAutoPeriodStr);
            String updatePeriodStr = AppPreference.getLocationUpdatePeriod(getBaseContext());
            long updatePeriodMills = Utils.intervalMillisForAlarm(updatePeriodStr);
            boolean notificationEnabled = AppPreference.isNotificationEnabled(getBaseContext());
            String notificationPeriodStr = AppPreference.getInterval(getBaseContext());
            if ("regular_only".equals(notificationPeriodStr)) {
                notificationEnabled = false;
            }
            long notificationPeriodMillis = Utils.intervalMillisForAlarm(notificationPeriodStr);
            long lastNotificationTimeInMs = AppPreference.getLastNotificationTimeInMs(getBaseContext());
            Location locationForNotification = checkNotificationAndReturnLocationForNotification(
                    now,
                    notificationEnabled,
                    notificationPeriodMillis,
                    lastNotificationTimeInMs);

            appendLog(getBaseContext(),
                    TAG,
                    "updateAutoPeriodStr:", updateAutoPeriodStr,
                            ", updatePeriodStr:", updatePeriodStr,
                            ", notificationPeriodStr:", notificationPeriodStr);
            long nextAlarmWakeup = AppAlarmService.START_SENSORS_CHECK_PERIOD;
            appendLog(getBaseContext(), TAG, "1:nextAlarmWakeup=", nextAlarmWakeup);
            List<Location> locations = locationsDbHelper.getAllRows();
            for (Location location: locations) {
                appendLog(getBaseContext(),
                        TAG,
                        "location:", location,
                                ", location.isEnabled:", location.isEnabled());
                boolean notificationForLocation = (locationForNotification != null) && location.getId().equals(locationForNotification.getId());
                if ((location.getOrderId() == 0) && (location.isEnabled())) {
                    long lastUpdate = location.getLastLocationUpdate();
                    Updated updated = performUpdateOfAutolocation(now, location, updateAutoPeriodStr, updateAutoPeriodMills, notificationForLocation);
                    nextAlarmWakeup = getNextTimeForNotification(nextAlarmWakeup, now, lastUpdate, updateAutoPeriodMills, !Updated.NOTHING.equals(updated));
                    appendLog(getBaseContext(), TAG, "2:nextAlarmWakeup=", nextAlarmWakeup);
                    if (notificationEnabled) {
                        nextAlarmWakeup = getNextTimeForNotification(nextAlarmWakeup,
                                                                     now,
                                                                     lastNotificationTimeInMs,
                                                                     notificationPeriodMillis,
                                                                     Updated.BY_NOTIFICATION.equals(updated));
                        appendLog(getBaseContext(), TAG, "3:nextAlarmWakeup=", nextAlarmWakeup);
                    }
                } else if ((location.getOrderId() != 0) /*location.isEnabled()*/) {
                    long lastUpdate = location.getLastLocationUpdate();
                    Updated updated = performUpdateOfWeather(now, location, updatePeriodStr, updatePeriodMills, notificationForLocation);
                    nextAlarmWakeup = getNextTimeForNotification(nextAlarmWakeup, now, lastUpdate, updatePeriodMills, !Updated.NOTHING.equals(updated));
                    appendLog(getBaseContext(), TAG, "4:nextAlarmWakeup=", nextAlarmWakeup);
                    if (notificationEnabled) {
                        nextAlarmWakeup = getNextTimeForNotification(nextAlarmWakeup,
                                now,
                                lastNotificationTimeInMs,
                                notificationPeriodMillis,
                                Updated.BY_NOTIFICATION.equals(updated));
                        appendLog(getBaseContext(), TAG, "5:nextAlarmWakeup=", nextAlarmWakeup);
                    }
                }
            }
            long nextTimeForLog = now.getTimeInMillis() + nextAlarmWakeup;
            appendLog(getBaseContext(), TAG, "1:nextTimeForLog=", nextTimeForLog);
            appendLogWithDate(getBaseContext(),
                    TAG,
                    "next scheduler time:", nextTimeForLog);
            reScheduleNextAlarm(JOB_ID, nextAlarmWakeup, StartAutoLocationJob.class);
            serviceConnected(sensorLocationUpdateServiceConnection);
            if (currentWeatherUnsentMessages.isEmpty()) {
                jobFinished(params, false);
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
        }
    };

    private ServiceConnection screenOnOffUpdateServiceConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className,
                                       IBinder service) {
            ScreenOnOffUpdateService.ScreenOnOffUpdateServiceBinder binder =
                    (ScreenOnOffUpdateService.ScreenOnOffUpdateServiceBinder) service;
            screenOnOffUpdateService = binder.getService();
            screenOnOffUpdateService.startSensorBasedUpdates();
            new Thread(new Runnable() {
                public void run() {
                    serviceConnected(screenOnOffUpdateServiceConnection);
                }
            }).start();
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
        }
    };
}