// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.password_manager;

import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.content.res.AppCompatResources;

import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.chrome.R;
import org.chromium.components.browser_ui.util.AvatarGenerator;
import org.chromium.components.url_formatter.UrlFormatter;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.widget.Toast;

/**
 *  A dialog offers the user the ability to choose credentials for authentication. User is
 *  presented with username along with avatar and full name in case they are available.
 *  Native counterpart should be notified about credentials user have chosen and also if user
 *  haven't chosen anything.
 */
public class AccountChooserDialog
        implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
    private final Context mContext;
    private final Credential[] mCredentials;

    /**
     * Title of the dialog, contains Smart Lock branding for the Smart Lock users.
     */
    private final String mTitle;
    private final int mTitleLinkStart;
    private final int mTitleLinkEnd;
    private final String mOrigin;
    private final String mSigninButtonText;
    private ArrayAdapter<Credential> mAdapter;

    /**
     * Holds the reference to the credentials which were chosen by the user.
     */
    private Credential mCredential;
    private long mNativeAccountChooserDialog;
    private AlertDialog mDialog;
    /**
     * True, if credentials were selected via "Sign In" button instead of clicking on the credential
     * itself.
     */
    private boolean mSigninButtonClicked;

    private AccountChooserDialog(Context context, long nativeAccountChooserDialog,
            Credential[] credentials, String title, int titleLinkStart, int titleLinkEnd,
            String origin, String signinButtonText) {
        mNativeAccountChooserDialog = nativeAccountChooserDialog;
        mContext = context;
        mCredentials = credentials.clone();
        mTitle = title;
        mTitleLinkStart = titleLinkStart;
        mTitleLinkEnd = titleLinkEnd;
        mOrigin = origin;
        mSigninButtonText = signinButtonText;
        mSigninButtonClicked = false;
    }

    /**
     *  Creates and shows the dialog which allows user to choose credentials for login.
     *  @param credentials Credentials to display in the dialog.
     *  @param title Title message for the dialog, which can contain Smart Lock branding.
     *  @param titleLinkStart Start of a link in case title contains Smart Lock branding.
     *  @param titleLinkEnd End of a link in case title contains Smart Lock branding.
     *  @param origin Address of the web page, where dialog was triggered.
     */
    @CalledByNative
    private static AccountChooserDialog createAndShowAccountChooser(WindowAndroid windowAndroid,
            long nativeAccountChooserDialog, Credential[] credentials, String title,
            int titleLinkStart, int titleLinkEnd, String origin, String signinButtonText) {
        Activity activity = windowAndroid.getActivity().get();
        if (activity == null) return null;
        AccountChooserDialog chooser =
                new AccountChooserDialog(activity, nativeAccountChooserDialog, credentials, title,
                        titleLinkStart, titleLinkEnd, origin, signinButtonText);
        chooser.show();
        return chooser;
    }

    private ArrayAdapter<Credential> generateAccountsArrayAdapter(
            Context context, Credential[] credentials) {
        return new ArrayAdapter<Credential>(context, 0 /* resource */, credentials) {
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                if (convertView == null) {
                    LayoutInflater inflater = LayoutInflater.from(getContext());
                    convertView =
                            inflater.inflate(R.layout.account_chooser_dialog_item, parent, false);
                }
                convertView.setSelected(false);
                convertView.setOnClickListener(view -> {
                    mCredential = mCredentials[position];
                    if (mDialog != null) mDialog.dismiss();
                });
                convertView.setTag(position);

                Credential credential = getItem(position);

                ImageView avatarView = (ImageView) convertView.findViewById(R.id.profile_image);
                Drawable avatar = credential.getAvatar();
                if (avatar == null) {
                    avatar = AppCompatResources.getDrawable(
                            getContext(), R.drawable.logo_avatar_anonymous);
                }
                avatarView.setImageDrawable(avatar);

                TextView mainNameView = (TextView) convertView.findViewById(R.id.main_name);
                TextView secondaryNameView =
                        (TextView) convertView.findViewById(R.id.secondary_name);
                if (credential.getFederation().isEmpty()) {
                    // Not federated credentials case
                    if (credential.getDisplayName().isEmpty()) {
                        mainNameView.setText(credential.getUsername());
                        secondaryNameView.setVisibility(View.GONE);
                    } else {
                        mainNameView.setText(credential.getDisplayName());
                        secondaryNameView.setText(credential.getUsername());
                        secondaryNameView.setVisibility(View.VISIBLE);
                    }
                } else {
                    mainNameView.setText(credential.getUsername());
                    secondaryNameView.setText(credential.getFederation());
                    secondaryNameView.setVisibility(View.VISIBLE);
                }

                ImageButton pslInfoButton =
                        (ImageButton) convertView.findViewById(R.id.psl_info_btn);
                final String originUrl = credential.getOriginUrl();

                if (!originUrl.isEmpty()) {
                    pslInfoButton.setVisibility(View.VISIBLE);
                    pslInfoButton.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            showTooltip(view, UrlFormatter.formatUrlForSecurityDisplay(originUrl),
                                    R.layout.material_tooltip);
                        }
                    });
                }

                return convertView;
            }
        };
    }

    private void show() {
        View titleView =
                LayoutInflater.from(mContext).inflate(R.layout.account_chooser_dialog_title, null);
        TextView origin = (TextView) titleView.findViewById(R.id.origin);
        origin.setText(mOrigin);
        TextView titleMessageText = (TextView) titleView.findViewById(R.id.title);
        if (mTitleLinkStart != 0 && mTitleLinkEnd != 0) {
            SpannableString spanableTitle = new SpannableString(mTitle);
            spanableTitle.setSpan(new ClickableSpan() {
                @Override
                public void onClick(View view) {
                    if (mNativeAccountChooserDialog != 0) {
                        AccountChooserDialogJni.get().onLinkClicked(
                                mNativeAccountChooserDialog, AccountChooserDialog.this);
                    }
                    mDialog.dismiss();
                }
            }, mTitleLinkStart, mTitleLinkEnd, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
            titleMessageText.setText(spanableTitle, TextView.BufferType.SPANNABLE);
            titleMessageText.setMovementMethod(LinkMovementMethod.getInstance());
        } else {
            titleMessageText.setText(mTitle);
        }
        mAdapter = generateAccountsArrayAdapter(mContext, mCredentials);
        final AlertDialog.Builder builder =
                new AlertDialog.Builder(mContext, R.style.ThemeOverlay_BrowserUI_AlertDialog)
                        .setCustomTitle(titleView)
                        .setNegativeButton(R.string.cancel, this)
                        .setAdapter(mAdapter, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int item) {
                                mCredential = mCredentials[item];
                            }
                        });
        if (!TextUtils.isEmpty(mSigninButtonText)) {
            builder.setPositiveButton(mSigninButtonText, this);
        }
        mDialog = builder.create();
        mDialog.setOnDismissListener(this);
        mDialog.show();
    }

    // status_bar_height is not a public framework resource, so we have to getIdentifier()
    @SuppressWarnings("DiscouragedApi")
    private void showTooltip(View view, String message, int layoutId) {
        Context context = view.getContext();
        Resources resources = context.getResources();
        LayoutInflater inflater = LayoutInflater.from(context);

        TextView text = (TextView) inflater.inflate(layoutId, null);
        text.setText(message);
        text.announceForAccessibility(message);

        // The tooltip should be shown above and to the left (right for RTL) of the info button.
        // In order to do so the tooltip's location on the screen is determined. This location is
        // specified with regard to the top left corner and ignores RTL layouts. For this reason the
        // location of the tooltip is also specified as offsets to the top left corner of the
        // screen. Since the tooltip should be shown above the info button, the height of the
        // tooltip needs to be measured. Furthermore, the height of the statusbar is ignored when
        // obtaining the icon's screen location, but must be considered when specifying a y offset.
        // In addition, the measured width is needed in LTR layout, so that the right end of the
        // tooltip aligns with the right end of the info icon.
        final int[] screenPos = new int[2];
        view.getLocationOnScreen(screenPos);

        text.measure(MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));

        final int width = view.getWidth();

        final int xOffset = view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
                ? screenPos[0]
                : screenPos[0] + width - text.getMeasuredWidth();

        final int statusBarHeightResourceId =
                resources.getIdentifier("status_bar_height", "dimen", "android");

        final int statusBarHeight = statusBarHeightResourceId > 0
                ? resources.getDimensionPixelSize(statusBarHeightResourceId)
                : 0;

        final int tooltipMargin = resources.getDimensionPixelSize(R.dimen.psl_info_tooltip_margin);

        final int yOffset =
                screenPos[1] - tooltipMargin - statusBarHeight - text.getMeasuredHeight();

        // The xOffset is with regard to the left edge of the screen. Gravity.LEFT is deprecated,
        // which is why the following line is necessary.
        final int xGravity = view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? Gravity.END
                                                                                    : Gravity.START;

        Toast toast = new Toast(context, text);
        toast.setGravity(Gravity.TOP | xGravity, xOffset, yOffset);
        toast.setDuration(Toast.LENGTH_SHORT);
        toast.show();
    }

    @CalledByNative
    private void imageFetchComplete(int index, Bitmap avatarBitmap) {
        if (mNativeAccountChooserDialog == 0) return;
        assert index >= 0 && index < mCredentials.length;
        assert mCredentials[index] != null;
        Drawable avatar = AvatarGenerator.makeRoundAvatar(
                mContext.getResources(), avatarBitmap, avatarBitmap.getHeight());
        mCredentials[index].setAvatar(avatar);
        ListView view = mDialog.getListView();
        if (index >= view.getFirstVisiblePosition() && index <= view.getLastVisiblePosition()) {
            // Profile image is in the visible range.
            View credentialView = view.getChildAt(index - view.getFirstVisiblePosition());
            if (credentialView == null) return;
            ImageView avatarView = (ImageView) credentialView.findViewById(R.id.profile_image);
            avatarView.setImageDrawable(avatar);
        }
    }

    @CalledByNative
    private void notifyNativeDestroyed() {
        mNativeAccountChooserDialog = 0;
        if (mDialog != null) mDialog.dismiss();
    }

    @Override
    public void onClick(DialogInterface dialog, int whichButton) {
        if (whichButton == DialogInterface.BUTTON_POSITIVE) {
            mCredential = mCredentials[0];
            mSigninButtonClicked = true;
        }
    }

    @Override
    public void onDismiss(DialogInterface dialog) {
        mDialog = null;
        if (mNativeAccountChooserDialog == 0) return;
        if (mCredential != null) {
            AccountChooserDialogJni.get().onCredentialClicked(mNativeAccountChooserDialog,
                    AccountChooserDialog.this, mCredential.getIndex(), mSigninButtonClicked);
        } else {
            AccountChooserDialogJni.get().cancelDialog(
                    mNativeAccountChooserDialog, AccountChooserDialog.this);
        }
    }

    @NativeMethods
    interface Natives {
        void onCredentialClicked(long nativeAccountChooserDialogAndroid,
                AccountChooserDialog caller, int credentialId, boolean signinButtonClicked);
        void cancelDialog(long nativeAccountChooserDialogAndroid, AccountChooserDialog caller);
        void onLinkClicked(long nativeAccountChooserDialogAndroid, AccountChooserDialog caller);
    }
}
