// Copyright 2004-present Facebook. All Rights Reserved.

package com.facebook.react;

import javax.annotation.Nullable;

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.app.Application;

import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.cxxbridge.JSBundleLoader;
import com.facebook.react.devsupport.DevSupportManager;
import com.facebook.react.devsupport.RedBoxHandler;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.uimanager.UIImplementationProvider;

/**
 * Builder class for {@link ReactInstanceManager}
 */
public class ReactInstanceManagerBuilder {

  protected final List<ReactPackage> mPackages = new ArrayList<>();

  protected @Nullable String mJSBundleAssetUrl;
  protected @Nullable JSBundleLoader mJSBundleLoader;
  protected @Nullable String mJSMainModuleName;
  protected @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener;
  protected @Nullable Application mApplication;
  protected boolean mUseDeveloperSupport;
  protected @Nullable LifecycleState mInitialLifecycleState;
  protected @Nullable UIImplementationProvider mUIImplementationProvider;
  protected @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
  protected JSCConfig mJSCConfig = JSCConfig.EMPTY;
  protected @Nullable Activity mCurrentActivity;
  protected @Nullable DefaultHardwareBackBtnHandler mDefaultHardwareBackBtnHandler;
  protected @Nullable RedBoxHandler mRedBoxHandler;
  protected boolean mLazyNativeModulesEnabled;
  protected boolean mLazyViewManagersEnabled;

  /* package protected */ ReactInstanceManagerBuilder() {
  }

  /**
   * Sets a provider of {@link UIImplementation}.
   * Uses default provider if null is passed.
   */
  public ReactInstanceManagerBuilder setUIImplementationProvider(
    @Nullable UIImplementationProvider uiImplementationProvider) {
    mUIImplementationProvider = uiImplementationProvider;
    return this;
  }

  /**
   * Name of the JS bundle file to be loaded from application's raw assets.
   * Example: {@code "index.android.js"}
   */
  public ReactInstanceManagerBuilder setBundleAssetName(String bundleAssetName) {
    mJSBundleAssetUrl = (bundleAssetName == null ? null : "assets://" + bundleAssetName);
    mJSBundleLoader = null;
    return this;
  }

  /**
   * Path to the JS bundle file to be loaded from the file system.
   *
   * Example: {@code "assets://index.android.js" or "/sdcard/main.jsbundle"}
   */
  public ReactInstanceManagerBuilder setJSBundleFile(String jsBundleFile) {
    if (jsBundleFile.startsWith("assets://")) {
      mJSBundleAssetUrl = jsBundleFile;
      mJSBundleLoader = null;
      return this;
    }
    return setJSBundleLoader(JSBundleLoader.createFileLoader(jsBundleFile));
  }

  /**
   * Bundle loader to use when setting up JS environment. This supersedes
   * prior invcations of {@link setJSBundleFile} and {@link setBundleAssetName}.
   *
   * Example: {@code JSBundleLoader.createFileLoader(application, bundleFile)}
   */
  public ReactInstanceManagerBuilder setJSBundleLoader(JSBundleLoader jsBundleLoader) {
    mJSBundleLoader = jsBundleLoader;
    mJSBundleAssetUrl = null;
    return this;
  }

  /**
   * Path to your app's main module on the packager server. This is used when
   * reloading JS during development. All paths are relative to the root folder
   * the packager is serving files from.
   * Examples:
   * {@code "index.android"} or
   * {@code "subdirectory/index.android"}
   */
  public ReactInstanceManagerBuilder setJSMainModuleName(String jsMainModuleName) {
    mJSMainModuleName = jsMainModuleName;
    return this;
  }

  public ReactInstanceManagerBuilder addPackage(ReactPackage reactPackage) {
    mPackages.add(reactPackage);
    return this;
  }

  public ReactInstanceManagerBuilder setBridgeIdleDebugListener(
    NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener) {
    mBridgeIdleDebugListener = bridgeIdleDebugListener;
    return this;
  }

  /**
   * Required. This must be your {@code Application} instance.
   */
  public ReactInstanceManagerBuilder setApplication(Application application) {
    mApplication = application;
    return this;
  }

