/* 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 android.os.Parcel;
import android.os.ParcelFormatException;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.util.ThreadUtils;

/**
 * This class represents a single <a
 * href="https://developer.mozilla.org/en-US/docs/Web/API/Notification">Web Notification</a>. These
 * can be received by connecting a {@link WebNotificationDelegate} to {@link GeckoRuntime} via
 * {@link GeckoRuntime#setWebNotificationDelegate(WebNotificationDelegate)}.
 */
public class WebNotification implements Parcelable {

  /**
   * Title is shown at the top of the notification window.
   *
   * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/Notification/title">Web
   *     Notification - title</a>
   */
  public final @Nullable String title;

  /**
   * Tag is the ID of the notification.
   *
   * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/Notification/tag">Web
   *     Notification - tag</a>
   */
  public final @NonNull String tag;

  private final @Nullable String mCookie;

  /**
   * Text represents the body of the notification.
   *
   * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/Notification/body">Web
   *     Notification - text</a>
   */
  public final @Nullable String text;

  /**
   * ImageURL contains the URL of an icon to be displayed as part of the notification.
   *
   * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/Notification/icon">Web
   *     Notification - icon</a>
   */
  public final @Nullable String imageUrl;

  /**
   * TextDirection indicates the direction that the language of the text is displayed. Possible
   * values are: auto: adopts the browser's language setting behaviour (the default.) ltr: left to
   * right. rtl: right to left.
   *
   * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/Notification/dir">Web
   *     Notification - dir</a>
   */
  public final @Nullable String textDirection;

  /**
   * Lang indicates the notification's language, as specified using a DOMString representing a BCP
   * 47 language tag.
   *
   * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMString">DOM String</a>
   * @see <a href="http://www.rfc-editor.org/rfc/bcp/bcp47.txt">BCP 47</a>
   * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/Notification/lang">Web
   *     Notification - lang</a>
   */
  public final @Nullable String lang;

  /**
   * RequireInteraction indicates whether a notification should remain active until the user clicks
   * or dismisses it, rather than closing automatically.
   *
   * @see <a
   *     href="https://developer.mozilla.org/en-US/docs/Web/API/Notification/requireInteraction">Web
   *     Notification - requireInteraction</a>
   */
  public final @NonNull boolean requireInteraction;

  /**
   * This is the URL of the page or Service Worker that generated the notification. Null if this
   * notification was not generated by a Web Page (e.g. from an Extension).
   *
   * <p>TODO: make NonNull once we have Bug 1589693
   */
  public final @Nullable String source;

  /**
   * When set, indicates that no sounds or vibrations should be made.
   *
   * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/Notification/silent">Web
   *     Notification - silent</a>
   */
  public final boolean silent;

  /** indicates whether the notification came from private browsing mode or not. */
  public final boolean privateBrowsing;

  /**
   * A vibration pattern to run with the display of the notification. A vibration pattern can be an
   * array with as few as one member. The values are times in milliseconds where the even indices
   * (0, 2, 4, etc.) indicate how long to vibrate and the odd indices indicate how long to pause.
   * For example, [300, 100, 400] would vibrate 300ms, pause 100ms, then vibrate 400ms.
   *
   * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/Notification/vibrate">Web
   *     Notification - vibrate</a>
   */
  public final @NonNull int[] vibrate;

  @WrapForJNI
  /* package */ WebNotification(
      @Nullable final String title,
      @NonNull final String tag,
      @Nullable final String cookie,
      @Nullable final String text,
      @Nullable final String imageUrl,
      @Nullable final String textDirection,
      @Nullable final String lang,
      @NonNull final boolean requireInteraction,
      @NonNull final String source,
      final boolean silent,
      final boolean privateBrowsing,
      @NonNull final int[] vibrate) {
    this.tag = tag;
    this.mCookie = cookie;
    this.title = title;
    this.text = text;
    this.imageUrl = imageUrl;
    this.textDirection = textDirection;
    this.lang = lang;
    this.requireInteraction = requireInteraction;
    this.source = "".equals(source) ? null : source;
    this.silent = silent;
    this.vibrate = vibrate;
    this.privateBrowsing = privateBrowsing;
  }

  /**
   * This should be called when the user taps or clicks a notification. Note that this does not
   * automatically dismiss the notification as far as Web Content is concerned. For that, see {@link
   * #dismiss()}.
   */
  @UiThread
  public void click() {
    ThreadUtils.assertOnUiThread();
    GeckoAppShell.onNotificationClick(tag, mCookie);
  }

  /**
   * This should be called when the app stops showing the notification. This is important, as there
   * may be a limit to the number of active notifications each site can display.
   */
  @UiThread
  public void dismiss() {
    ThreadUtils.assertOnUiThread();
    GeckoAppShell.onNotificationClose(tag, mCookie);
  }

  // Increment this value whenever anything changes in the parcelable representation.
  private static final int VERSION = 1;

  // To avoid TransactionTooLargeException, we only store small imageUrls
  private static final int IMAGE_URL_LENGTH_MAX = 150;

  @Override
  public int describeContents() {
    return 0;
  }

  @Override
  public void writeToParcel(final Parcel dest, final int flags) {
    dest.writeInt(VERSION);
    dest.writeString(title);
    dest.writeString(tag);
    dest.writeString(mCookie);
    dest.writeString(text);
    if (imageUrl.length() < IMAGE_URL_LENGTH_MAX) {
      dest.writeString(imageUrl);
    } else {
      dest.writeString("");
    }
    dest.writeString(textDirection);
    dest.writeString(lang);
    dest.writeInt(requireInteraction ? 1 : 0);
    dest.writeString(source);
    dest.writeInt(silent ? 1 : 0);
    dest.writeInt(privateBrowsing ? 1 : 0);
    dest.writeIntArray(vibrate);
  }

  private WebNotification(final Parcel in) {
    title = in.readString();
    tag = in.readString();
    mCookie = in.readString();
    text = in.readString();
    imageUrl = in.readString();
    textDirection = in.readString();
    lang = in.readString();
    requireInteraction = in.readInt() == 1;
    source = in.readString();
    silent = in.readInt() == 1;
    privateBrowsing = in.readInt() == 1;
    vibrate = in.createIntArray();
  }

  public static final Creator<WebNotification> CREATOR =
      new Creator<>() {
        @Override
        public WebNotification createFromParcel(final Parcel in) {
          final int version = in.readInt();
          if (version != VERSION) {
            throw new ParcelFormatException(
                "Mismatched version: " + version + " expected: " + VERSION);
          }
          return new WebNotification(in);
        }

        @Override
        public WebNotification[] newArray(final int size) {
          return new WebNotification[size];
        }
      };
}
