package com.oasisfeng.android.base;

import android.app.Activity;
import android.app.Application.ActivityLifecycleCallbacks;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.annotation.NonNull;

import com.oasisfeng.android.base.Scopes.Scope;

import java.util.HashSet;
import java.util.Set;

import static android.content.Context.MODE_PRIVATE;

/** @author Oasis */
public class Scopes {

    public static final String KPrefsNameAppScope = "app.scope";
    public static final String KPrefsNameVersionScope = "version.scope";

    public interface Scope {

        boolean isMarked(@NonNull String tag);
        boolean mark(@NonNull String tag);
        boolean unmark(@NonNull String tag);
    }

    public static Scope app(final Context context) { return new AppScope(context); }
    public static Scope version(final Context context) { return new VersionScope(context); }
    public static Scope process() { return ProcessScope.mSingleton; }
    public static Scope session(final Activity activity) {
    	if (SessionScope.mSingleton == null) SessionScope.mSingleton = new SessionScope(activity);
    	return SessionScope.mSingleton;
    }

    private Scopes() {}
}

class SessionScope extends MemoryBasedScopeImpl {

	private static final int KSessionTimeout = 30 * 60 * 1000;		// TODO: Configurable

	public SessionScope(final Activity activity) {
		activity.getApplication().registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {

			@Override public void onActivityResumed(final Activity a) {
				if (System.currentTimeMillis() >= mTimeLastSession + KSessionTimeout)
					mSeen.clear();
			}

			@Override public void onActivityPaused(final Activity a) {
				mTimeLastSession = System.currentTimeMillis();
			}

			@Override public void onActivityStopped(final Activity a) {}
			@Override public void onActivityStarted(final Activity a) {}
			@Override public void onActivitySaveInstanceState(final Activity a, final Bundle s) {}
			@Override public void onActivityCreated(final Activity a, final Bundle s) {}
			@Override public void onActivityDestroyed(final Activity a) {}
		});
	}

	private long mTimeLastSession = 0;

	static SessionScope mSingleton;
}

class ProcessScope extends MemoryBasedScopeImpl {

	static final Scope mSingleton = new ProcessScope();
}

class MemoryBasedScopeImpl implements Scope {

	@Override public boolean isMarked(@NonNull final String tag) {
        return mSeen.contains(tag);
    }

    @Override public boolean mark(@NonNull final String tag) {
        return mSeen.add(tag);
    }

    @Override public boolean unmark(@NonNull final String tag) {
        return mSeen.remove(tag);
    }

    protected final Set<String> mSeen = new HashSet<>();
}

class VersionScope extends SharedPrefsBasedScopeImpl {

	private static final String KPrefsKeyVersionCode = "version-code";

    VersionScope(final Context context) {
        super(resetIfVersionChanges(context, context.getSharedPreferences(Scopes.KPrefsNameVersionScope, MODE_PRIVATE)));
    }

    private static SharedPreferences resetIfVersionChanges(final Context context, final SharedPreferences prefs) {
        final int version = Versions.code(context);
        if (version != prefs.getInt(KPrefsKeyVersionCode, 0))
            prefs.edit().clear().putInt(KPrefsKeyVersionCode, version).apply();
        return prefs;
    }
}

class AppScope extends SharedPrefsBasedScopeImpl {

	AppScope(final Context context) { super(context.getSharedPreferences(Scopes.KPrefsNameAppScope, MODE_PRIVATE)); }
}

class SharedPrefsBasedScopeImpl implements Scope {

    private static final String KPrefsKeyPrefix = "first-time-";        // Old name, for backward-compatibility

    @Override public boolean isMarked(@NonNull final String tag) {
        final String key = KPrefsKeyPrefix + tag;
        return ! mPrefs.getBoolean(key, true);
    }

    @Override public boolean mark(@NonNull final String tag) {
        final String key = KPrefsKeyPrefix + tag;
        if (! mPrefs.getBoolean(key, true)) return false;
        mPrefs.edit().putBoolean(key, false).apply();
        return true;
    }

    @Override public boolean unmark(@NonNull final String tag) {
        final String key = KPrefsKeyPrefix + tag;
        if (mPrefs.getBoolean(key, true)) return false;
        mPrefs.edit().putBoolean(key, true).apply();
        return true;
    }

    protected SharedPrefsBasedScopeImpl(final SharedPreferences prefs) {
        mPrefs = prefs;
    }

    private final SharedPreferences mPrefs;
}
@@ -3639,20 +3639,73 @@
 cope %7B%0A%0A
-    
+%09private static final String KPrefsKeyPrefix = %22mark-%22;%0A%09
 private 
@@ -3735,24 +3735,30 @@
 efsKeyPrefix
+Legacy
  = %22first-ti
@@ -3762,24 +3762,17 @@
 -time-%22;
-        
+%09
 // Old n
@@ -3883,93 +3883,60 @@
     
-final String key = KPrefsKeyPrefix + tag;%0A        return ! mPrefs.getBoolean(key, tru
+return mPrefs.getBoolean(KPrefsKeyPrefix + tag, fals
 e);%0A
@@ -4059,22 +4059,14 @@
 ag;%0A
-        
+%09%09
 if (
-! 
 mPre
@@ -4076,35 +4076,36 @@
 getBoolean(key, 
-tru
+fals
 e)) return false
@@ -4144,20 +4144,19 @@
 an(key, 
-fals
+tru
 e).apply
@@ -4310,24 +4310,26 @@
         if (
+! 
 mPrefs.getBo
@@ -4339,19 +4339,20 @@
 an(key, 
-tru
+fals
 e)) retu
@@ -4391,35 +4391,36 @@
 putBoolean(key, 
-tru
+fals
 e).apply();%0A    
@@ -4540,20 +4540,486 @@
  prefs;%0A
-    
+%09%09// Migrate legacy entries%0A%09%09SharedPreferences.Editor editor = null;%0A%09%09for (final String key : prefs.getAll().keySet()) %7B%0A%09%09%09if (! key.startsWith(KPrefsKeyPrefixLegacy)) continue;%0A%09%09%09if (editor == null) editor = prefs.edit();%0A%09%09%09try %7B%0A%09%09%09%09editor.putBoolean(KPrefsKeyPrefix + key.substring(KPrefsKeyPrefixLegacy.length()), ! prefs.getBoolean(key, true));%0A%09%09%09%7D catch (final ClassCastException ignored) %7B%7D%0A%09%09%09editor.remove(key);%0A%09%09%7D%0A%09%09if (editor != null) editor.apply();%0A%09
 %7D%0A%0A    p

Use new key schema for SharedPrefsBasedScopeImpl.