  public ReactInstanceManagerBuilder setCurrentActivity(Activity activity) {
    mCurrentActivity = activity;
    return this;
  }

  public ReactInstanceManagerBuilder setDefaultHardwareBackBtnHandler(
    DefaultHardwareBackBtnHandler defaultHardwareBackBtnHandler) {
    mDefaultHardwareBackBtnHandler = defaultHardwareBackBtnHandler;
    return this;
  }

  /**
   * When {@code true}, developer options such as JS reloading and debugging are enabled.
   * Note you still have to call {@link #showDevOptionsDialog} to show the dev menu,
   * e.g. when the device Menu button is pressed.
   */
  public ReactInstanceManagerBuilder setUseDeveloperSupport(boolean useDeveloperSupport) {
    mUseDeveloperSupport = useDeveloperSupport;
    return this;
  }

  /**
   * Sets the initial lifecycle state of the host. For example, if the host is already resumed at
   * creation time, we wouldn't expect an onResume call until we get an onPause call.
   */
  public ReactInstanceManagerBuilder setInitialLifecycleState(
    LifecycleState initialLifecycleState) {
    mInitialLifecycleState = initialLifecycleState;
    return this;
  }

  /**
   * Set the exception handler for all native module calls. If not set, the default
   * {@link DevSupportManager} will be used, which shows a redbox in dev mode and rethrows
   * (crashes the app) in prod mode.
   */
  public ReactInstanceManagerBuilder setNativeModuleCallExceptionHandler(
    NativeModuleCallExceptionHandler handler) {
    mNativeModuleCallExceptionHandler = handler;
    return this;
  }

  public ReactInstanceManagerBuilder setJSCConfig(JSCConfig jscConfig) {
    mJSCConfig = jscConfig;
    return this;
  }

  public ReactInstanceManagerBuilder setRedBoxHandler(@Nullable RedBoxHandler redBoxHandler) {
    mRedBoxHandler = redBoxHandler;
    return this;
  }

  public ReactInstanceManagerBuilder setLazyNativeModulesEnabled(boolean lazyNativeModulesEnabled) {
    mLazyNativeModulesEnabled = lazyNativeModulesEnabled;
    return this;
  }

  public ReactInstanceManagerBuilder setLazyViewManagersEnabled(boolean lazyViewManagersEnabled) {
    mLazyViewManagersEnabled = lazyViewManagersEnabled;
    return this;
  }

  /**
   * Instantiates a new {@link ReactInstanceManager}.
   * Before calling {@code build}, the following must be called:
   * <ul>
   * <li> {@link #setApplication}
   * <li> {@link #setCurrentActivity} if the activity has already resumed
   * <li> {@link #setDefaultHardwareBackBtnHandler} if the activity has already resumed
   * <li> {@link #setJSBundleFile} or {@link #setJSMainModuleName}
   * </ul>
   */
  public ReactInstanceManager build() {
    Assertions.assertNotNull(
      mApplication,
      "Application property has not been set with this builder");

    Assertions.assertCondition(
      mUseDeveloperSupport || mJSBundleAssetUrl != null || mJSBundleLoader != null,
      "JS Bundle File or Asset URL has to be provided when dev support is disabled");

    Assertions.assertCondition(
      mJSMainModuleName != null || mJSBundleAssetUrl != null || mJSBundleLoader != null,
      "Either MainModuleName or JS Bundle File needs to be provided");

    if (mUIImplementationProvider == null) {
      // create default UIImplementationProvider if the provided one is null.
      mUIImplementationProvider = new UIImplementationProvider();
    }

    return new ReactInstanceManager(
      mApplication,
      mCurrentActivity,
      mDefaultHardwareBackBtnHandler,
      (mJSBundleLoader == null && mJSBundleAssetUrl != null) ?
        JSBundleLoader.createAssetLoader(mApplication, mJSBundleAssetUrl) : mJSBundleLoader,
      mJSMainModuleName,
      mPackages,
      mUseDeveloperSupport,
      mBridgeIdleDebugListener,
      Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"),
      mUIImplementationProvider,
      mNativeModuleCallExceptionHandler,
      mJSCConfig,
      mRedBoxHandler,
      mLazyNativeModulesEnabled,
      mLazyViewManagersEnabled);
  }
}
