/**
 * Copyright (c) 2012 Todoroo Inc
 *
 * See the file "LICENSE" for the full license governing this code.
 */
package com.todoroo.astrid.sync;

import java.util.concurrent.atomic.AtomicBoolean;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.Preferences;

/**
 * Performs synchronization service logic in background service to avoid
 * ANR (application not responding) messages.
 * <p>
 * Starting this service
 *  schedules a repeating alarm which handles
 * synchronization with your serv
 *
 * @author Tim Su
 *
 */
abstract public class SyncBackgroundService extends Service {

	/** Minimum time before an auto-sync */
	private static final long AUTO_SYNC_MIN_OFFSET = 5*60*1000L;

    @Autowired private ExceptionService exceptionService;

    // --- abstract methods

    abstract protected SyncProvider<?> getSyncProvider();

    abstract protected SyncProviderUtilities getSyncUtilities();

    // --- implementation

    public SyncBackgroundService() {
        DependencyInjectionService.getInstance().inject(this);
    }

    private final AtomicBoolean started = new AtomicBoolean(false);

    /** Receive the alarm - start the synchronize service! */
    @Override
    public void onStart(Intent intent, int startId) {
        try {
            if(intent != null && !started.getAndSet(true)) {
                startSynchronization(this);
            }
        } catch (Exception e) {
            exceptionService.reportError(getSyncUtilities().getIdentifier() + "-bg-sync", e); //$NON-NLS-1$
        }
    }

    /** Start the actual synchronization */
    private void startSynchronization(Context context) {
        if(context == null || context.getResources() == null)
            return;

        ContextManager.setContext(context);

        if(!getSyncUtilities().isLoggedIn())
            return;

        getSyncProvider().synchronize(context);
    }

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

    public synchronized void stop() {
        started.set(false);
        stopSelf();
    }

    // --- alarm management

    /**
     * Schedules repeating alarm for auto-synchronization
     */
    public void scheduleService() {
        int syncFrequencySeconds = 0;
        try {
            syncFrequencySeconds = Preferences.getIntegerFromString(
                    getSyncUtilities().getSyncIntervalKey(), -1);
        } catch(ClassCastException e) {
            Preferences.setStringFromInteger(getSyncUtilities().getSyncIntervalKey(), 0);
        }
        Context context = ContextManager.getContext();
        if(syncFrequencySeconds <= 0) {
    	    unscheduleService(context);
    	    return;
    	}

    	// figure out synchronization frequency
        long interval = 1000L * syncFrequencySeconds;
        long offset = computeNextSyncOffset(interval);

        // give a little padding
        offset = Math.max(offset, AUTO_SYNC_MIN_OFFSET);

        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        PendingIntent pendingIntent = PendingIntent.getService(context, getSyncUtilities().getSyncIntervalKey(),
                createAlarmIntent(context), PendingIntent.FLAG_UPDATE_CURRENT);

        Log.i("Astrid", "Autosync set for " + offset / 1000 //$NON-NLS-1$ //$NON-NLS-2$
            + " seconds repeating every " + syncFrequencySeconds); //$NON-NLS-1$

        // cancel all existing
        am.cancel(pendingIntent);

        // schedule new
        am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + offset,
                interval, pendingIntent);
    }


    /**
     * Removes repeating alarm for auto-synchronization
     */
    private void unscheduleService(Context context) {
        AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
        PendingIntent pendingIntent = PendingIntent.getService(context, getSyncUtilities().getSyncIntervalKey(),
                createAlarmIntent(context), PendingIntent.FLAG_UPDATE_CURRENT);
        am.cancel(pendingIntent);
    }

    /** Create the alarm intent */
    private Intent createAlarmIntent(Context context) {
        Intent intent = new Intent(context, getClass());
        return intent;
    }

    // --- utility methods

    private long computeNextSyncOffset(long interval) {
        // figure out last synchronize time
        long lastSyncDate = getSyncUtilities().getLastSyncDate();

        // if user never synchronized, give them a full offset period before bg sync
        if(lastSyncDate != 0)
            return Math.max(0, lastSyncDate + interval - DateUtilities.now());
        else
            return interval;
    }


}
