/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
 * 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_example;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.net.Uri;
import android.os.Build;
import android.text.InputType;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckedTextView;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ScrollView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.TimePicker;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import org.mozilla.geckoview.AllowOrDeny;
import org.mozilla.geckoview.Autocomplete;
import org.mozilla.geckoview.GeckoResult;
import org.mozilla.geckoview.GeckoSession;
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaSource;
import org.mozilla.geckoview.SlowScriptResponse;

final class BasicGeckoViewPrompt implements GeckoSession.PromptDelegate {
  protected static final String LOGTAG = "BasicGeckoViewPrompt";

  private final Activity mActivity;
  public int filePickerRequestCode = 1;
  private int mFileType;
  private GeckoResult<PromptResponse> mFileResponse;
  private FilePrompt mFilePrompt;

  public BasicGeckoViewPrompt(final Activity activity) {
    mActivity = activity;
  }

  @Override
  public GeckoResult<PromptResponse> onAlertPrompt(
      final GeckoSession session, final AlertPrompt prompt) {
    final Activity activity = mActivity;
    if (activity == null) {
      return GeckoResult.fromValue(prompt.dismiss());
    }
    final AlertDialog.Builder builder =
        new AlertDialog.Builder(activity)
            .setTitle(prompt.title)
            .setMessage(prompt.message)
            .setPositiveButton(android.R.string.ok, /* onClickListener */ null);
    GeckoResult<PromptResponse> res = new GeckoResult<PromptResponse>();
    createStandardDialog(builder, prompt, res).show();
    return res;
  }

  @Override
  public GeckoResult<PromptResponse> onButtonPrompt(
      final GeckoSession session, final ButtonPrompt prompt) {
    final Activity activity = mActivity;
    if (activity == null) {
      return GeckoResult.fromValue(prompt.dismiss());
    }
    final AlertDialog.Builder builder =
        new AlertDialog.Builder(activity).setTitle(prompt.title).setMessage(prompt.message);

    GeckoResult<PromptResponse> res = new GeckoResult<PromptResponse>();

    final DialogInterface.OnClickListener listener =
        new DialogInterface.OnClickListener() {
          @Override
          public void onClick(final DialogInterface dialog, final int which) {
            if (which == DialogInterface.BUTTON_POSITIVE) {
              res.complete(prompt.confirm(ButtonPrompt.Type.POSITIVE));
            } else if (which == DialogInterface.BUTTON_NEGATIVE) {
              res.complete(prompt.confirm(ButtonPrompt.Type.NEGATIVE));
            } else {
              res.complete(prompt.dismiss());
            }
          }
        };

    builder.setPositiveButton(android.R.string.ok, listener);
    builder.setNegativeButton(android.R.string.cancel, listener);

    createStandardDialog(builder, prompt, res).show();
    return res;
  }

  @Override
  public GeckoResult<PromptResponse> onSharePrompt(
      final GeckoSession session, final SharePrompt prompt) {
    return GeckoResult.fromValue(prompt.dismiss());
  }

  @Nullable
  @Override
  public GeckoResult<PromptResponse> onRepostConfirmPrompt(
      final GeckoSession session, final RepostConfirmPrompt prompt) {
    final Activity activity = mActivity;
    if (activity == null) {
      return GeckoResult.fromValue(prompt.dismiss());
    }
    final AlertDialog.Builder builder =
        new AlertDialog.Builder(activity)
            .setTitle(R.string.repost_confirm_title)
            .setMessage(R.string.repost_confirm_message);

    GeckoResult<PromptResponse> res = new GeckoResult<>();

    final DialogInterface.OnClickListener listener =
        (dialog, which) -> {
          if (which == DialogInterface.BUTTON_POSITIVE) {
            res.complete(prompt.confirm(AllowOrDeny.ALLOW));
          } else if (which == DialogInterface.BUTTON_NEGATIVE) {
            res.complete(prompt.confirm(AllowOrDeny.DENY));
          } else {
            res.complete(prompt.dismiss());
          }
        };

    builder.setPositiveButton(R.string.repost_confirm_resend, listener);
    builder.setNegativeButton(R.string.repost_confirm_cancel, listener);

    createStandardDialog(builder, prompt, res).show();
    return res;
  }

  @Nullable
  @Override
  public GeckoResult<PromptResponse> onCreditCardSave(
      @NonNull GeckoSession session,
      @NonNull AutocompleteRequest<Autocomplete.CreditCardSaveOption> request) {
    Log.i(LOGTAG, "onCreditCardSave " + request.options[0].value);
    return null;
  }

