package rx.android.preferences;

import android.content.SharedPreferences;
import rx.Observable;
import rx.functions.Action1;
import rx.functions.Func1;

/** A preference of type {@link T}. Instances can be created from {@link RxSharedPreferences}. */
public final class Preference<T> {
  /** Stores and retrieves instances of {@code T} in {@link SharedPreferences}. */
  public interface Adapter<T> {
    /**
     * Retrieve the value for {@code key} from {@code preferences}, using {@code defaultValue} if
     * no value exists.
     */
    T get(String key, T defaultValue, SharedPreferences preferences);

    /** Store nullable {@code value} for {@code key} in {@code editor}. */
    void set(String key, T value, SharedPreferences.Editor editor);
  }

  private final SharedPreferences preferences;
  private final String key;
  private final T defaultValue;
  private final Adapter<T> adapter;
  private final Observable<T> values;

  Preference(SharedPreferences preferences, final String key, T defaultValue, Adapter<T> adapter,
      Observable<String> keyChanges) {
    this.preferences = preferences;
    this.key = key;
    this.defaultValue = defaultValue;
    this.adapter = adapter;
    this.values = keyChanges
        .filter(new Func1<String, Boolean>() {
          @Override public Boolean call(String changedKey) {
            return key.equals(changedKey);
          }
        })
        .startWith("<init>") // Dummy value to trigger initial load.
        .map(new Func1<String, T>() {
          @Override public T call(String ignored) {
            return get();
          }
        });
  }

  /** The key for which this preference will store and retrieve values. */
  public String key() {
    return key;
  }

  /** The value used if none is stored. May be {@code null}. */
  public T defaultValue() {
    return defaultValue;
  }

  /**
   * Retrieve the current value for this preference. Returns {@link #defaultValue()} if no value is
   * set.
   */
  public T get() {
    return adapter.get(key, defaultValue, preferences);
  }

  /** Change this preference's stored value to {@code value}. */
  public void set(T value) {
    SharedPreferences.Editor editor = preferences.edit();
    adapter.set(key, value, editor);
    editor.apply();
  }

  /** Returns true if this preference has a stored value. */
  public boolean isSet() {
    return preferences.contains(key);
  }

  /** Delete the stored value for this preference, if any. */
  public void delete() {
    preferences.edit().remove(key).apply();
  }

  /**
   * Observe changes to this preference. The current value or {@link #defaultValue()} will be
   * emitted on first subscribe.
   */
  public Observable<T> asObservable() {
    return values;
  }

  /** An action which stores a new value for this preference. */
  public Action1<? super T> asAction() {
    return new Action1<T>() {
      @Override public void call(T value) {
        set(value);
      }
    };
  }
}

@@ -607,24 +607,31 @@
 s);%0A%0A    /**
+%0A     *
  Store nulla
@@ -682,16 +682,144 @@
 editor%7D.
+%0A     * %3Cp%3E%0A     * Note: Implementations %3Cb%3Emust not%3C/b%3E call %7B@code commit()%7D or %7B@code apply()%7D on%0A     * %7B@code editor%7D.%0A    
  */%0A    

Document adapters should not commit/apply data.