package org.realityforge.arez;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.jetbrains.annotations.TestOnly;

/**
 * Support class to provide access to instances of ArezContext.
 * The {@link ContextProvider} that is bound to this class will determine how this
 * achieved but it may involve using Zones (in a JavaScript runtime) or ThreadLocals
 * (on the JVM). The default implementation provides a singleton Context.
 *
 * <p>This class is not used by any of the tools in Core but provides support for
 * downstream tools such as the models annotated with annotations in
 * {@link org.realityforge.arez.annotations} and generated by processor in
 * {@link org.realityforge.arez.processor}.</p>
 */
@Unsupported( "This is still a work in progress and may be removed in the future" )
public final class Arez
{
  /**
   * The provider that used to access context.
   */
  private static ContextProvider c_provider;

  private Arez()
  {
  }

  /**
   * Interface for supplying an instance of an ArezContext to the caller.,
   */
  @FunctionalInterface
  public interface ContextProvider
  {
    /**
     * Return a ArezContext based on the providers particular strategy.
     *
     * @return the ArezContext.
     */
    @Nonnull
    ArezContext context();
  }

  /**
   * Return the ArezContext from the provider.
   *
   * @return the ArezContext.
   */
  @Nonnull
  public static ArezContext context()
  {
    return getContextProvider().context();
  }

  /**
   * Bind a ContextProvider to Arez.
   * This method should not be called if a provider has already been bound
   * or the method {@link #context()} has already been called (and created
   * the default provider).
   *
   * @param provider the ContextProvider to bind.
   */
  public static void bindProvider( @Nonnull final ContextProvider provider )
  {
    Guards.invariant( () -> null == c_provider,
                      () -> "Attempting to bind ContextProvider " + provider + " but there is already a " +
                            "provider bound as " + c_provider + "." );
    c_provider = provider;
  }

  /**
   * Return the ContextProvider creating the default provider if necessary.
   *
   * @return the ContextProvider.
   */
  @Nonnull
  synchronized static ContextProvider getContextProvider()
  {
    if ( null == c_provider )
    {
      c_provider = new StaticContextProvider();
    }
    return c_provider;
  }

  @TestOnly
  static void setProvider( @Nullable final ContextProvider provider )
  {
    c_provider = provider;
  }

  /**
   * Default implementation of context provider that just returns a singleton context.
   */
  static final class StaticContextProvider
    implements ContextProvider
  {
    private final ArezContext _context = new ArezContext();

    /**
     * {@inheritDoc}
     */
    @Nonnull
    public ArezContext context()
    {
      return _context;
    }
  }
}

package org.realityforge.arez;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.jetbrains.annotations.TestOnly;

/**
 * Support class to provide access to instances of ArezContext.
 * The {@link ContextProvider} that is bound to this class will determine how this
 * achieved but it may involve using Zones (in a JavaScript runtime) or ThreadLocals
 * (on the JVM). The default implementation provides a singleton Context.
 *
 * <p>This class is not used by any of the tools in Core but provides support for
 * downstream tools such as the models annotated with annotations in the
 * org.realityforge.arez.annotations package and generated by the annotation processor
 * in the org.realityforge.arez.processor package.</p>
 */
@Unsupported( "This is still a work in progress and may be removed in the future" )
public final class Arez
{
  /**
   * The provider that used to access context.
   */
  private static ContextProvider c_provider;

  private Arez()
  {
  }

  /**
   * Interface for supplying an instance of an ArezContext to the caller.,
   */
  @FunctionalInterface
  public interface ContextProvider
  {
    /**
     * Return a ArezContext based on the providers particular strategy.
     *
     * @return the ArezContext.
     */
    @Nonnull
    ArezContext context();
  }

  /**
   * Return the ArezContext from the provider.
   *
   * @return the ArezContext.
   */
  @Nonnull
  public static ArezContext context()
  {
    return getContextProvider().context();
  }

  /**
   * Bind a ContextProvider to Arez.
   * This method should not be called if a provider has already been bound
   * or the method {@link #context()} has already been called (and created
   * the default provider).
   *
   * @param provider the ContextProvider to bind.
   */
  public static void bindProvider( @Nonnull final ContextProvider provider )
  {
    Guards.invariant( () -> null == c_provider,
                      () -> "Attempting to bind ContextProvider " + provider + " but there is already a " +
                            "provider bound as " + c_provider + "." );
    c_provider = provider;
  }

  /**
   * Return the ContextProvider creating the default provider if necessary.
   *
   * @return the ContextProvider.
   */
  @Nonnull
  synchronized static ContextProvider getContextProvider()
  {
    if ( null == c_provider )
    {
      c_provider = new StaticContextProvider();
    }
    return c_provider;
  }

  @TestOnly
  static void setProvider( @Nullable final ContextProvider provider )
  {
    c_provider = provider;
  }

  /**
   * Default implementation of context provider that just returns a singleton context.
   */
  static final class StaticContextProvider
    implements ContextProvider
  {
    private final ArezContext _context = new ArezContext();

    /**
     * {@inheritDoc}
     */
    @Nonnull
    public ArezContext context()
    {
      return _context;
    }
  }
}

Fix javadocs