  @Nullable
  @Override
  public GeckoResult<PromptResponse> onLoginSave(
      @NonNull GeckoSession session,
      @NonNull AutocompleteRequest<Autocomplete.LoginSaveOption> request) {
    Log.i(LOGTAG, "onLoginSave");
    return GeckoResult.fromValue(request.confirm(request.options[0]));
  }

  @Nullable
  @Override
  public GeckoResult<PromptResponse> onBeforeUnloadPrompt(
      final GeckoSession session, final BeforeUnloadPrompt prompt) {
    final Activity activity = mActivity;
    if (activity == null) {
      return GeckoResult.fromValue(prompt.dismiss());
    }
    final AlertDialog.Builder builder =
        new AlertDialog.Builder(activity)
            .setTitle(R.string.before_unload_title)
            .setMessage(R.string.before_unload_message);

    GeckoResult<PromptResponse> res = new GeckoResult<>();

    final DialogInterface.OnClickListener listener =
        (dialog, which) -> {
          if (which == DialogInterface.BUTTON_POSITIVE) {
            res.complete(prompt.confirm(AllowOrDeny.ALLOW));
          } else if (which == DialogInterface.BUTTON_NEGATIVE) {
            res.complete(prompt.confirm(AllowOrDeny.DENY));
          } else {
            res.complete(prompt.dismiss());
          }
        };

    builder.setPositiveButton(R.string.before_unload_leave_page, listener);
    builder.setNegativeButton(R.string.before_unload_stay, listener);

    createStandardDialog(builder, prompt, res).show();
    return res;
  }

  private int getViewPadding(final AlertDialog.Builder builder) {
    final TypedArray attr =
        builder
            .getContext()
            .obtainStyledAttributes(new int[] {android.R.attr.listPreferredItemPaddingLeft});
    final int padding = attr.getDimensionPixelSize(0, 1);
    attr.recycle();
    return padding;
  }

  private LinearLayout addStandardLayout(
      final AlertDialog.Builder builder, final String title, final String msg) {
    final ScrollView scrollView = new ScrollView(builder.getContext());
    final LinearLayout container = new LinearLayout(builder.getContext());
    final int horizontalPadding = getViewPadding(builder);
    final int verticalPadding = (msg == null || msg.isEmpty()) ? horizontalPadding : 0;
    container.setOrientation(LinearLayout.VERTICAL);
    container.setPadding(
        /* left */ horizontalPadding, /* top */ verticalPadding,
        /* right */ horizontalPadding, /* bottom */ verticalPadding);
    scrollView.addView(container);
    builder.setTitle(title).setMessage(msg).setView(scrollView);
    return container;
  }

  private AlertDialog createStandardDialog(
      final AlertDialog.Builder builder,
      final BasePrompt prompt,
      final GeckoResult<PromptResponse> response) {
    final AlertDialog dialog = builder.create();
    dialog.setOnDismissListener(
        new DialogInterface.OnDismissListener() {
          @Override
          public void onDismiss(final DialogInterface dialog) {
            if (!prompt.isComplete()) {
              response.complete(prompt.dismiss());
            }
          }
        });
    return dialog;
  }

  @Override
  public GeckoResult<PromptResponse> onTextPrompt(
      final GeckoSession session, final TextPrompt prompt) {
    final Activity activity = mActivity;
    if (activity == null) {
      return GeckoResult.fromValue(prompt.dismiss());
    }
    final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
    final LinearLayout container = addStandardLayout(builder, prompt.title, prompt.message);
    final EditText editText = new EditText(builder.getContext());
    editText.setText(prompt.defaultValue);
    container.addView(editText);

    GeckoResult<PromptResponse> res = new GeckoResult<PromptResponse>();

    builder
        .setNegativeButton(android.R.string.cancel, /* listener */ null)
        .setPositiveButton(
            android.R.string.ok,
            new DialogInterface.OnClickListener() {
              @Override
              public void onClick(final DialogInterface dialog, final int which) {
                res.complete(prompt.confirm(editText.getText().toString()));
              }
            });

    createStandardDialog(builder, prompt, res).show();
    return res;
  }

