/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
 * vim: ts=4 sw=4 expandtab:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.geckoview;

import static org.mozilla.geckoview.ExperimentDelegate.ExperimentException.ERROR_EXPERIMENT_DELEGATE_NOT_IMPLEMENTED;

import androidx.annotation.AnyThread;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.json.JSONObject;

/**
 * This delegate is used to pass experiment information between the embedding application and
 * GeckoView.
 *
 * <p>An experiment is used to give users different application behavior in order to learn and
 * improve upon what features users prefer the most. This is accomplished by providing users
 * different application experiences and collecting data about how the differing experiences
 * impacted user behavior.
 */
public interface ExperimentDelegate {
  /**
   * Used to retrieve experiment information for the given feature identification.
   *
   * <p>A @param feature is the item or experience the experimented is about. For example, "prompt"
   * or "print" could be a feature.
   *
   * <p>The @return experiment information will be information on what the application should do for
   * the experiment. This is highly context dependent on how the experiment was setup and is decided
   * and controlled by the experiment framework. For example, a feature of "prompt" may return
   * {dismiss-button: {color: "red", full-screen: true}} or "print" may return {dotprint-enabled:
   * true}. That information can then be used to present differing behavior for the user.
   *
   * @param feature The name or identification of the experiment feature.
   * @return A {@link GeckoResult<JSONObject>} with experiment criteria. Typically will have a value
   *     related to showing or adjusting a feature. Will complete exceptionally with {@link
   *     ExperimentException} if the feature wasn't found.
   */
  @AnyThread
  default @NonNull GeckoResult<JSONObject> onGetExperimentFeature(@NonNull String feature) {
    final GeckoResult<JSONObject> result = new GeckoResult<>();
    result.completeExceptionally(
        new ExperimentException(ERROR_EXPERIMENT_DELEGATE_NOT_IMPLEMENTED));
    return result;
  }

  /**
   * Used to let the experiment framework know that the user was shown the feature. Should be
   * recorded as close as possible to the differing behavior.
   *
   * <p>One important part of experimentation is knowing when users encountered an experiment
   * surface or difference in behavior. Sending an exposure event is recording with the experiment
   * framework that the user encountered a differing behavior.
   *
   * <p>For example, if a user never encountered a @param feature "prompt", then the exposure event
   * would never be recorded. However, if the user does encounter a "prompt", then the experiment
   * framework needs a record that the user encountered the experiment surface.
   *
   * @param feature The name or identification the experiment feature.
   * @return A {@link GeckoResult<Void>} will complete if the feature was found and exposure
   *     recorded. Will complete exceptionally with {@link ExperimentException} if the feature
   *     wasn't found.
   */
  @AnyThread
  default @NonNull GeckoResult<Void> onRecordExposureEvent(@NonNull String feature) {
    final GeckoResult<Void> result = new GeckoResult<>();
    result.completeExceptionally(
        new ExperimentException(ERROR_EXPERIMENT_DELEGATE_NOT_IMPLEMENTED));
    return result;
  }

  /**
   * Used to let the experiment framework know that the user was shown the feature in a given
   * experiment. Should be recorded as close as possible to the differing behavior.
   *
   * <p>Use [onRecordExposureEvent], if there is no experiment slug.
   *
   * <p>This API is used similarly to [onRecordExposureEvent], but when a specific feature was
   * encountered. For example a @param feature may be "prompt" and a given @param slug may be
   * "dismiss" or "confirm". This is used to indicate a specific experiment surface was encountered.
   *
   * @param feature The name or identification the experiment feature.
   * @param slug The name or identification of the specific experiment feature.
   * @return A {@link GeckoResult<Void>} will complete if the feature was found and exposure
   *     recorded. Will complete exceptionally with {@link ExperimentException} if the feature
   *     wasn't found or not recorded.
   */
  @AnyThread
  default @NonNull GeckoResult<Void> onRecordExperimentExposureEvent(
      @NonNull String feature, @NonNull String slug) {
    final GeckoResult<Void> result = new GeckoResult<>();
    result.completeExceptionally(
        new ExperimentException(ERROR_EXPERIMENT_DELEGATE_NOT_IMPLEMENTED));
    return result;
  }

  /**
   * Used to let the experiment framework send a malformed configuration event when the feature
   * configuration is not semantically valid.
   *
   * @param feature The name or identification the experiment feature.
   * @param part An optional detail or part identifier to be attached to the event.
   * @return A {@link GeckoResult<Void>} will complete if the feature was found and the event
   *     recorded. Will complete exceptionally with {@link ExperimentException} if the feature
   *     wasn't found or not recorded.
   */
  @AnyThread
  default @NonNull GeckoResult<Void> onRecordMalformedConfigurationEvent(
      @NonNull String feature, @NonNull String part) {
    final GeckoResult<Void> result = new GeckoResult<>();
    result.completeExceptionally(
        new ExperimentException(ERROR_EXPERIMENT_DELEGATE_NOT_IMPLEMENTED));
    return result;
  }

  /**
   * An exception to be used when there is an issue retrieving or sending information to the
   * experiment framework.
   */
  class ExperimentException extends Exception {

    /**
     * Construct an [ExperimentException]
     *
     * @param code error code the given exception corresponds to
     */
    public ExperimentException(final @Codes int code) {
      this.code = code;
    }

    /** Default error for unexpected issues. */
    public static final int ERROR_UNKNOWN = -1;

    /** The experiment feature was not available. */
    public static final int ERROR_FEATURE_NOT_FOUND = -2;

    /** The experiment slug was not available. */
    public static final int ERROR_EXPERIMENT_SLUG_NOT_FOUND = -3;

    /** The experiment delegate is not implemented. */
    public static final int ERROR_EXPERIMENT_DELEGATE_NOT_IMPLEMENTED = -4;

    /** Experiment exception error codes. */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(
        value = {
          ERROR_UNKNOWN,
          ERROR_FEATURE_NOT_FOUND,
          ERROR_EXPERIMENT_SLUG_NOT_FOUND,
          ERROR_EXPERIMENT_DELEGATE_NOT_IMPLEMENTED
        })
    public @interface Codes {}

    /** One of {@link Codes} that provides more information about this exception. */
    public final @Codes int code;

    @Override
    public String toString() {
      return "ExperimentException: " + code;
    }
  }
}
