/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.settings.bluetooth;

import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import android.text.Editable;
import android.text.InputFilter;
import android.text.InputFilter.LengthFilter;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.TextView;

import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;

import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.flags.Flags;

/**
 * A dialogFragment used by {@link BluetoothPairingDialog} to create an appropriately styled dialog
 * for the bluetooth device.
 */
public class BluetoothPairingDialogFragment extends InstrumentedDialogFragment implements
        TextWatcher {

    private static final String TAG = "BTPairingDialogFragment";

    private AlertDialog.Builder mBuilder;
    private AlertDialog mDialog;
    private BluetoothPairingController mPairingController;
    private BluetoothPairingDialog mPairingDialogActivity;
    private EditText mPairingView;
    private boolean mPositiveClicked = false;
    /**
     * The interface we expect a listener to implement. Typically this should be done by
     * the controller.
     */
    public interface BluetoothPairingDialogListener {

        void onDialogNegativeClick(BluetoothPairingDialogFragment dialog);

        void onDialogPositiveClick(BluetoothPairingDialogFragment dialog);
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        if (!isPairingControllerSet()) {
            throw new IllegalStateException(
                "Must call setPairingController() before showing dialog");
        }
        if (!isPairingDialogActivitySet()) {
            throw new IllegalStateException(
                "Must call setPairingDialogActivity() before showing dialog");
        }
        mBuilder = new AlertDialog.Builder(getActivity());
        mDialog = setupDialog();
        mDialog.setCanceledOnTouchOutside(false);
        return mDialog;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        /* Cancel pairing unless 1) explicitly accepted by user 2) the event is triggered by
         * orientation change. */
        boolean shouldCancelPairing =
                !mPositiveClicked && !getActivity().isChangingConfigurations();
        if (mPairingController.getDialogType() != BluetoothPairingController.DISPLAY_PASSKEY_DIALOG
                && shouldCancelPairing) {
            mPairingController.onCancel();
        }
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
    }

    @Override
    public void afterTextChanged(Editable s) {
        // enable the positive button when we detect potentially valid input
        mDialog.findViewById(R.id.positive_button).setEnabled(mPairingController.isPasskeyValid(s));
        // notify the controller about user input
        mPairingController.updateUserInput(s.toString());
    }

    protected void onAcceptButtonClicked() {
        mPositiveClicked = true;
        mPairingController.onDialogPositiveClick(this);
        mPairingDialogActivity.dismiss();
    }

    protected void onDeclineButtonClicked() {
        mPairingController.onDialogNegativeClick(this);
        mPairingDialogActivity.dismiss();
    }

    @Override
    public int getMetricsCategory() {
        return SettingsEnums.BLUETOOTH_DIALOG_FRAGMENT;
    }

    /**
     * Used in testing to get a reference to the dialog.
     * @return - The fragments current dialog
     */
    protected AlertDialog getmDialog() {
        return mDialog;
    }

    /**
     * Sets the controller that the fragment should use. this method MUST be called
     * before you try to show the dialog or an error will be thrown. An implementation
     * of a pairing controller can be found at {@link BluetoothPairingController}. A
     * controller may not be substituted once it is assigned. Forcibly switching a
     * controller for a new one will lead to undefined behavior.
     */
    void setPairingController(BluetoothPairingController pairingController) {
        if (isPairingControllerSet()) {
            throw new IllegalStateException("The controller can only be set once. "
                    + "Forcibly replacing it will lead to undefined behavior");
        }
        mPairingController = pairingController;
    }

    /**
     * Checks whether mPairingController is set
     * @return True when mPairingController is set, False otherwise
     */
    boolean isPairingControllerSet() {
        return mPairingController != null;
    }

    /**
     * Sets the BluetoothPairingDialog activity that started this fragment
     * @param pairingDialogActivity The pairing dialog activty that started this fragment
     */
    void setPairingDialogActivity(BluetoothPairingDialog pairingDialogActivity) {
        if (isPairingDialogActivitySet()) {
            throw new IllegalStateException("The pairing dialog activity can only be set once");
        }
        mPairingDialogActivity = pairingDialogActivity;
    }

    /**
     * Checks whether mPairingDialogActivity is set
     * @return True when mPairingDialogActivity is set, False otherwise
     */
    boolean isPairingDialogActivitySet() {
        return mPairingDialogActivity != null;
    }

    /**
     * Creates the appropriate type of dialog and returns it.
     */
    private AlertDialog setupDialog() {
        AlertDialog dialog;
        switch (mPairingController.getDialogType()) {
            case BluetoothPairingController.USER_ENTRY_DIALOG:
                dialog = createUserEntryDialog();
                break;
            case BluetoothPairingController.CONFIRMATION_DIALOG:
                dialog = createConsentDialog();
                break;
            case BluetoothPairingController.DISPLAY_PASSKEY_DIALOG:
                dialog = createDisplayPasskeyOrPinDialog();
                break;
            default:
                dialog = null;
                Log.e(TAG, "Incorrect pairing type received, not showing any dialog");
        }
        return dialog;
    }

    /**
     * Helper method to return the text of the pin entry field - this exists primarily to help us
     * simulate having existing text when the dialog is recreated, for example after a screen
     * rotation.
     */
    @VisibleForTesting
    CharSequence getPairingViewText() {
        if (mPairingView != null) {
            return mPairingView.getText();
        }
        return null;
    }

    /**
     * Returns a dialog with UI elements that allow a user to provide input.
     */
    private AlertDialog createUserEntryDialog() {
        mBuilder.setTitle(getString(R.string.bluetooth_pairing_request,
                mPairingController.getDeviceName()));
        mBuilder.setView(createPinEntryView());
        AlertDialog dialog = mBuilder.create();
        dialog.setOnShowListener(d -> {
            if (TextUtils.isEmpty(getPairingViewText())) {
                mDialog.findViewById(R.id.positive_button).setEnabled(false);
            }
            if (mPairingView != null && mPairingView.requestFocus()) {
                InputMethodManager imm = (InputMethodManager)
                        getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                if (imm != null) {
                    imm.showSoftInput(mPairingView, InputMethodManager.SHOW_IMPLICIT);
                }
            }
        });
        return dialog;
    }

    /**
     * Creates the custom view with UI elements for user input.
     */
    private View createPinEntryView() {
        View view = getActivity().getLayoutInflater().inflate(R.layout.bluetooth_pin_entry, null);
        TextView messageViewCaptionHint = (TextView) view.findViewById(R.id.pin_values_hint);
        TextView messageView2 = (TextView) view.findViewById(R.id.message_below_pin);
        CheckBox alphanumericPin = (CheckBox) view.findViewById(R.id.alphanumeric_pin);

        CheckBox contactSharing =
                (CheckBox) view.findViewById(R.id.phonebook_sharing_message_entry_pin);
        if (contactSharing != null) {
            contactSharing.setText(getString(R.string.bluetooth_pairing_shares_phonebook));
            contactSharing.setVisibility(
                    mPairingController.isContactSharingVisible() ? View.VISIBLE : View.GONE);
            // OnCheckedChangeListener has to be registered before the initial setChecked()
            contactSharing.setOnCheckedChangeListener(mPairingController);
            contactSharing.setChecked(mPairingController.getContactSharingState());
        }

        EditText pairingView = (EditText) view.findViewById(R.id.text);
        mPairingView = pairingView;

        pairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
        pairingView.addTextChangedListener(this);
        alphanumericPin.setOnCheckedChangeListener((buttonView, isChecked) -> {
            // change input type for soft keyboard to numeric or alphanumeric
            if (isChecked) {
                mPairingView.setInputType(InputType.TYPE_CLASS_TEXT);
            } else {
                mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
            }
        });

        int messageId = mPairingController.getDeviceVariantMessageId();
        int messageIdHint = mPairingController.getDeviceVariantMessageHintId();
        int maxLength = mPairingController.getDeviceMaxPasskeyLength();
        alphanumericPin.setVisibility(mPairingController.pairingCodeIsAlphanumeric()
                ? View.VISIBLE : View.GONE);
        if (messageId != BluetoothPairingController.INVALID_DIALOG_TYPE) {
            messageView2.setText(messageId);
        } else {
            messageView2.setVisibility(View.GONE);
        }
        if (messageIdHint != BluetoothPairingController.INVALID_DIALOG_TYPE) {
            messageViewCaptionHint.setText(messageIdHint);
        } else {
            messageViewCaptionHint.setVisibility(View.GONE);
        }
        pairingView.setFilters(new InputFilter[]{
                new LengthFilter(maxLength)});

        Button negativeButton = view.findViewById(R.id.negative_button);
        negativeButton.setOnClickListener(v -> onDeclineButtonClicked());
        Button positiveButton = view.findViewById(R.id.positive_button);
        positiveButton.setOnClickListener(v -> onAcceptButtonClicked());

        return view;
    }

    /**
     * Creates a dialog with UI elements that allow the user to confirm a pairing request.
     */
    private AlertDialog createConfirmationDialog() {
        if (mPairingController.hasPairingContent()) {
            mBuilder.setTitle(getString(R.string.bluetooth_pairing_confirmation_title));
        } else {
            mBuilder.setTitle(getString(R.string.bluetooth_pairing_request,
                    mPairingController.getDeviceName()));
        }
        mBuilder.setView(createView());
        AlertDialog dialog = mBuilder.create();
        return dialog;
    }

    /**
     * Creates a dialog with UI elements that allow the user to consent to a pairing request.
     */
    private AlertDialog createConsentDialog() {
        return createConfirmationDialog();
    }

    /**
     * Creates a dialog that informs users of a pairing request and shows them the passkey/pin
     * of the device.
     */
    private AlertDialog createDisplayPasskeyOrPinDialog() {
        mBuilder.setTitle(getString(R.string.bluetooth_pairing_request,
                mPairingController.getDeviceName()));
        mBuilder.setView(createView());
        AlertDialog dialog = mBuilder.create();

        // Tell the controller the dialog has been created.
        mPairingController.notifyDialogDisplayed();

        return dialog;
    }

    /**
     * Creates a custom view for dialogs which need to show users additional information but do
     * not require user input.
     */
    private View createView() {
        View view = getActivity().getLayoutInflater().inflate(R.layout.bluetooth_pin_confirm, null);
        TextView pairingConfirmationHint =
                (TextView) view.findViewById(R.id.pairing_confirmation_hint);
        TextView pairingViewCaption = (TextView) view.findViewById(R.id.pairing_caption);
        TextView pairingViewContent = (TextView) view.findViewById(R.id.pairing_subhead);
        TextView messagePairing = (TextView) view.findViewById(R.id.pairing_code_message);
        CompoundButton contactSharing = getContactSharingSwitch(view);
        view.findViewById(R.id.phonebook_sharing).setVisibility(
                mPairingController.isContactSharingVisible() ? View.VISIBLE : View.GONE);
        // OnCheckedChangeListener has to be registered before the initial setChecked()
        contactSharing.setOnCheckedChangeListener(mPairingController);
        contactSharing.setChecked(mPairingController.getContactSharingState());

        messagePairing.setVisibility(mPairingController.isDisplayPairingKeyVariant()
                ? View.VISIBLE : View.GONE);
        if (mPairingController.hasPairingContent()) {
            if (mPairingController.isDisplayPairingKeyVariant()) {
                pairingViewCaption.setVisibility(View.VISIBLE);
            } else {
                pairingConfirmationHint.setText(
                        getString(
                                R.string.bluetooth_pairing_confirmation_msg,
                                mPairingController.getDeviceName()));
                pairingConfirmationHint.setVisibility(View.VISIBLE);
            }
            pairingViewContent.setVisibility(View.VISIBLE);
            pairingViewContent.setText(mPairingController.getPairingContent());
        }
        final TextView messagePairingSet = (TextView) view.findViewById(R.id.pairing_group_message);
        if (mPairingController.isLateBonding()) {
            messagePairingSet.setText(getString(R.string.bluetooth_pairing_group_late_bonding));
        }

        boolean setPairingMessage =
            mPairingController.isCoordinatedSetMemberDevice() || mPairingController.isLateBonding();

        messagePairingSet.setVisibility(setPairingMessage ? View.VISIBLE : View.GONE);

        Button negativeButton = view.findViewById(R.id.negative_button);
        negativeButton.setVisibility(View.VISIBLE);
        negativeButton.setOnClickListener(v -> onDeclineButtonClicked());
        if (mPairingController.getDialogType() == BluetoothPairingController.CONFIRMATION_DIALOG) {
            Button positiveButton = view.findViewById(R.id.positive_button);
            positiveButton.setVisibility(View.VISIBLE);
            positiveButton.setOnClickListener(v -> onAcceptButtonClicked());
        }

        return view;
    }

    private CompoundButton getContactSharingSwitch(View container) {
        CompoundButton legacySwitch =
                container.findViewById(R.id.phonebook_sharing_message_confirm_pin);
        CompoundButton expressiveSwitch =
                container.findViewById(R.id.phonebook_sharing_message_confirm_pin_expressive);
        if (Flags.enableBluetoothSettingsExpressiveDesign()) {
            legacySwitch.setVisibility(View.GONE);
            expressiveSwitch.setVisibility(View.VISIBLE);
            return expressiveSwitch;
        } else {
            legacySwitch.setVisibility(View.VISIBLE);
            expressiveSwitch.setVisibility(View.GONE);
            return legacySwitch;
        }
    }
}