  @Override
  public GeckoResult<PromptResponse> onAuthPrompt(
      final GeckoSession session, final AuthPrompt prompt) {
    final Activity activity = mActivity;
    if (activity == null) {
      return GeckoResult.fromValue(prompt.dismiss());
    }
    final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
    final LinearLayout container = addStandardLayout(builder, prompt.title, prompt.message);

    final int flags = prompt.authOptions.flags;
    final int level = prompt.authOptions.level;
    final EditText username;
    if ((flags & AuthPrompt.AuthOptions.Flags.ONLY_PASSWORD) == 0) {
      username = new EditText(builder.getContext());
      username.setHint(R.string.username);
      username.setText(prompt.authOptions.username);
      container.addView(username);
    } else {
      username = null;
    }

    final EditText password = new EditText(builder.getContext());
    password.setHint(R.string.password);
    password.setText(prompt.authOptions.password);
    password.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
    container.addView(password);

    if (level != AuthPrompt.AuthOptions.Level.NONE) {
      final ImageView secure = new ImageView(builder.getContext());
      secure.setImageResource(android.R.drawable.ic_lock_lock);
      container.addView(secure);
    }

    GeckoResult<PromptResponse> res = new GeckoResult<PromptResponse>();

    builder
        .setNegativeButton(android.R.string.cancel, /* listener */ null)
        .setPositiveButton(
            android.R.string.ok,
            new DialogInterface.OnClickListener() {
              @Override
              public void onClick(final DialogInterface dialog, final int which) {
                if ((flags & AuthPrompt.AuthOptions.Flags.ONLY_PASSWORD) == 0) {
                  res.complete(
                      prompt.confirm(username.getText().toString(), password.getText().toString()));

                } else {
                  res.complete(prompt.confirm(password.getText().toString()));
                }
              }
            });
    createStandardDialog(builder, prompt, res).show();

    return res;
  }

  private static class ModifiableChoice {
    public boolean modifiableSelected;
    public String modifiableLabel;
    public final ChoicePrompt.Choice choice;

    public ModifiableChoice(ChoicePrompt.Choice c) {
      choice = c;
      modifiableSelected = choice.selected;
      modifiableLabel = choice.label;
    }
  }

  private void addChoiceItems(
      final int type,
      final ArrayAdapter<ModifiableChoice> list,
      final ChoicePrompt.Choice[] items,
      final String indent) {
    if (type == ChoicePrompt.Type.MENU) {
      for (final ChoicePrompt.Choice item : items) {
        list.add(new ModifiableChoice(item));
      }
      return;
    }

    for (final ChoicePrompt.Choice item : items) {
      final ModifiableChoice modItem = new ModifiableChoice(item);

      final ChoicePrompt.Choice[] children = item.items;

      if (indent != null && children == null) {
        modItem.modifiableLabel = indent + modItem.modifiableLabel;
      }
      list.add(modItem);

      if (children != null) {
        final String newIndent;
        if (type == ChoicePrompt.Type.SINGLE || type == ChoicePrompt.Type.MULTIPLE) {
          newIndent = (indent != null) ? indent + '\t' : "\t";
        } else {
          newIndent = null;
        }
        addChoiceItems(type, list, children, newIndent);
      }
    }
  }

