@SuppressWarnings("unused")
public class BindingsManager {
  /* [ CONSTANTS ] =============================================================================================== */

  /** Associated with POP action constant. */
  private final static int DO_POP = 1;
  /** Associated with PUSH action constant. */
  private final static int DO_PUSH = 0;

  /** Internal messages. */
  private interface Messages {
    /** Detected View change. */
    int ON_VIEW_CHANGED = 1;
    /** Detected Model change. */
    int ON_MODEL_CHANGED = 2;
    /** Request unfreeze operation execution in UI thread. */
    int UNFREEZE = 4;
    /** Request validation success report delivery in UI thread. */
    int SUCCESS = 8;
    /** Request validation failure report delivery in UI thread. */
    int FAILURE = 16;
    /** Request validation report delivery in UI thread. */
    int LIFECYCLE_VALIDATION = 32;
    /** Do create binding. */
    int LIFECYCLE_BINDING = 64;
  }

  /* [ MEMBERS ] ================================================================================================= */

  /** Weak references on lifecycle listeners. */
  private final Map<Lifecycle, Void> mListeners = new WeakHashMap<>();
  /** Facade For all types of the Views. */
  private final Selector<?, ?> mFacade;
  /** Collection of all defined binding rules. */
  private final List<Binder<?, ?>> mRules = new LinkedList<>();
  /** Freeze counter. */
  private final AtomicInteger mFreezeCounter = new AtomicInteger(0);
  /** Set of binding exchange transactions. We store order, binder and direction (boolean: true - pop, false - push). */
  private final List<Pair<Binder<?, ?>, Integer>> mPending = new ArrayList<>();
  /** Handler for forwarding processing to MAIN UI thread. */
  private final Handler mDispatcher = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(final Message msg) {
      return onHandleMessage(msg);
    }
  });

  /* [ CONSTRUCTORS ] ============================================================================================= */

  /** Create bindings manager for activity instance. */
  protected BindingsManager(@NonNull final Activity activity) {
    mFacade = Views.<View>root(activity);
  }

  /** Create bindings manager for native OS fragment. */
  protected BindingsManager(@NonNull final Fragment fragment) {
    mFacade = Views.<View>root(fragment);
  }

  /** Create bindings manager for 'support fragment'. */
  protected BindingsManager(@NonNull final android.support.v4.app.Fragment fragment) {
    mFacade = Views.<View>root(fragment);
  }

  /** Create bindings manager for view instance. */
  protected BindingsManager(@NonNull final View view) {
    mFacade = Views.<View>root(view);
  }

  /** Create bindings manager for adapter instance. */
  protected BindingsManager(@NonNull final BindingAdapter adapter) {
    mFacade = Adapters.<Adapter>root(adapter);
  }

  /* [ STATIC HELPERS ] =========================================================================================== */

  public static BindingsManager newInstance(@NonNull final Activity a,
                                            @NonNull final Lifecycle listener) {
    return new BindingsManager(a).register(listener);
  }

  public static BindingsManager newInstance(@NonNull final android.support.v4.app.Fragment f,
                                            @NonNull final Lifecycle listener) {
    return new BindingsManager(f).register(listener);
  }

  public static BindingsManager newInstance(@NonNull final Fragment f,
                                            @NonNull final Lifecycle listener) {
    return new BindingsManager(f).register(listener);
  }

  public static BindingsManager newInstance(@NonNull final View v,
                                            @NonNull final Lifecycle listener) {
    return new BindingsManager(v).register(listener);
  }

  public static BindingsManager newInstance(@NonNull final BindingAdapter ba,
                                            @NonNull final Lifecycle listener) {
    return new BindingsManager(ba).register(listener);
  }

  /* [ OVERRIDES ] ================================================================================================ */

  /** UI THREAD! processing of messages in UI thread. */
  protected boolean onHandleMessage(final Message msg) {
    final Binder<?, ?> binder = (msg.obj instanceof Binder) ? (Binder<?, ?>) msg.obj : Binder.EMPTY;

    switch (msg.what) {
      case Messages.ON_MODEL_CHANGED:
        pop(binder);
        return true;

      case Messages.ON_VIEW_CHANGED:
        push(binder);
        return true;

      case Messages.UNFREEZE:
        if (DO_POP == msg.arg1) {
          binder.pop();
        } else {
          binder.push();
        }
        return true;

      case Messages.SUCCESS:
        binder.getOnSuccess().onValidationSuccess(this, binder);
        return true;

      case Messages.FAILURE:
        binder.getOnFailure().onValidationFailure(this, binder);
        return true;

      case Messages.LIFECYCLE_BINDING:
        for (Lifecycle lf : mListeners.keySet()) {
          lf.onCreateBinding(this);
        }
        return true;

      case Messages.LIFECYCLE_VALIDATION:
        for (Lifecycle lf : mListeners.keySet()) {
          lf.onValidationResult(this, isAllValid());
        }
        return true;
    }

    return false;
  }

  /* [ BINDING RULES DEFINING ] =================================================================================== */

  /** Get list of all binding rules. */
  public List<Binder<?, ?>> getBindings() {
    return mRules;
  }

  /** Get list of binder's that interact with specified model instance. */
  public List<Binder<?, ?>> getBindingsByModel(@NonNull final Object model) {
    final List<Binder<?, ?>> result = new LinkedList<>();

    for (Binder<?, ?> b : mRules) {
      if (model.equals(b.getRuntimeModel())) {
        result.add(b);
      }
    }

    return result;
  }

  /** Get list of binder's that interact with specified view instance. */
  public List<Binder<?, ?>> getBindingsByView(@NonNull final Object view) {
    final List<Binder<?, ?>> result = new LinkedList<>();

    for (Binder<?, ?> b : mRules) {
      if (view.equals(b.getRuntimeView())) {
        result.add(b);
      }
    }

    return result;
  }

  /** Get list of binder's that has specified tag ID. */
  public List<Binder<?, ?>> getBindingsByTag(@IdRes final int id) {
    final List<Binder<?, ?>> result = new LinkedList<>();

    for (Binder<?, ?> b : mRules) {
      if (null != b.getTag(id)) {
        result.add(b);
      }
    }

    return result;
  }

  /** Is all rules successfully validated? */
  public boolean isAllValid() {
    return getFailedBindings().size() == 0;
  }

  /** Get list of all successfully validated bindings. */
  public List<Binder<?, ?>> getSuccessBindings() {
    final List<Binder<?, ?>> result = new LinkedList<>();

    // if POP or PUSH operation never performed, than we assume that they are in OK state
    for (final Binder<?, ?> b : mRules) {
      if (b.isPopOk() && b.isPushOk()) {
        result.add(b);
      }
    }

    return result;
  }

  /** Get list of failed validated bindings. */
  public List<Binder<?, ?>> getFailedBindings() {
    final List<Binder<?, ?>> result = new LinkedList<>();

    // at least one operation should be in failed state
    for (final Binder<?, ?> b : mRules) {
      if (!b.isPopOk() || !b.isPushOk()) {
        result.add(b);
      }
    }

    return result;
  }

  /**
   * Get selector on root view.
   * <p/>
   * For different type of instance selector will be resolved to different instances:<br/>
   * - for Activity --&gt; to Root view of Activity;<br/>
   * - for Fragment --&gt; to Root view of the Fragment;<br/>
   * - for Adapter --&gt; to current item under processing;<br/>
   * - for View --&gt; to view instance itself;<br/>
   */
  @NonNull
  public <TRight> Selector<?, TRight> getFacade() {
    return (Selector<?, TRight>) mFacade;
  }

  /* [ GENERIC BINDERS ] ========================================================================================== */

  /** Create new bind rule instance. */
  public <TLeft, TRight> Binder<TLeft, TRight> bind() {
    final Binder<TLeft, TRight> result = new Binder<>();

    return result.attachToManager(this);
  }

  /** Create new bind rule instance with view and model. */
  public <TLeft, TRight> Binder<TLeft, TRight> bind(@NonNull final Selector<?, TLeft> view,
                                                    @NonNull final Selector<?, TRight> model) {
    final Binder<TLeft, TRight> result = new Binder<>();

    return result
        .view(view)
        .model(model)
        .attachToManager(this);
  }

  /* [ LIFECYCLE ] ================================================================================================ */

  /** Register lifecycle extender listener. */
  public BindingsManager register(@NonNull final Lifecycle listener) {
    mListeners.put(listener, null);

    return this;
  }

  /** Unregister lifecycle extender listener. */
  public BindingsManager unregister(@NonNull final Lifecycle listener) {
    mListeners.remove(listener);

    return this;
  }

  /**
   * Execute from: {@link Activity#onRestart()}. Execute complete restart of Binding manager lifecycle.
   *
   * @param caller reference on method caller, allows to identify fragments and activities.
   */
  public void doRestart(final Object caller) {
    // TODO: this is activity state... what should we do?
  }

  /**
   * Execute from: {@link Activity#onStart()} or {@link android.support.v4.app.Fragment#onStart()}, {@link
   * Fragment#onStart()}.
   *
   * @param caller reference on method caller, allows to identify fragments and activities.
   */
  public void doStart(final Object caller) {
    notifyOnCreateBinding();
  }

  /**
   * Execute from: {@link Activity#onResume()} or {@link android.support.v4.app.Fragment#onResume()}, {@link
   * Fragment#onResume()}.
   *
   * @param caller reference on method caller, allows to identify fragments and activities.
   */
  public void doResume(final Object caller) {
    pop();
  }

  /**
   * Execute from: {@link Activity#onPause()} or {@link android.support.v4.app.Fragment#onPause()}, {@link
   * Fragment#onPause()}.
   *
   * @param caller reference on method caller, allows to identify fragments and activities.
   */
  public void doPause(final Object caller) {
    push();
  }

  /**
   * Execute from: {@link Activity#onStop()} or {@link android.support.v4.app.Fragment#onStop()}, {@link
   * Fragment#onStop()}.
   *
   * @param caller reference on method caller, allows to identify fragments and activities.
   */
  public void doStop(final Object caller) {
    for (int i = mRules.size() - 1; i >= 0; i--) {
      final Binder<?, ?> b = mRules.get(i);

      // self de-registration from Binding Manager happens
      b.destroy();
    }
  }

  /**
   * Execute from: {@link Activity#onDestroy()} or {@link android.support.v4.app.Fragment#onDestroy()}, {@link
   * Fragment#onDestroy()}.
   *
   * @param caller reference on method caller, allows to identify fragments and activities.
   */
  public void doDestroy(final Object caller) {
    // TODO: cleanup memory, drop reflection cache?!
  }

  /* package */ void notifyOnCreateBinding() {
    final Message msg = mDispatcher.obtainMessage(Messages.LIFECYCLE_BINDING);
    mDispatcher.sendMessage(msg);
  }

  /* package */ void notifyOnViewChanged(@NonNull final Binder<?, ?> binder) {
    final Message msg = mDispatcher.obtainMessage(Messages.ON_VIEW_CHANGED, binder);
    mDispatcher.sendMessage(msg);
  }

  /* package */ void notifyOnModelChanged(@NonNull final Binder<?, ?> binder) {
    final Message msg = mDispatcher.obtainMessage(Messages.ON_MODEL_CHANGED, binder);
    mDispatcher.sendMessage(msg);
  }

  /* package */ void notifyOnValidation(@NonNull final Binder<?, ?> binder) {
    final Message msg = mDispatcher.obtainMessage(Messages.LIFECYCLE_VALIDATION);
    mDispatcher.sendMessage(msg);
  }

  /* package */ void notifyOnSuccess(@NonNull final Binder<?, ?> binder) {
    if (null != binder.getOnSuccess()) {
      final Message msg = mDispatcher.obtainMessage(Messages.SUCCESS, binder);
      mDispatcher.sendMessage(msg);
    }
  }

  /* package */ void notifyOnFailure(@NonNull final Binder<?, ?> binder) {
    if (null != binder.getOnFailure()) {
      final Message msg = mDispatcher.obtainMessage(Messages.FAILURE, binder);
      mDispatcher.sendMessage(msg);
    }
  }

	/* [ PUSH AND POP ] ============================================================================================= */

  /**
   * Evaluate all bindings. Perform on each PUSH. VIEW data delivered to MODEL.<br/> Keep in mind that this is
   * <b>ASYNC</b> operation. Results of binding you will receive in specially designed callbacks/listeners: {@link
   * BindingsManager.Lifecycle}, {@link Success} or {@link Failure}.
   *
   * @return this instance.
   */
  public BindingsManager push() {
    return push(mRules);
  }

  /**
   * Evaluate all bindings from list. Perform on each PUSH. VIEW data delivered to MODEL.<br/> Keep in mind that this is
   * <b>ASYNC</b> operation. Results of binding you will receive in specially designed callbacks/listeners: {@link
   * BindingsManager.Lifecycle}, {@link Success} or {@link Failure}.
   *
   * @return this instance.
   */
  public BindingsManager push(@NonNull final List<Binder<?, ?>> binders) {
    for (Binder<?, ?> b : binders) {
      push(b);
    }

    return this;
  }

  /**
   * Force model modelInstance update by values from view's.
   *
   * @param modelInstance the modelInstance of model
   * @return this instance.
   */
  public BindingsManager pushTo(@NonNull final Object modelInstance) {
    return push(getBindingsByModel(modelInstance));
  }

  /**
   * Force model instance update by value from view with respect to 'Freeze mode'.
   *
   * @param binder binding rule.
   * @return this instance.
   */
  public BindingsManager push(@NonNull final Binder<?, ?> binder) {
    if (isFrozen()) {
      mPending.add(new Pair<Binder<?, ?>, Integer>(binder, DO_PUSH));
    } else {
      binder.push();
    }

    return this;
  }

  /**
   * Force views updates that are bind to the provided model modelInstance.
   *
   * @param modelInstance the modelInstance of model
   * @return this instance.
   */
  public BindingsManager popByModel(@NonNull final Object modelInstance) {
    return pop(getBindingsByModel(modelInstance));
  }

  /** Pop data from Model to view instance. */
  public BindingsManager popTo(@NonNull final Object viewInstance) {
    return pop(getBindingsByView(viewInstance));
  }

  /**
   * Evaluate all bindings. Perform on each POP. MODEL data delivered to VIEW.<br/> Keep in mind that this is
   * <b>ASYNC</b> operation. Results of binding you will receive in specially designed callbacks/listeners: {@link
   * BindingsManager.Lifecycle}, {@link Success} or {@link Failure}.
   *
   * @return this instance.
   */
  public BindingsManager pop() {
    return pop(mRules);
  }

  /**
   * Evaluate all bindings from list. Perform on each POP. MODEL data delivered to VIEW.<br/> Keep in mind that this is
   * <b>ASYNC</b> operation. Results of binding you will receive in specially designed callbacks/listeners: {@link
   * BindingsManager.Lifecycle}, {@link Success} or {@link Failure}.
   *
   * @return this instance.
   */
  public BindingsManager pop(@NonNull final List<Binder<?, ?>> binders) {
    for (Binder<?, ?> b : binders) {
      pop(b);
    }

    return this;
  }

  /**
   * Force views updates that are bind to the provided model instance with respect to 'Freeze mode'.
   *
   * @param binder binding rule.
   * @return this instance.
   */
  public BindingsManager pop(@NonNull final Binder<?, ?> binder) {
    if (isFrozen()) {
      mPending.add(new Pair<Binder<?, ?>, Integer>(binder, DO_POP));
    } else {
      binder.pop();
    }

    return this;
  }

  /**
   * Are we in 'freeze mode' state or not?
   *
   * @return {@code true} - if we frozen, otherwise {@code false}.
   */
  public boolean isFrozen() {
    return mFreezeCounter.get() > 0;
  }

  /** Get number of pending POP/PUSH actions. */
  public int getPendingQueueSize() {
    return mPending.size();
  }

  /**
   * Stop triggering of all data push/pop operations.
   *
   * @return this instance.
   */
  public BindingsManager freeze() {
    mFreezeCounter.incrementAndGet();
    return this;
  }

  /**
   * Recover triggering of all data push/pop operations.
   *
   * @return this instance.
   */
  public BindingsManager unfreeze() {
    if (0 >= mFreezeCounter.decrementAndGet()) {
      mFreezeCounter.set(0);

      // execute pending data exchange requests
      if (!mPending.isEmpty()) {
        for (Pair<Binder<?, ?>, Integer> p : mPending) {
          // update is possible only in UI thread
          if (View.class.isAssignableFrom(p.first.getViewType())) {
            mDispatcher.sendMessage(mDispatcher.obtainMessage(Messages.UNFREEZE, p.second, -1, p.first));
            continue;
          }

          // do data exchange
          if (DO_POP == p.second) {
            p.first.pop();
          } else {
            p.first.push();
          }
        }

        mPending.clear();
      }
    }

    return this;
  }

  /**
   * Method force binding between data model and view without data exchange. This is useful for binding verification on
   * initial phase. It allows to check that all fields in data model and view exists and can be associated.
   *
   * @throws WrongConfigurationError - found mismatch. Raised on first found mismatch.
   */
  @UiThread
  public void associate() throws WrongConfigurationError {
    for (Binder<?, ?> binder : mRules) {
      binder.resolve();
    }
  }

	/* [ NESTED DECLARATIONS ] ====================================================================================== */

  /**
   * Lifecycle extending callback. Implement it if you want to enhance original lifecycle by new state, during which
   * binding operation is the most suitable.
   */
  public interface Lifecycle {
    /** Raised on moment when is the best to create bindings. Execute in MAIN thread. */
    void onCreateBinding(final BindingsManager bm);

    /** Raised on moment of validation. Executed in MAIN thread. */
    void onValidationResult(final BindingsManager bm, final boolean success);
  }
} @SuppressWarnings("unused")
public final class Binders {
  /* [ CONSTRUCTORS ] ============================================================================================= */

