/*
 * Copyright (C) 2014 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.inject.multibindings;

import static com.google.inject.internal.RealOptionalBinder.newRealOptionalBinder;

import com.google.common.base.Optional;
import com.google.inject.Binder;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.internal.RealOptionalBinder;

/**
 * An API to bind optional values, optionally with a default value. OptionalBinder fulfills two
 * roles:
 *
 * <ol>
 * <li>It allows a framework to define an injection point that may or may not be bound by users.
 * <li>It allows a framework to supply a default value that can be changed by users.
 * </ol>
 *
 * <p>When an OptionalBinder is added, it will always supply the bindings: {@code Optional<T>} and
 * {@code Optional<Provider<T>>}. If {@link #setBinding} or {@link #setDefault} are called, it will
 * also bind {@code T}.
 *
 * <p>{@code setDefault} is intended for use by frameworks that need a default value. User code can
 * call {@code setBinding} to override the default. <b>Warning: Even if setBinding is called, the
 * default binding will still exist in the object graph. If it is a singleton, it will be
 * instantiated in {@code Stage.PRODUCTION}.</b>
 *
 * <p>If setDefault or setBinding are linked to Providers, the Provider may return {@code null}. If
 * it does, the Optional bindings will be absent. Binding setBinding to a Provider that returns null
 * will not cause OptionalBinder to fall back to the setDefault binding.
 *
 * <p>If neither setDefault nor setBinding are called, it will try to link to a user-supplied
 * binding of the same type. If no binding exists, the optionals will be absent. Otherwise, if a
 * user-supplied binding of that type exists, or if setBinding or setDefault are called, the
 * optionals will return present if they are bound to a non-null value.
 *
 * <p>Values are resolved at injection time. If a value is bound to a provider, that provider's get
 * method will be called each time the optional is injected (unless the binding is also scoped, or
 * an optional of provider is injected).
 *
 * <p>Annotations are used to create different optionals of the same key/value type. Each distinct
 * annotation gets its own independent binding.
 *
 * <pre><code>
 * public class FrameworkModule extends AbstractModule {
 *   protected void configure() {
 *     OptionalBinder.newOptionalBinder(binder(), Renamer.class);
 *   }
 * }</code></pre>
 *
 * <p>With this module, an {@link Optional}{@code <Renamer>} can now be injected. With no other
 * bindings, the optional will be absent. Users can specify bindings in one of two ways:
 *
 * <p>Option 1:
 *
 * <pre><code>
 * public class UserRenamerModule extends AbstractModule {
 *   protected void configure() {
 *     bind(Renamer.class).to(ReplacingRenamer.class);
 *   }
 * }</code></pre>
 *
 * <p>or Option 2:
 *
 * <pre><code>
 * public class UserRenamerModule extends AbstractModule {
 *   protected void configure() {
 *     OptionalBinder.newOptionalBinder(binder(), Renamer.class)
 *         .setBinding().to(ReplacingRenamer.class);
 *   }
 * }</code></pre>
 *
 * With both options, the {@code Optional<Renamer>} will be present and supply the ReplacingRenamer.
 *
 * <p>Default values can be supplied using:
 *
 * <pre><code>
 * public class FrameworkModule extends AbstractModule {
 *   protected void configure() {
 *     OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, LookupUrl.class))
 *         .setDefault().toInstance(DEFAULT_LOOKUP_URL);
 *   }
 * }</code></pre>
 *
 * With the above module, code can inject an {@code @LookupUrl String} and it will supply the
 * DEFAULT_LOOKUP_URL. A user can change this value by binding
 *
 * <pre><code>
 * public class UserLookupModule extends AbstractModule {
 *   protected void configure() {
 *     OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, LookupUrl.class))
 *         .setBinding().toInstance(CUSTOM_LOOKUP_URL);
 *   }
 * }</code></pre>
 *
 * ... which will override the default value.
 *
 * <p>If one module uses setDefault the only way to override the default is to use setBinding. It is
 * an error for a user to specify the binding without using OptionalBinder if setDefault or
 * setBinding are called. For example,
 *
 * <pre><code>
 * public class FrameworkModule extends AbstractModule {
 *   protected void configure() {
 *     OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, LookupUrl.class))
 *         .setDefault().toInstance(DEFAULT_LOOKUP_URL);
 *   }
 * }
 * public class UserLookupModule extends AbstractModule {
 *   protected void configure() {
 *     bind(Key.get(String.class, LookupUrl.class)).toInstance(CUSTOM_LOOKUP_URL);
 *   }
 * }</code></pre>
 *
 * ... would generate an error, because both the framework and the user are trying to bind
 * {@code @LookupUrl String}.
 *
 * @author sameb@google.com (Sam Berlin)
 * @since 4.0
 */
public class OptionalBinder<T> {
  // This class is non-final due to users mocking this in tests :(

  public static <T> OptionalBinder<T> newOptionalBinder(Binder binder, Class<T> type) {
    return new OptionalBinder<T>(
        newRealOptionalBinder(binder.skipSources(OptionalBinder.class), Key.get(type)));
  }

  public static <T> OptionalBinder<T> newOptionalBinder(Binder binder, TypeLiteral<T> type) {
    return new OptionalBinder<T>(
        newRealOptionalBinder(binder.skipSources(OptionalBinder.class), Key.get(type)));
  }

  public static <T> OptionalBinder<T> newOptionalBinder(Binder binder, Key<T> type) {
    return new OptionalBinder<T>(
        newRealOptionalBinder(binder.skipSources(OptionalBinder.class), type));
  }

  private final RealOptionalBinder<T> delegate;

  private OptionalBinder(RealOptionalBinder<T> delegate) {
    this.delegate = delegate;
  }

  /**
   * Returns a binding builder used to set the default value that will be injected. The binding set
   * by this method will be ignored if {@link #setBinding} is called.
   *
   * <p>It is an error to call this method without also calling one of the {@code to} methods on the
   * returned binding builder.
   */
  public LinkedBindingBuilder<T> setDefault() {
    return delegate.setDefault();
  }

  /**
   * Returns a binding builder used to set the actual value that will be injected. This overrides
   * any binding set by {@link #setDefault}.
   *
   * <p>It is an error to call this method without also calling one of the {@code to} methods on the
   * returned binding builder.
   */
  public LinkedBindingBuilder<T> setBinding() {
    return delegate.setBinding();
  }

  // Some tests depend on equals/hashCode behavior of OptionalBinder

  @Override
  public boolean equals(Object obj) {
    if (obj instanceof OptionalBinder) {
      return delegate.equals(((OptionalBinder<?>) obj).delegate);
    }
    return false;
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }
}