  private void onChoicePromptImpl(
      final GeckoSession session,
      final String title,
      final String message,
      final int type,
      final ChoicePrompt.Choice[] choices,
      final ChoicePrompt prompt,
      final GeckoResult<PromptResponse> res) {
    final Activity activity = mActivity;
    if (activity == null) {
      res.complete(prompt.dismiss());
      return;
    }
    final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
    addStandardLayout(builder, title, message);

    final ListView list = new ListView(builder.getContext());
    if (type == ChoicePrompt.Type.MULTIPLE) {
      list.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
    }

    final ArrayAdapter<ModifiableChoice> adapter =
        new ArrayAdapter<ModifiableChoice>(
            builder.getContext(), android.R.layout.simple_list_item_1) {
          private static final int TYPE_MENU_ITEM = 0;
          private static final int TYPE_MENU_CHECK = 1;
          private static final int TYPE_SEPARATOR = 2;
          private static final int TYPE_GROUP = 3;
          private static final int TYPE_SINGLE = 4;
          private static final int TYPE_MULTIPLE = 5;
          private static final int TYPE_COUNT = 6;

          private LayoutInflater mInflater;
          private View mSeparator;

          @Override
          public int getViewTypeCount() {
            return TYPE_COUNT;
          }

          @Override
          public int getItemViewType(final int position) {
            final ModifiableChoice item = getItem(position);
            if (item.choice.separator) {
              return TYPE_SEPARATOR;
            } else if (type == ChoicePrompt.Type.MENU) {
              return item.modifiableSelected ? TYPE_MENU_CHECK : TYPE_MENU_ITEM;
            } else if (item.choice.items != null) {
              return TYPE_GROUP;
            } else if (type == ChoicePrompt.Type.SINGLE) {
              return TYPE_SINGLE;
            } else if (type == ChoicePrompt.Type.MULTIPLE) {
              return TYPE_MULTIPLE;
            } else {
              throw new UnsupportedOperationException();
            }
          }

          @Override
          public boolean isEnabled(final int position) {
            final ModifiableChoice item = getItem(position);
            return !item.choice.separator
                && !item.choice.disabled
                && ((type != ChoicePrompt.Type.SINGLE && type != ChoicePrompt.Type.MULTIPLE)
                    || item.choice.items == null);
          }

          @Override
          public View getView(final int position, View view, final ViewGroup parent) {
            final int itemType = getItemViewType(position);
            final int layoutId;
            if (itemType == TYPE_SEPARATOR) {
              if (mSeparator == null) {
                mSeparator = new View(getContext());
                mSeparator.setLayoutParams(
                    new ListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 2, itemType));
                final TypedArray attr =
                    getContext().obtainStyledAttributes(new int[] {android.R.attr.listDivider});
                mSeparator.setBackgroundResource(attr.getResourceId(0, 0));
                attr.recycle();
              }
              return mSeparator;
            } else if (itemType == TYPE_MENU_ITEM) {
              layoutId = android.R.layout.simple_list_item_1;
            } else if (itemType == TYPE_MENU_CHECK) {
              layoutId = android.R.layout.simple_list_item_checked;
            } else if (itemType == TYPE_GROUP) {
              layoutId = android.R.layout.preference_category;
            } else if (itemType == TYPE_SINGLE) {
              layoutId = android.R.layout.simple_list_item_single_choice;
            } else if (itemType == TYPE_MULTIPLE) {
              layoutId = android.R.layout.simple_list_item_multiple_choice;
            } else {
              throw new UnsupportedOperationException();
            }

            if (view == null) {
              if (mInflater == null) {
                mInflater = LayoutInflater.from(builder.getContext());
              }
              view = mInflater.inflate(layoutId, parent, false);
            }

            final ModifiableChoice item = getItem(position);
            final TextView text = (TextView) view;
            text.setEnabled(!item.choice.disabled);
            text.setText(item.modifiableLabel);
            if (view instanceof CheckedTextView) {
              final boolean selected = item.modifiableSelected;
              if (itemType == TYPE_MULTIPLE) {
                list.setItemChecked(position, selected);
              } else {
                ((CheckedTextView) view).setChecked(selected);
              }
            }
            return view;
          }
        };
    addChoiceItems(type, adapter, choices, /* indent */ null);

    list.setAdapter(adapter);
    builder.setView(list);