  /** hidden constructor. */
  private Binders() {
    throw new AssertionError();
  }

  /* [ TYPED VERSIONS ] =========================================================================================== */

  /** Char-to-Char. */
  @NonNull
  public static Binder<Character, Character> letter(final BindingsManager bm) {
    // used default direct() formatting
    return bm.bind();
  }

  /** CharSequence-to-CharSequence. */
  @NonNull
  public static Binder<CharSequence, CharSequence> chars(final BindingsManager bm) {
    // used default direct() formatting
    return bm.bind();
  }

  /** CharSequence-to-String. Format {@link Molds#fromCharsToString()} is assigned during the call. */
  @NonNull
  public static Binder<CharSequence, String> strings(final BindingsManager bm) {
    final Binder<CharSequence, String> result = bm.bind();

    // char sequence to string converter assigned
    return result.format(fromCharsToString());
  }

  /** String-to-String. */
  @NonNull
  public static Binder<String, String> texts(final BindingsManager bm) {
    // used default direct() formatting
    return bm.bind();
  }

  /** CharSequence-to-Integer. */
  @NonNull
  public static Binder<CharSequence, Integer> numeric(final BindingsManager bm) {
    final Binder<CharSequence, Integer> bind = bm.bind();

    // char sequence to integer converter assigned
    return bind.format(Molds.fromCharsToInteger());
  }

