package com.github.florent37.runtimepermission;

import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentTransaction;

import com.github.florent37.runtimepermission.callbacks.AcceptedCallback;
import com.github.florent37.runtimepermission.callbacks.DeniedCallback;
import com.github.florent37.runtimepermission.callbacks.ForeverDeniedCallback;
import com.github.florent37.runtimepermission.callbacks.PermissionListener;
import com.github.florent37.runtimepermission.callbacks.ResponseCallback;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class RuntimePermission {

    private static final String TAG = "PERMISSION_FRAGMENT_WEEEEE";

    private final Reference<FragmentActivity> activityReference;

    //The list of permissions we want to ask
    private final List<String> permissionsToRequest = new ArrayList<>();

    //region callbacks
    private final List<ResponseCallback> responseCallbacks = new ArrayList<>();
    private final List<AcceptedCallback> acceptedCallbacks = new ArrayList<>();
    private final List<ForeverDeniedCallback> foreverDeniedCallbacks = new ArrayList<>();
    private final List<DeniedCallback> deniedCallbacks = new ArrayList<>();
    private final List<PermissionListener> permissionListeners = new ArrayList<>();

    //the listener we will give to the fragment
    private final PermissionFragment.PermissionListener listener = new PermissionFragment.PermissionListener() {
        @Override
        public void onRequestPermissionsResult(List<String> acceptedPermissions, List<String> refusedPermissions, List<String> askAgainPermissions) {
            onReceivedPermissionResult(acceptedPermissions, refusedPermissions, askAgainPermissions);
        }
    };
    //endregion

    public RuntimePermission(@Nullable final FragmentActivity activity) {
        if (activity != null) {
            this.activityReference = new WeakReference<>(activity);
        } else {
            this.activityReference = new WeakReference<>(null);
        }
    }

    /**
     * Fill permissions to only ask If we do not call this method,
     * If not set or empty, the library will find all needed permissions to ask from manifest
     * You can call .request(permissions) after this method if you want to give permissions in a separate method
     */
    public static RuntimePermission askPermission(@Nullable final FragmentActivity activity, String... permissions) {
        return new RuntimePermission(activity).request(permissions);
    }

    /**
     * Fill permissions to only ask If we do not call this method,
     * If not set or empty, the library will find all needed permissions to ask from manifest
     * You can call .request(permissions) after this method if you want to give permissions in a separate method
     */
    public static RuntimePermission askPermission(@Nullable final Fragment fragment, String... permissions) {
        @Nullable FragmentActivity activity = null;
        if (fragment != null) {
            activity = fragment.getActivity();
        }
        return askPermission(activity).request(permissions);
    }

    /**
     * Just a helper methods in case the user blocks permission.
     * It goes to your application settings page for the user to enable permission again.
     */
    public void goToSettings() {
        final FragmentActivity fragmentActivity = this.activityReference.get();
        if (fragmentActivity != null) {
            fragmentActivity.startActivity(new Intent(
                    Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                    Uri.fromParts("package", fragmentActivity.getPackageName(), null)));
        }
    }

    private void onReceivedPermissionResult(List<String> acceptedPermissions, List<String> refusedPermissions, List<String> askAgainPermissions) {
        final PermissionResult permissionResult = new PermissionResult(this, acceptedPermissions, refusedPermissions, askAgainPermissions);
        if (permissionResult.isAccepted()) {
            for (AcceptedCallback callback : acceptedCallbacks) {
                callback.onAccepted(permissionResult);
            }
            for (PermissionListener permissionListener : permissionListeners) {
                permissionListener.onAccepted(permissionResult, permissionResult.getAccepted());
            }
        }
        if (permissionResult.hasDenied()) {
            for (DeniedCallback callback : deniedCallbacks) {
                callback.onDenied(permissionResult);
            }
        }
        if (permissionResult.hasForeverDenied()) {
            for (ForeverDeniedCallback callback : foreverDeniedCallbacks) {
                callback.onForeverDenied(permissionResult);
            }
        }

        if (permissionResult.hasForeverDenied() || permissionResult.hasDenied()) {
            for (PermissionListener permissionListener : permissionListeners) {
                permissionListener.onDenied(permissionResult, permissionResult.getDenied(), permissionResult.getForeverDenied());
            }
        }

        for (ResponseCallback responseCallback : responseCallbacks) {
            responseCallback.onResponse(permissionResult);
        }
    }

    /**
     * We want to only request given permissions
     * If we do not call this method, the library will find all needed permissions to ask from manifest
     *
     * @see android.Manifest.permission
     */
    public RuntimePermission request(@Nullable final List<String> permissions) {
        if (permissions != null) {
            permissionsToRequest.clear();
            permissionsToRequest.addAll(permissions);
        }
        return this;
    }

    /**
     * We want to only request given permissions
     *
     * @see android.Manifest.permission
     */
    public RuntimePermission request(@Nullable final String... permissions) {
        if (permissions != null) {
            return this.request(Arrays.asList(permissions));
        } else {
            return this;
        }
    }

    public RuntimePermission onResponse(@Nullable final ResponseCallback callback) {
        if (callback != null) {
            responseCallbacks.add(callback);
        }
        return this;
    }

    public RuntimePermission onResponse(@Nullable final PermissionListener permissionListener) {
        if (permissionListener != null) {
            permissionListeners.add(permissionListener);
        }
        return this;
    }

    public RuntimePermission onAccepted(@Nullable final AcceptedCallback callback) {
        if (callback != null) {
            acceptedCallbacks.add(callback);
        }
        return this;
    }

    public RuntimePermission onDenied(@Nullable final DeniedCallback callback) {
        if (callback != null) {
            deniedCallbacks.add(callback);
        }
        return this;
    }

    public RuntimePermission onForeverDenied(@Nullable final ForeverDeniedCallback callback) {
        if (callback != null) {
            foreverDeniedCallbacks.add(callback);
        }
        return this;
    }

    public void ask(@Nullable ResponseCallback responseCallback) {
        onResponse(responseCallback)
                .ask();
    }

    public void ask(@Nullable PermissionListener permissionListener) {
        onResponse(permissionListener)
                .ask();
    }

    /**
     * If we request permission using .request(names), we only ask them
     * If not, this lib will search needed permissions from Manifest
     */
    private List<String> findNeededPermissions(@NonNull Context context) {
        if (permissionsToRequest.isEmpty()) {
            return PermissionManifestFinder.findNeededPermissionsFromManifest(context);
        } else {
            return permissionsToRequest;
        }
    }

    /**
     * Ask for the permission. Which permission? Anything you register on your manifest that needs it.
     * It is safe to call this every time without querying `shouldAsk`.
     * In case you call `ask` without any permission, the method returns.
     */
    public void ask() {
        final FragmentActivity activity = activityReference.get();
        if (activity == null || activity.isFinishing()) {
            return;
        }

        //retrieve permissions we want
        final List<String> permissions = findNeededPermissions(activity);

        // No need to ask for permissions on API levels below Android Marshmallow
        if (permissions.isEmpty() || Build.VERSION.SDK_INT < Build.VERSION_CODES.M || arePermissionsAlreadyAccepted(activity, permissions)) {
            onAllAccepted(permissions);
        } else {
            final PermissionFragment oldFragment = (PermissionFragment) activity
                    .getSupportFragmentManager()
                    .findFragmentByTag(TAG);

            if (oldFragment != null) {
                oldFragment.setListener(listener);
            } else {
                final PermissionFragment newFragment = PermissionFragment.newInstance(permissions);
                newFragment.setListener(listener);

                activity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        activity.getSupportFragmentManager()
                                .beginTransaction()
                                .add(newFragment, TAG)
                                .commitAllowingStateLoss();
                        // change to .commitNowAllowingStateLoss() to see the crash
                    }
                });

            }
        }
    }

    private boolean arePermissionsAlreadyAccepted(@NonNull Context context, @NonNull final List<String> permissions) {
        for (String permission : permissions) {
            final int permissionState = ContextCompat.checkSelfPermission(context, permission);
            if (permissionState == PackageManager.PERMISSION_DENIED) {
                return false;
            }
        }
        return true;
    }

    private void onAllAccepted(@NonNull final List<String> permissions) {
        onReceivedPermissionResult(permissions, null, null);
    }

    public void askAgain() {

    }
}