    final AlertDialog dialog;
    if (type == ChoicePrompt.Type.SINGLE || type == ChoicePrompt.Type.MENU) {
      dialog = createStandardDialog(builder, prompt, res);
      list.setOnItemClickListener(
          new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(
                final AdapterView<?> parent, final View v, final int position, final long id) {
              final ModifiableChoice item = adapter.getItem(position);
              if (type == ChoicePrompt.Type.MENU) {
                final ChoicePrompt.Choice[] children = item.choice.items;
                if (children != null) {
                  // Show sub-menu.
                  dialog.setOnDismissListener(null);
                  dialog.dismiss();
                  onChoicePromptImpl(
                      session, item.modifiableLabel, /* msg */ null, type, children, prompt, res);
                  return;
                }
              }
              res.complete(prompt.confirm(item.choice));
              dialog.dismiss();
            }
          });
    } else if (type == ChoicePrompt.Type.MULTIPLE) {
      list.setOnItemClickListener(
          new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(
                final AdapterView<?> parent, final View v, final int position, final long id) {
              final ModifiableChoice item = adapter.getItem(position);
              item.modifiableSelected = ((CheckedTextView) v).isChecked();
            }
          });
      builder
          .setNegativeButton(android.R.string.cancel, /* listener */ null)
          .setPositiveButton(
              android.R.string.ok,
              new DialogInterface.OnClickListener() {
                @Override
                public void onClick(final DialogInterface dialog, final int which) {
                  final int len = adapter.getCount();
                  ArrayList<String> items = new ArrayList<>(len);
                  for (int i = 0; i < len; i++) {
                    final ModifiableChoice item = adapter.getItem(i);
                    if (item.modifiableSelected) {
                      items.add(item.choice.id);
                    }
                  }
                  res.complete(prompt.confirm(items.toArray(new String[items.size()])));
                }
              });
      dialog = createStandardDialog(builder, prompt, res);
    } else {
      throw new UnsupportedOperationException();
    }
    dialog.show();

    prompt.setDelegate(
        new PromptInstanceDelegate() {
          @Override
          public void onPromptDismiss(final BasePrompt prompt) {
            dialog.dismiss();
          }

          @Override
          public void onPromptUpdate(final BasePrompt prompt) {
            dialog.setOnDismissListener(null);
            dialog.dismiss();
            final ChoicePrompt newPrompt = (ChoicePrompt) prompt;
            onChoicePromptImpl(
                session,
                newPrompt.title,
                newPrompt.message,
                newPrompt.type,
                newPrompt.choices,
                newPrompt,
                res);
          }
        });
  }

  @Override
  public GeckoResult<PromptResponse> onChoicePrompt(
      final GeckoSession session, final ChoicePrompt prompt) {
    final GeckoResult<PromptResponse> res = new GeckoResult<PromptResponse>();
    onChoicePromptImpl(
        session, prompt.title, prompt.message, prompt.type, prompt.choices, prompt, res);
    return res;
  }

  private static int parseColor(final String value, final int def) {
    try {
      return Color.parseColor(value);
    } catch (final IllegalArgumentException e) {
      return def;
    }
  }

  @Override
  public GeckoResult<PromptResponse> onColorPrompt(
      final GeckoSession session, final ColorPrompt prompt) {
    final Activity activity = mActivity;
    if (activity == null) {
      return GeckoResult.fromValue(prompt.dismiss());
    }
    final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
    addStandardLayout(builder, prompt.title, /* msg */ null);

    final int initial = parseColor(prompt.defaultValue, /* def */ 0);
    final ArrayAdapter<Integer> adapter =
        new ArrayAdapter<Integer>(builder.getContext(), android.R.layout.simple_list_item_1) {
          private LayoutInflater mInflater;

          @Override
          public int getViewTypeCount() {
            return 2;
          }

          @Override
          public int getItemViewType(final int position) {
            return (getItem(position) == initial) ? 1 : 0;
          }

          @Override
          public View getView(final int position, View view, final ViewGroup parent) {
            if (mInflater == null) {
              mInflater = LayoutInflater.from(builder.getContext());
            }
            final int color = getItem(position);
            if (view == null) {
              view =
                  mInflater.inflate(
                      (color == initial)
                          ? android.R.layout.simple_list_item_checked
                          : android.R.layout.simple_list_item_1,
                      parent,
                      false);
            }
            view.setBackgroundResource(android.R.drawable.editbox_background);
            view.getBackground().setColorFilter(color, PorterDuff.Mode.MULTIPLY);
            return view;
          }
        };

    adapter.addAll(
        0xffff4444 /* holo_red_light */,
        0xffcc0000 /* holo_red_dark */,
        0xffffbb33 /* holo_orange_light */,
        0xffff8800 /* holo_orange_dark */,
        0xff99cc00 /* holo_green_light */,
        0xff669900 /* holo_green_dark */,
        0xff33b5e5 /* holo_blue_light */,
        0xff0099cc /* holo_blue_dark */,
        0xffaa66cc /* holo_purple */,
        0xffffffff /* white */,
        0xffaaaaaa /* lighter_gray */,
        0xff555555 /* darker_gray */,
        0xff000000 /* black */);

    final ListView list = new ListView(builder.getContext());
    list.setAdapter(adapter);
    builder.setView(list);

    GeckoResult<PromptResponse> res = new GeckoResult<PromptResponse>();

    final AlertDialog dialog = createStandardDialog(builder, prompt, res);
    list.setOnItemClickListener(
        new AdapterView.OnItemClickListener() {
          @Override
          public void onItemClick(
              final AdapterView<?> parent, final View v, final int position, final long id) {
            res.complete(
                prompt.confirm(String.format("#%06x", 0xffffff & adapter.getItem(position))));
            dialog.dismiss();
          }
        });
    dialog.show();

    return res;
  }

  private static Date parseDate(
      final SimpleDateFormat formatter, final String value, final boolean defaultToNow) {
    try {
      if (value != null && !value.isEmpty()) {
        return formatter.parse(value);
      }
    } catch (final ParseException e) {
    }
    return defaultToNow ? new Date() : null;
  }

  @SuppressWarnings("deprecation")
  private static void setTimePickerTime(final TimePicker picker, final Calendar cal) {
    if (Build.VERSION.SDK_INT >= 23) {
      picker.setHour(cal.get(Calendar.HOUR_OF_DAY));
      picker.setMinute(cal.get(Calendar.MINUTE));
    } else {
      picker.setCurrentHour(cal.get(Calendar.HOUR_OF_DAY));
      picker.setCurrentMinute(cal.get(Calendar.MINUTE));
    }
  }

  @SuppressWarnings("deprecation")
  private static void setCalendarTime(final Calendar cal, final TimePicker picker) {
    if (Build.VERSION.SDK_INT >= 23) {
      cal.set(Calendar.HOUR_OF_DAY, picker.getHour());
      cal.set(Calendar.MINUTE, picker.getMinute());
    } else {
      cal.set(Calendar.HOUR_OF_DAY, picker.getCurrentHour());
      cal.set(Calendar.MINUTE, picker.getCurrentMinute());
    }
  }

  @Override
  public GeckoResult<PromptResponse> onDateTimePrompt(
      final GeckoSession session, final DateTimePrompt prompt) {
    final Activity activity = mActivity;
    if (activity == null) {
      return GeckoResult.fromValue(prompt.dismiss());
    }
    final String format;
    if (prompt.type == DateTimePrompt.Type.DATE) {
      format = "yyyy-MM-dd";
    } else if (prompt.type == DateTimePrompt.Type.MONTH) {
      format = "yyyy-MM";
    } else if (prompt.type == DateTimePrompt.Type.WEEK) {
      format = "yyyy-'W'ww";
    } else if (prompt.type == DateTimePrompt.Type.TIME) {
      format = "HH:mm";
    } else if (prompt.type == DateTimePrompt.Type.DATETIME_LOCAL) {
      format = "yyyy-MM-dd'T'HH:mm";
    } else {
      throw new UnsupportedOperationException();
    }

    final SimpleDateFormat formatter = new SimpleDateFormat(format, Locale.ROOT);
    final Date minDate = parseDate(formatter, prompt.minValue, /* defaultToNow */ false);
    final Date maxDate = parseDate(formatter, prompt.maxValue, /* defaultToNow */ false);
    final Date date = parseDate(formatter, prompt.defaultValue, /* defaultToNow */ true);
    final Calendar cal = formatter.getCalendar();
    cal.setTime(date);

    final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
    final LayoutInflater inflater = LayoutInflater.from(builder.getContext());
    final DatePicker datePicker;
    if (prompt.type == DateTimePrompt.Type.DATE
        || prompt.type == DateTimePrompt.Type.MONTH
        || prompt.type == DateTimePrompt.Type.WEEK
        || prompt.type == DateTimePrompt.Type.DATETIME_LOCAL) {
      final int resId =
          builder
              .getContext()
              .getResources()
              .getIdentifier("date_picker_dialog", "layout", "android");
      DatePicker picker = null;
      if (resId != 0) {
        try {
          picker = (DatePicker) inflater.inflate(resId, /* root */ null);
        } catch (final ClassCastException | InflateException e) {
        }
      }
      if (picker == null) {
        picker = new DatePicker(builder.getContext());
      }
      picker.init(
          cal.get(Calendar.YEAR),
          cal.get(Calendar.MONTH),
          cal.get(Calendar.DAY_OF_MONTH), /* listener */
          null);
      if (minDate != null) {
        picker.setMinDate(minDate.getTime());
      }
      if (maxDate != null) {
        picker.setMaxDate(maxDate.getTime());
      }
      datePicker = picker;
    } else {
      datePicker = null;
    }

    final TimePicker timePicker;
    if (prompt.type == DateTimePrompt.Type.TIME
        || prompt.type == DateTimePrompt.Type.DATETIME_LOCAL) {
      final int resId =
          builder
              .getContext()
              .getResources()
              .getIdentifier("time_picker_dialog", "layout", "android");
      TimePicker picker = null;
      if (resId != 0) {
        try {
          picker = (TimePicker) inflater.inflate(resId, /* root */ null);
        } catch (final ClassCastException | InflateException e) {
        }
      }
      if (picker == null) {
        picker = new TimePicker(builder.getContext());
      }
      setTimePickerTime(picker, cal);
      picker.setIs24HourView(DateFormat.is24HourFormat(builder.getContext()));
      timePicker = picker;
    } else {
      timePicker = null;
    }

    final LinearLayout container = addStandardLayout(builder, prompt.title, /* msg */ null);
    container.setPadding(/* left */ 0, /* top */ 0, /* right */ 0, /* bottom */ 0);
    if (datePicker != null) {
      container.addView(datePicker);
    }
    if (timePicker != null) {
      container.addView(timePicker);
    }

    GeckoResult<PromptResponse> res = new GeckoResult<PromptResponse>();

    final DialogInterface.OnClickListener listener =
        new DialogInterface.OnClickListener() {
          @Override
          public void onClick(final DialogInterface dialog, final int which) {
            if (which == DialogInterface.BUTTON_NEUTRAL) {
              // Clear
              res.complete(prompt.confirm(""));
              return;
            }
            if (datePicker != null) {
              cal.set(datePicker.getYear(), datePicker.getMonth(), datePicker.getDayOfMonth());
            }
            if (timePicker != null) {
              setCalendarTime(cal, timePicker);
            }
            res.complete(prompt.confirm(formatter.format(cal.getTime())));
          }
        };
    builder
        .setNegativeButton(android.R.string.cancel, /* listener */ null)
        .setNeutralButton(R.string.clear_field, listener)
        .setPositiveButton(android.R.string.ok, listener);

    final AlertDialog dialog = createStandardDialog(builder, prompt, res);
    dialog.show();

    prompt.setDelegate(
        new PromptInstanceDelegate() {
          @Override
          public void onPromptDismiss(final BasePrompt prompt) {
            dialog.dismiss();
          }
        });
    return res;
  }

  @Override
  @TargetApi(19)
  public GeckoResult<PromptResponse> onFilePrompt(GeckoSession session, FilePrompt prompt) {
    final Activity activity = mActivity;
    if (activity == null) {
      return GeckoResult.fromValue(prompt.dismiss());
    }

    // Merge all given MIME types into one, using wildcard if needed.
    String mimeType = null;
    String mimeSubtype = null;
    if (prompt.mimeTypes != null) {
      for (final String rawType : prompt.mimeTypes) {
        final String normalizedType = rawType.trim().toLowerCase(Locale.ROOT);
        final int len = normalizedType.length();
        int slash = normalizedType.indexOf('/');
        if (slash < 0) {
          slash = len;
        }
        final String newType = normalizedType.substring(0, slash);
        final String newSubtype = normalizedType.substring(Math.min(slash + 1, len));
        if (mimeType == null) {
          mimeType = newType;
        } else if (!mimeType.equals(newType)) {
          mimeType = "*";
        }
        if (mimeSubtype == null) {
          mimeSubtype = newSubtype;
        } else if (!mimeSubtype.equals(newSubtype)) {
          mimeSubtype = "*";
        }
      }
    }

    final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    intent.setType(
        (mimeType != null ? mimeType : "*") + '/' + (mimeSubtype != null ? mimeSubtype : "*"));
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
    if (Build.VERSION.SDK_INT >= 18 && prompt.type == FilePrompt.Type.MULTIPLE) {
      intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
    }
    if (Build.VERSION.SDK_INT >= 19 && prompt.mimeTypes.length > 0) {
      intent.putExtra(Intent.EXTRA_MIME_TYPES, prompt.mimeTypes);
    }

    GeckoResult<PromptResponse> res = new GeckoResult<PromptResponse>();

    try {
      mFileResponse = res;
      mFilePrompt = prompt;
      activity.startActivityForResult(intent, filePickerRequestCode);
    } catch (final ActivityNotFoundException e) {
      Log.e(LOGTAG, "Cannot launch activity", e);
      return GeckoResult.fromValue(prompt.dismiss());
    }

    return res;
  }

  public void onFileCallbackResult(final int resultCode, final Intent data) {
    if (mFileResponse == null) {
      return;
    }

    final GeckoResult<PromptResponse> res = mFileResponse;
    mFileResponse = null;

    final FilePrompt prompt = mFilePrompt;
    mFilePrompt = null;

    if (resultCode != Activity.RESULT_OK || data == null) {
      res.complete(prompt.dismiss());
      return;
    }

    final Uri uri = data.getData();
    final ClipData clip = data.getClipData();

    if (prompt.type == FilePrompt.Type.SINGLE
        || (prompt.type == FilePrompt.Type.MULTIPLE && clip == null)) {
      res.complete(prompt.confirm(mActivity, uri));
    } else if (prompt.type == FilePrompt.Type.MULTIPLE) {
      if (clip == null) {
        Log.w(LOGTAG, "No selected file");
        res.complete(prompt.dismiss());
        return;
      }
      final int count = clip.getItemCount();
      final ArrayList<Uri> uris = new ArrayList<>(count);
      for (int i = 0; i < count; i++) {
        uris.add(clip.getItemAt(i).getUri());
      }
      res.complete(prompt.confirm(mActivity, uris.toArray(new Uri[uris.size()])));
    }
  }

  public GeckoResult<Integer> onPermissionPrompt(
      final GeckoSession session,
      final String title,
      final GeckoSession.PermissionDelegate.ContentPermission perm) {
    final Activity activity = mActivity;
    final GeckoResult<Integer> res = new GeckoResult<>();
    if (activity == null) {
      res.complete(GeckoSession.PermissionDelegate.ContentPermission.VALUE_PROMPT);
      return res;
    }
    final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
    builder
        .setTitle(title)
        .setNegativeButton(
            android.R.string.cancel,
            new DialogInterface.OnClickListener() {
              @Override
              public void onClick(final DialogInterface dialog, final int which) {
                res.complete(GeckoSession.PermissionDelegate.ContentPermission.VALUE_DENY);
              }
            })
        .setPositiveButton(
            android.R.string.ok,
            new DialogInterface.OnClickListener() {
              @Override
              public void onClick(final DialogInterface dialog, final int which) {
                res.complete(GeckoSession.PermissionDelegate.ContentPermission.VALUE_ALLOW);
              }
            });

    final AlertDialog dialog = builder.create();
    dialog.show();
    return res;
  }

  public void onSlowScriptPrompt(
      GeckoSession geckoSession, String title, GeckoResult<SlowScriptResponse> reportAction) {
    final Activity activity = mActivity;
    if (activity == null) {
      return;
    }
    final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
    builder
        .setTitle(title)
        .setNegativeButton(
            activity.getString(R.string.wait),
            new DialogInterface.OnClickListener() {
              @Override
              public void onClick(final DialogInterface dialog, final int which) {
                reportAction.complete(SlowScriptResponse.CONTINUE);
              }
            })
        .setPositiveButton(
            activity.getString(R.string.stop),
            new DialogInterface.OnClickListener() {
              @Override
              public void onClick(final DialogInterface dialog, final int which) {
                reportAction.complete(SlowScriptResponse.STOP);
              }
            });

    final AlertDialog dialog = builder.create();
    dialog.show();
  }

  private Spinner addMediaSpinner(
      final Context context,
      final ViewGroup container,
      final MediaSource[] sources,
      final String[] sourceNames) {
    final ArrayAdapter<MediaSource> adapter =
        new ArrayAdapter<MediaSource>(context, android.R.layout.simple_spinner_item) {
          private View convertView(final int position, final View view) {
            if (view != null) {
              final MediaSource item = getItem(position);
              ((TextView) view).setText(sourceNames != null ? sourceNames[position] : item.name);
            }
            return view;
          }

          @Override
          public View getView(final int position, View view, final ViewGroup parent) {
            return convertView(position, super.getView(position, view, parent));
          }

          @Override
          public View getDropDownView(final int position, final View view, final ViewGroup parent) {
            return convertView(position, super.getDropDownView(position, view, parent));
          }
        };
    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    adapter.addAll(sources);

    final Spinner spinner = new Spinner(context);
    spinner.setAdapter(adapter);
    spinner.setSelection(0);
    container.addView(spinner);
    return spinner;
  }

  public void onMediaPrompt(
      final GeckoSession session,
      final String title,
      final MediaSource[] video,
      final MediaSource[] audio,
      final String[] videoNames,
      final String[] audioNames,
      final GeckoSession.PermissionDelegate.MediaCallback callback) {
    final Activity activity = mActivity;
    if (activity == null || (video == null && audio == null)) {
      callback.reject();
      return;
    }
    final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
    final LinearLayout container = addStandardLayout(builder, title, /* msg */ null);

    final Spinner videoSpinner;
    if (video != null) {
      videoSpinner = addMediaSpinner(builder.getContext(), container, video, videoNames);
    } else {
      videoSpinner = null;
    }

    final Spinner audioSpinner;
    if (audio != null) {
      audioSpinner = addMediaSpinner(builder.getContext(), container, audio, audioNames);
    } else {
      audioSpinner = null;
    }

    builder
        .setNegativeButton(android.R.string.cancel, /* listener */ null)
        .setPositiveButton(
            android.R.string.ok,
            new DialogInterface.OnClickListener() {
              @Override
              public void onClick(final DialogInterface dialog, final int which) {
                final MediaSource video =
                    (videoSpinner != null) ? (MediaSource) videoSpinner.getSelectedItem() : null;
                final MediaSource audio =
                    (audioSpinner != null) ? (MediaSource) audioSpinner.getSelectedItem() : null;
                callback.grant(video, audio);
              }
            });

    final AlertDialog dialog = builder.create();
    dialog.setOnDismissListener(
        new DialogInterface.OnDismissListener() {
          @Override
          public void onDismiss(final DialogInterface dialog) {
            callback.reject();
          }
        });
    dialog.show();
  }

  public void onMediaPrompt(
      final GeckoSession session,
      final String title,
      final MediaSource[] video,
      final MediaSource[] audio,
      final GeckoSession.PermissionDelegate.MediaCallback callback) {
    onMediaPrompt(session, title, video, audio, null, null, callback);
  }

  @Override
  public GeckoResult<PromptResponse> onPopupPrompt(
      final GeckoSession session, final PopupPrompt prompt) {
    return GeckoResult.fromValue(prompt.confirm(AllowOrDeny.ALLOW));
  }
}