  /** String-to-Integer. */
  @NonNull
  public static Binder<String, Integer> numbers(final BindingsManager bm) {
    final Binder<String, Integer> bind = bm.bind();

    // string to integer converter assigned
    return bind.format(Molds.fromStringToNumber(Integer.class));
  }

  // TODO: byte-to-byte, short-to-short, long-to-long, float-to-float

  /** Integer-to-Integer. */
  @NonNull
  public static Binder<Integer, Integer> integers(final BindingsManager bm) {
    // used default direct() formatting
    return bm.bind();
  }

  /** Double-to-Double */
  @NonNull
  public static Binder<Double, Double> reals(final BindingsManager bm) {
    // used default direct() formatting
    return bm.bind();
  }

  /** Boolean-to-Boolean. */
  @NonNull
  public static Binder<Boolean, Boolean> bools(final BindingsManager bm) {
    // used default direct() formatting
    return bm.bind();
  }
} @SuppressWarnings("unused")
public final class Listeners {
  /* [ CONSTANTS ] ================================================================================================= */

  /** Generic EMPTY listener. */
  public final static Listener NONE = new Listener() {
    @Override
    public Listener binding(@NonNull final Selector<?, ?> instance) {
      // do nothing...
      return this;
    }

    @Override
    public void willNotify(@NonNull final Notifications selector) {
      // do nothing
    }

    @Override
    public void detach(@NonNull final Notifications selector) {
      // do nothing
    }
  };
  /** Extra key used for delivering listener ID to broadcast processing class. */
  private final static String EXT_KEY = "key";
  /** Action name used for timer based data exchange scheduling. */
  private final static String ACTION_TIME = "action_time_comes";

