/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

package com.facebook.react.modules.permissions;

import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Process;
import android.util.SparseArray;

import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.PermissionAwareActivity;
import com.facebook.react.modules.core.PermissionListener;

import java.util.ArrayList;

/**
 * Module that exposes the Android M Permission system to JS.
 */
@ReactModule(name = "PermissionsAndroid")
public class PermissionsModule extends ReactContextBaseJavaModule implements PermissionListener {

  private static final String ERROR_INVALID_ACTIVITY = "E_INVALID_ACTIVITY";
  private final SparseArray<Callback> mCallbacks;
  private int mRequestCode = 0;
  private final String GRANTED = "granted";
  private final String DENIED = "denied";
  private final String NEVER_ASK_AGAIN = "never_ask_again";

  public PermissionsModule(ReactApplicationContext reactContext) {
    super(reactContext);
    mCallbacks = new SparseArray<Callback>();
  }

  @Override
  public String getName() {
    return "PermissionsAndroid";
  }

  /**
   * Check if the app has the permission given. successCallback is called with true if the
   * permission had been granted, false otherwise. See {@link Activity#checkSelfPermission}.
   */
  @ReactMethod
  public void checkPermission(final String permission, final Promise promise) {
    Context context = getReactApplicationContext().getBaseContext();
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
      promise.resolve(context.checkPermission(permission, Process.myPid(), Process.myUid()) ==
        PackageManager.PERMISSION_GRANTED);
      return;
    }
    promise.resolve(context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED);
  }

  /**
   * Check whether the app should display a message explaining why a certain permission is needed.
   * successCallback is called with true if the app should display a message, false otherwise.
   * This message is only displayed if the user has revoked this permission once before, and if the
   * permission dialog will be shown to the user (the user can choose to not be shown that dialog
   * again). For devices before Android M, this always returns false.
   * See {@link Activity#shouldShowRequestPermissionRationale}.
   */
  @ReactMethod
  public void shouldShowRequestPermissionRationale(final String permission, final Promise promise) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
      promise.resolve(false);
      return;
    }
    try {
      promise.resolve(getPermissionAwareActivity().shouldShowRequestPermissionRationale(permission));
    } catch (IllegalStateException e) {
      promise.reject(ERROR_INVALID_ACTIVITY, e);
    }
  }

  /**
   * Request the given permission. successCallback is called with true if the permission had been
   * granted, false otherwise. For devices before Android M, this instead checks if the user has
   * the permission given or not.
   * See {@link Activity#checkSelfPermission}.
   */
  @ReactMethod
  public void requestPermission(final String permission, final Promise promise) {
    Context context = getReactApplicationContext().getBaseContext();
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
      promise.resolve(context.checkPermission(permission, Process.myPid(), Process.myUid()) ==
              PackageManager.PERMISSION_GRANTED);
      return;
    }
    if (context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) {
      promise.resolve(GRANTED);
      return;
    }

    try {
      PermissionAwareActivity activity = getPermissionAwareActivity();

      mCallbacks.put(
        mRequestCode, new Callback() {
          @Override
          public void invoke(Object... args) {
            int[] results = (int[]) args[0];
            if (results[0] == PackageManager.PERMISSION_GRANTED) {
              promise.resolve(GRANTED);
            } else {
              PermissionAwareActivity activity = (PermissionAwareActivity) args[1];
              if (activity.shouldShowRequestPermissionRationale(permission)) {
                promise.resolve(DENIED);
              } else {
                promise.resolve(NEVER_ASK_AGAIN);
              }
            }
          }
        }
      );

      activity.requestPermissions(new String[]{permission}, mRequestCode, this);
      mRequestCode++;
    } catch (IllegalStateException e) {
      promise.reject(ERROR_INVALID_ACTIVITY, e);
    }
  }

  @ReactMethod
  public void requestMultiplePermissions(final ReadableArray permissions, final Promise promise) {
    final WritableMap grantedPermissions = new WritableNativeMap();
    final ArrayList<String> permissionsToCheck = new ArrayList<String>();
    int checkedPermissionsCount = 0;

    Context context = getReactApplicationContext().getBaseContext();

    for (int i = 0; i < permissions.size(); i++) {
      String perm = permissions.getString(i);

      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
        grantedPermissions.putString(perm, context.checkPermission(perm, Process.myPid(), Process.myUid()) ==
        PackageManager.PERMISSION_GRANTED ? GRANTED : DENIED);
        checkedPermissionsCount++;
      } else if (context.checkSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) {
        grantedPermissions.putString(perm, GRANTED);
        checkedPermissionsCount++;
      } else {
        permissionsToCheck.add(perm);
      }
    }
    if (permissions.size() == checkedPermissionsCount) {
      promise.resolve(grantedPermissions);
      return;
    }
    try {

      PermissionAwareActivity activity = getPermissionAwareActivity();

      mCallbacks.put(
      mRequestCode, new Callback() {
        @Override
        public void invoke(Object... args) {
          int[] results = (int[]) args[0];
          PermissionAwareActivity activity = (PermissionAwareActivity) args[1];
          for (int j = 0; j < permissionsToCheck.size(); j++) {
            String permission = permissionsToCheck.get(j);
            if (results[j] == PackageManager.PERMISSION_GRANTED) {
              grantedPermissions.putString(permission, GRANTED);
            } else {
              if (activity.shouldShowRequestPermissionRationale(permission)) {
                grantedPermissions.putString(permission, DENIED);
              } else {
                grantedPermissions.putString(permission, NEVER_ASK_AGAIN);
              }
            }
          }
          promise.resolve(grantedPermissions);
        }
      });

      activity.requestPermissions(permissionsToCheck.toArray(new String[0]), mRequestCode, this);
      mRequestCode++;
    } catch (IllegalStateException e) {
      promise.reject(ERROR_INVALID_ACTIVITY, e);
    }
  }

  /**
   * Method called by the activity with the result of the permission request.
   */
  @Override
  public boolean onRequestPermissionsResult(
    int requestCode,
    String[] permissions,
    int[] grantResults) {
      mCallbacks.get(requestCode).invoke(grantResults, getPermissionAwareActivity());
      mCallbacks.remove(requestCode);
      return mCallbacks.size() == 0;
  }

  private PermissionAwareActivity getPermissionAwareActivity() {
    Activity activity = getCurrentActivity();
    if (activity == null) {
      throw new IllegalStateException("Tried to use permissions API while not attached to an " +
          "Activity.");
    } else if (!(activity instanceof PermissionAwareActivity)) {
      throw new IllegalStateException("Tried to use permissions API but the host Activity doesn't" +
          " implement PermissionAwareActivity.");
    }
    return (PermissionAwareActivity) activity;
  }
}