  /* [ STATIC MEMBERS ] ============================================================================================ */

  /** Broadcasts updates synchronization guard. */
  private static final Object sSync = new Object();
  /** Global map for alarm manager pending intents processing. */
  private static final Map<Integer, WeakReference<Listener>> sBroadcast = new HashMap<>();
  /** Instance of broadcast receiver that will process our timer based data exchange requests. */
  private static AlarmReceiver sReceiver;

	/* [ CONSTRUCTORS ] ============================================================================================== */

  /** hidden constructor. */
  private Listeners() {
    throw new AssertionError();
  }

	/* [ STATIC METHODS ] ============================================================================================ */

  /**
   * Join multiple listeners into one expression.
   *
   * @param listeners the array of listeners
   * @return the joined listeners instance.
   */
  @NonNull
  public static Listener anyOf(@NonNull final Listener... listeners) {
    return new Listener() {
      @Override
      public Listener binding(@NonNull final Selector<?, ?> instance) {
        for (Listener listener : listeners) {
          listener.binding(instance);
        }

        return this;
      }

      @Override
      public void willNotify(@NonNull final Notifications selector) {
        for (Listener listener : listeners) {
          listener.willNotify(selector);
        }
      }

      @Override
      public void detach(@NonNull final Notifications selector) {
        for (Listener listener : listeners) {
          listener.detach(selector);
        }
      }
    };
  }

  /**
   * Get 'empty listener' instance. this is simply a stub that can be used in expressions but it doing absolutely
   * nothing. You can add Logs into stub, for making binding process more transparent.
   */
  @NonNull
  public static Listener none() {
    return NONE;
  }

  /** Raise notification on any change from Observable side. */
  @NonNull
  public static Listener onObservable() {
    return new ObserverListener();
  }

  /** Listen to notifications from Observable instance and raise notification only if filter matched. */
  @NonNull
  public static Listener onObservable(final Object filter) {
    return new ObserverListener(filter);
  }

  /** Detect string changes and raise data exchange on that. */
  @NonNull
  public static Listener onTextChanged() {
    return new TextWatcherListener();
  }

  /** Detect focus loss and raise data exchange on that. */
  @NonNull
  public static Listener onFocusLost() {
    return new FocusLostListener();
  }

  /** Timer based raiser of data exchange. */
  @NonNull
  public static Listener onTimer(@IntRange(from = 0) final long delay,
                                 @IntRange(from = 0) final long interval) {
    return new TimerListener(delay, interval);
  }

  /** Timer based raiser of data exchange. Based on more resource effective Alarm Manager. */
  @NonNull
  public static Listener onAlarm(@NonNull final Context context,
                                 @IntRange(from = 0) final long delay,
                                 @IntRange(from = 0) final long interval) {
    return new AlarmListener(context, delay, interval);
  }

  /* [ IMPLEMENTATION ] ============================================================================================ */

  /** Register listener and if needed create Receiver for events processing. */
  private static void register(@NonNull final Context context, @NonNull final Listener listener) {
    // register our instance as a broadcast listener
    sBroadcast.put(listener.hashCode(), new WeakReference<>(listener));

    // create instance of Broadcast receiver
    if (null == sReceiver) {
      synchronized (sSync) {
        if (null == sReceiver) {
          context.registerReceiver(sReceiver = new AlarmReceiver(), new IntentFilter(ACTION_TIME));
        }
      }
    }
  }

  /** Unregister the listener and release OS resources. */
  private static void unregister(@NonNull final Context context, @NonNull final Listener listener) {
    sBroadcast.remove(listener.hashCode());

    if (sBroadcast.isEmpty()) {
      synchronized (sSync) {
        if (sBroadcast.isEmpty()) {
          context.unregisterReceiver(sReceiver);
          sReceiver = null;
        }
      }
    }
  }

	/* [ NESTED DECLARATIONS ] ======================================================================================= */

  /** Listen to texts changes of the TextView control. */
  private static class TextWatcherListener implements Listener, TextWatcher {
    /** Set of notifiers. */
    private final Set<Notifications> mKnown = new HashSet<>();

    @Override
    public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {

    }

    @Override
    public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
      for (Notifications selector : mKnown) {
        selector.onChanged();
      }
    }

    @Override
    public void afterTextChanged(final Editable s) {

    }

    @Override
    public Listener binding(@NonNull final Selector<?, ?> instance) {
      final Object view = instance.getRuntimeInstance();

      if (view instanceof TextView) {
        final TextView tv = (TextView) view;

        tv.addTextChangedListener(this);
      }

      return this;
    }

    @Override
    public void willNotify(@NonNull final Notifications listener) {
      mKnown.add(listener);
    }

    @Override
    public void detach(@NonNull final Notifications listener) {
      mKnown.remove(listener);
    }
  }

  /** Listen to focus loss of the View. */
  private static class FocusLostListener implements Listener, View.OnFocusChangeListener {
    /** Set of notifiers. */
    private final Set<Notifications> mKnown = new HashSet<>();

    @Override
    public void onFocusChange(final View v, final boolean hasFocus) {
      // we listen only focus loss
      if (hasFocus) return;

      for (Notifications selector : mKnown) {
        selector.onChanged();
      }
    }

    @Override
    public Listener binding(@NonNull final Selector<?, ?> instance) {
      final Object view = instance.getRuntimeInstance();

      if (view instanceof View) {
        final View tv = (View) view;

        tv.setOnFocusChangeListener(this);
      }

      return this;
    }

    @Override
    public void willNotify(@NonNull final Notifications listener) {
      mKnown.add(listener);
    }

    @Override
    public void detach(@NonNull final Notifications listener) {
      mKnown.remove(listener);
    }
  }

  /** Listen to Observable instance. */
  private static class ObserverListener implements Listener, Observer {
    /** filter that allows any kind of data be treated as a notification to listener. */
    public static final Object ANYTHING = new Object();
    /** Set of notifiers. */
    private final Set<Notifications> mKnown = new HashSet<>();
    /** Filter instance. */
    private final Object mFilter;

    /** No filter, allowed all notifications. */
    public ObserverListener() {
      this(ANYTHING);
    }

    /** Define custom filter. */
    public ObserverListener(final Object filter) {
      mFilter = filter;
    }

    @Override
    public Listener binding(@NonNull final Selector<?, ?> instance) {
      final Object i = instance.getRuntimeInstance();

      if (i instanceof Observable) {
        final Observable ob = (Observable) i;

        ob.addObserver(this);
      }

      return this;
    }

    @Override
    public void willNotify(@NonNull final Notifications listener) {
      mKnown.add(listener);
    }

    @Override
    public void detach(@NonNull final Notifications listener) {
      mKnown.remove(listener);
    }

    @Override
    public void update(final Observable observable, final Object data) {
      if (ANYTHING.equals(mFilter) || mFilter.equals(data)) {
        for (Notifications selector : mKnown) {
          selector.onChanged();
        }
      }
    }
  }

  /** Raise event by timer. */
  private static class TimerListener extends TimerTask implements Listener {
    /** Create instance of timer for countdown. Timer is created with daemon flag. */
    private final Timer mCountDown = new Timer(true);
    /** Set of notifiers. */
    private final Set<Notifications> mKnown = new HashSet<>();
    /** Delay in milliseconds. */
    private final long mDelay;
    /** Period of repeat. */
    private final long mPeriod;

    /** new instance with delay and period settings defining. */
    public TimerListener(@IntRange(from = 0) final long delay, @IntRange(from = 0) final long period) {
      mDelay = delay;
      mPeriod = period;
    }

    @Override
    public void run() {
      for (Notifications selector : mKnown) {
        selector.onChanged();
      }
    }

    @Override
    public Listener binding(@NonNull final Selector<?, ?> instance) {
      mCountDown.schedule(this, mDelay, mPeriod);

      return this;
    }

    @Override
    public void willNotify(@NonNull final Notifications listener) {
      mKnown.add(listener);
    }

    @Override
    public void detach(@NonNull final Notifications listener) {
      mKnown.remove(listener);
    }
  }

  /** Broadcast receiver for processing the AlarmListener requests. */
  private static class AlarmReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(final Context context, final Intent intent) {
      // ignore wrong calls
      if (null == context || null == intent) return;

      // get listener that request the processing
      final int key = intent.getIntExtra(EXT_KEY, -1);
      final WeakReference<Listener> wrl = sBroadcast.get(key);
      final Listener l;

      // nothing to process
      if (null == wrl || null == (l = wrl.get())) return;

      // if listener is a known to us type, than raise the changed state
      if (l instanceof AlarmListener) {
        ((AlarmListener) l).raise();
      }
    }
  }

  /**
   * Timer based listener, but instead of separated thread used AlarmManager which is more battery effective in compare
   * to background thread.
   */
  private static class AlarmListener implements Listener {
    /** Application context. */
    private final Context mContext;
    /** Set of notifiers. */
    private final Set<Notifications> mKnown = new HashSet<>();
    /** Delay in milliseconds. */
    private final long mDelay;
    /** Period of repeat. */
    private final long mPeriod;
    /** pending operation. */
    private PendingIntent mOperation;

    /** Create instance that will start raising 'changed' state in specified delay and with specified interval. */
    public AlarmListener(@NonNull final Context context,
                         @IntRange(from = 0) final long delay,
                         @IntRange(from = 0) final long interval) {
      mContext = context;
      mDelay = delay;
      mPeriod = interval;
    }

    @Override
    public Listener binding(@NonNull final Selector<?, ?> instance) {
      // register our instance as a broadcast listener
      register(mContext, this);

      // create intent that will be delivered by Alarm Manager
      final Intent intent = new Intent(mContext, AlarmReceiver.class)
          .setAction(ACTION_TIME)
          .putExtra(EXT_KEY, hashCode());

      final PendingIntent operation = PendingIntent.getBroadcast(mContext, hashCode(),
          intent, PendingIntent.FLAG_UPDATE_CURRENT);

      // set the timer
      final AlarmManager manager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
      manager.setRepeating(AlarmManager.ELAPSED_REALTIME,
          SystemClock.elapsedRealtime() + mDelay, mPeriod,
          operation);

      return this;
    }

    /** Notify all listeners that its time for data exchange. */
    public void raise() {
      for (Notifications selector : mKnown) {
        selector.onChanged();
      }
    }

    @Override
    public void willNotify(@NonNull final Notifications listener) {
      mKnown.add(listener);
    }

    @Override
    public void detach(@NonNull final Notifications listener) {
      mKnown.remove(listener);

      if (mKnown.isEmpty() && null != mOperation) {
        final AlarmManager manager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
        manager.cancel(mOperation);

        unregister(mContext, this);
      }
    }
  }
} public interface ToView<TOut, TIn> {
  /**
   * Convert In- value into Out- value.
   *
   * @param value the instance to convert
   * @return the converted value
   * @throws com.artfulbits.binding.exceptions.OneWayBindingError in case if allowed only one way binding.
   */
  TOut toView(final TIn value);
} public abstract class BindingSupportFragment
    extends android.support.v4.app.Fragment
    implements BindingsManager.Lifecycle {
  /** Instance of binding manager. */
  private BindingsManager mBm = BindingsManager.newInstance(this, this);

  /** get instance of the Binding manager. */
  public BindingsManager getBindingsManager() {
    return mBm;
  }

  /** {@inheritDoc} */
  @Override
  public void onStart() {
    super.onStart();

    // freeze all updates
    mBm.freeze().doStart(this); // call of --> onCreateBinding(...);
  }

  /** {@inheritDoc} */
  @Override
  public void onResume() {
    super.onResume();

    // fragment is ready for updates. UN-freeze can be called several times
    mBm.unfreeze().doResume(this); // call of --> onValidationResult
  }

  /** {@inheritDoc} */
  @Override
  public void onPause() {
    mBm.doPause(this);

    super.onPause();
  }

  /** {@inheritDoc} */
  @Override
  public void onStop() {
    mBm.doStop(this);

    super.onStop();
  }

  /** {@inheritDoc} */
  @Override
  public void onDestroy() {
    mBm.doDestroy(this);

    super.onDestroy();
  }

  /** {@inheritDoc} */
  @Override
  public void onCreateBinding(@NonNull final BindingsManager bm) {
    // reserved for inheritors
  }

  /** {@inheritDoc} */
  @Override
  public void onValidationResult(@Nullable final BindingsManager bm, boolean success) {
    // reserved for inheritors
  }
} @SuppressWarnings("unused")
public class Pin extends Observable {
  private String mPassword;
  private String mConfirmPassword;

  /** Calculated message based on state of instance. */
  public int getMessage() {
    if (TextUtils.isEmpty(mPassword) || TextUtils.isEmpty(mConfirmPassword))
      return R.string.msgPasswordPatternFail;

    if (mPassword.length() < 4 || mPassword.length() > 6)
      return R.string.msgPasswordPatternFail;

    if (mConfirmPassword.length() < 4 || mConfirmPassword.length() > 6)
      return R.string.msgPasswordPatternFail;

    return mPassword.equals(mConfirmPassword) ?
        R.string.msgPasswordOK :
        R.string.msgPasswordFail;
  }

  public String getConfirmPassword() {
    return mConfirmPassword;
  }

  public void setConfirmPassword(final String confirmPassword) {
    mConfirmPassword = confirmPassword;

    setChanged();
    notifyObservers("ConfirmPassword");
  }

  public String getPassword() {
    return mPassword;
  }

  public void setPassword(final String password) {
    mPassword = password;

    setChanged();
    notifyObservers("Password");
  }
} @NonNull
public static Listener anyOf(@NonNull final Listener... listeners) {
  return new Listener() {
    @Override
    public Listener binding(@NonNull final Selector<?, ?> instance) {
      for (Listener listener : listeners) {
        listener.binding(instance);
      }

      return this;
    }

    @Override
    public void willNotify(@NonNull final Notifications selector) {
      for (Listener listener : listeners) {
        listener.willNotify(selector);
      }
    }

    @Override
    public void detach(@NonNull final Notifications selector) {
      for (Listener listener : listeners) {
        listener.detach(selector);
      }
    }
  };
} @NonNull
public static Listener onFocusLost() {
  return new FocusLostListener();
} @NonNull
public static Listener onObservable() {
  return new ObserverListener();
} @NonNull
public static Listener onTextChanged() {
  return new TextWatcherListener();
} @NonNull
public static Property<Integer> integer(@NonNull final String name) {
  return new Property<>(Integer.class, name);
} @NonNull
public static <I, T> Selector<I, T> pojo(@NonNull final I instance, @NonNull final Property<T> property) {
  return new Selector<>(instance, property);
} @NonNull
public static Property<String> text(@NonNull final String name) {
  return new Property<>(String.class, name);
} @NonNull
public static Formatting<CharSequence, String> fromCharsToString() {
  return new Formatting<CharSequence, String>() {
    @Override
    public String toModel(final CharSequence value) {
      return value.toString();
    }

    @Override
    public CharSequence toView(final String value) {
      return value;
    }
  };
} @NonNull
public static <T, V> Formatting<T, V> onlyPop(@NonNull final Formatting<T, V> f) {
  return new Formatting<T, V>() {
    @Override
    public T toView(final V value) {
      return f.toView(value);
    }

    @Override
    public V toModel(final T value) {
      throw new OneWayBindingError();
    }
  };
} @NonNull
public static <T, V> Formatting<T, V> onlyPush(@NonNull final Formatting<T, V> f) {
  return new Formatting<T, V>() {
    @Override
    public T toView(final V value) {
      throw new OneWayBindingError();
    }

    @Override
    public V toModel(final T value) {
      return f.toModel(value);
    }
  };
} @NonNull
public static Selector<EditText, CharSequence> editText(@NonNull final View v, final int id) {
  return textView(Views.<EditText>withId(v, id));
} @NonNull
public static Selector<TextView, CharSequence> textView(@NonNull final View v, final int id) {
  return textView(Views.<TextView>withId(v, id));
}
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.artfulbits.binding.BindingsManager;
import com.artfulbits.binding.toolbox.Binders;
import com.artfulbits.binding.toolbox.Listeners;
import com.artfulbits.binding.toolbox.ToView;
import com.artfulbits.binding.ui.BindingSupportFragment;
import com.artfulbits.sample.data.Pin;
import org.hamcrest.Matchers;
import static com.artfulbits.binding.toolbox.Listeners.anyOf;
import static com.artfulbits.binding.toolbox.Listeners.onFocusLost;
import static com.artfulbits.binding.toolbox.Listeners.onObservable;
import static com.artfulbits.binding.toolbox.Listeners.onTextChanged;
import static com.artfulbits.binding.toolbox.Models.integer;
import static com.artfulbits.binding.toolbox.Models.pojo;
import static com.artfulbits.binding.toolbox.Models.text;
import static com.artfulbits.binding.toolbox.Molds.fromCharsToString;
import static com.artfulbits.binding.toolbox.Molds.onlyPop;
import static com.artfulbits.binding.toolbox.Molds.onlyPush;
import static com.artfulbits.binding.toolbox.Views.editText;
import static com.artfulbits.binding.toolbox.Views.textView;
import static org.hamcrest.Matchers.*;
package com.artfulbits.sample;





/** Fragment for entering PIN, demo of connected to each other properties. */
public class PinFragment extends BindingSupportFragment {
  /** model instance. */
  private final Pin mPin = new Pin();

  /** Get access to model instance. */
  public Pin getModel() {
    return mPin;
  }

  /** {@inheritDoc} */
  @Override
  public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle saved) {
    return inflater.inflate(R.layout.fragment_pin, container, false);
  }

  /** {@inheritDoc} */
  @Override
  public void onCreateBinding(@NonNull final BindingsManager bm) {
    if (null == getView()) return;

    /**
     * EXPLANATION:
     * - we listen to user input in et_password and et_confirm_password
     * - during update of the model instance by entered text we expect Observable callbacks
     * - observable callback force Binding update of UI label - tv_feedback
     *
     * USE CASE:
     * - on wrong password display Error message
     * - on correct password display OK message,
     * - Correct pattern: pin contains at least 4 digits and not more than 6
     */

    // do binding only from UI side, only PUSH
    Binders.strings(bm) // <CharSequence - to - String>
        .view(editText(getView(), R.id.et_password))
        .onView(Listeners.anyOf(onTextChanged(), onFocusLost()))