/*
 * 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, softwareateCre
 * 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.contacts.group;

import android.app.Dialog;
import android.app.DialogFragment;
import android.app.LoaderManager;
import android.content.Context;
import android.content.CursorLoader;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract.Groups;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import androidx.appcompat.app.AlertDialog;

import com.android.contacts.ContactSaveService;
import com.android.contacts.R;
import com.android.contacts.model.account.AccountWithDataSet;

import com.google.android.material.textfield.TextInputLayout;
import com.google.common.base.Strings;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * Edits the name of a group.
 */
public final class GroupNameEditDialogFragment extends DialogFragment implements
        LoaderManager.LoaderCallbacks<Cursor> {

    private static final String KEY_GROUP_NAME = "groupName";

    private static final String ARG_IS_INSERT = "isInsert";
    private static final String ARG_GROUP_NAME = "groupName";
    private static final String ARG_ACCOUNT = "account";
    private static final String ARG_CALLBACK_ACTION = "callbackAction";
    private static final String ARG_GROUP_ID = "groupId";

    private static final long NO_GROUP_ID = -1;


    /** Callbacks for hosts of the {@link GroupNameEditDialogFragment}. */
    public interface Listener {
        void onGroupNameEditCancelled();

        void onGroupNameEditCompleted(String name);

        public static final Listener None = new Listener() {
            @Override
            public void onGroupNameEditCancelled() {
            }

            @Override
            public void onGroupNameEditCompleted(String name) {
            }
        };
    }

    private boolean mIsInsert;
    private String mGroupName;
    private long mGroupId;
    private Listener mListener;
    private AccountWithDataSet mAccount;
    private EditText mGroupNameEditText;
    private TextInputLayout mGroupNameTextLayout;
    private Set<String> mExistingGroups = Collections.emptySet();

    public static GroupNameEditDialogFragment newInstanceForCreation(
            AccountWithDataSet account, String callbackAction) {
        return newInstance(account, callbackAction, NO_GROUP_ID, null);
    }

    public static GroupNameEditDialogFragment newInstanceForUpdate(
            AccountWithDataSet account, String callbackAction, long groupId, String groupName) {
        return newInstance(account, callbackAction, groupId, groupName);
    }

    private static GroupNameEditDialogFragment newInstance(
            AccountWithDataSet account, String callbackAction, long groupId, String groupName) {
        if (account == null) {
            throw new IllegalArgumentException("Invalid account");
        }
        final boolean isInsert = groupId == NO_GROUP_ID;
        final Bundle args = new Bundle();
        args.putBoolean(ARG_IS_INSERT, isInsert);
        args.putLong(ARG_GROUP_ID, groupId);
        args.putString(ARG_GROUP_NAME, groupName);
        args.putParcelable(ARG_ACCOUNT, account);
        args.putString(ARG_CALLBACK_ACTION, callbackAction);

        final GroupNameEditDialogFragment dialog = new GroupNameEditDialogFragment();
        dialog.setArguments(args);
        return dialog;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setStyle(STYLE_NORMAL, R.style.ContactsAlertDialogThemeAppCompat);
        final Bundle args = getArguments();
        if (savedInstanceState == null) {
            mGroupName = args.getString(KEY_GROUP_NAME);
        } else {
            mGroupName = savedInstanceState.getString(ARG_GROUP_NAME);
        }

        mGroupId = args.getLong(ARG_GROUP_ID, NO_GROUP_ID);
        mIsInsert = args.getBoolean(ARG_IS_INSERT, true);
        mAccount = getArguments().getParcelable(ARG_ACCOUNT);

        // There is only one loader so the id arg doesn't matter.
        getLoaderManager().initLoader(0, null, this);
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Build a dialog with two buttons and a view of a single EditText input field
        final TextView title = (TextView) View.inflate(getActivity(), R.layout.dialog_title, null);
        title.setText(mIsInsert
                ? R.string.group_name_dialog_insert_title
                : R.string.group_name_dialog_update_title);
        final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), getTheme())
                .setCustomTitle(title)
                .setView(R.layout.group_name_edit_dialog)
                .setNegativeButton(android.R.string.cancel, new OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        hideInputMethod();
                        getListener().onGroupNameEditCancelled();
                        dismiss();
                    }
                })
                // The Positive button listener is defined below in the OnShowListener to
                // allow for input validation
                .setPositiveButton(android.R.string.ok, null);

        // Disable the create button when the name is empty
        final AlertDialog alertDialog = builder.create();
        alertDialog.getWindow().setSoftInputMode(
                WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
        alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                mGroupNameEditText = (EditText) alertDialog.findViewById(android.R.id.text1);
                mGroupNameTextLayout =
                        (TextInputLayout) alertDialog.findViewById(R.id.text_input_layout);
                if (!TextUtils.isEmpty(mGroupName)) {
                    mGroupNameEditText.setText(mGroupName);
                    // Guard against already created group names that are longer than the max
                    final int maxLength = getResources().getInteger(
                            R.integer.group_name_max_length);
                    mGroupNameEditText.setSelection(
                            mGroupName.length() > maxLength ? maxLength : mGroupName.length());
                }
                showInputMethod(mGroupNameEditText);

                final Button createButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
                createButton.setEnabled(!TextUtils.isEmpty(getGroupName()));

                // Override the click listener to prevent dismissal if creating a duplicate group.
                createButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        maybePersistCurrentGroupName(v);
                    }
                });
                mGroupNameEditText.addTextChangedListener(new TextWatcher() {
                    @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) {
                        mGroupNameTextLayout.setError(null);
                        createButton.setEnabled(!TextUtils.isEmpty(s));
                    }
                });
            }
        });

        return alertDialog;
    }

    /**
     * Sets the listener for the rename
     *
     * Setting a listener on a fragment is error prone since it will be lost if the fragment
     * is recreated. This exists because it is used from a view class (GroupMembersView) which
     * needs to modify it's state when this fragment updates the name.
     *
     * @param listener the listener. can be null
     */
    public void setListener(Listener listener) {
        mListener = listener;
    }

    private boolean hasNameChanged() {
        final String name = Strings.nullToEmpty(getGroupName());
        final String originalName = getArguments().getString(ARG_GROUP_NAME);
        return (mIsInsert && !name.isEmpty()) || !name.equals(originalName);
    }

    private void maybePersistCurrentGroupName(View button) {
        if (!hasNameChanged()) {
            dismiss();
            return;
        }
        String name = getGroupName();
        // Trim group name, when group is saved.
        // When "Group" exists, do not save " Group ". This behavior is the same as Google Contacts.
        if (!TextUtils.isEmpty(name)) {
            name = name.trim();
        }
        // Note we don't check if the loader finished populating mExistingGroups. It's not the
        // end of the world if the user ends up with a duplicate group and in practice it should
        // never really happen (the query should complete much sooner than the user can edit the
        // label)
        if (mExistingGroups.contains(name)) {
            mGroupNameTextLayout.setError(
                    getString(R.string.groupExistsErrorMessage));
            button.setEnabled(false);
            return;
        }
        final String callbackAction = getArguments().getString(ARG_CALLBACK_ACTION);
        final Intent serviceIntent;
        if (mIsInsert) {
            serviceIntent = ContactSaveService.createNewGroupIntent(getActivity(), mAccount,
                    name, null, getActivity().getClass(), callbackAction);
        } else {
            serviceIntent = ContactSaveService.createGroupRenameIntent(getActivity(), mGroupId,
                    name, getActivity().getClass(), callbackAction);
        }
        ContactSaveService.startService(getActivity(), serviceIntent);
        getListener().onGroupNameEditCompleted(mGroupName);
        dismiss();
    }

    @Override
    public void onCancel(DialogInterface dialog) {
        super.onCancel(dialog);
        getListener().onGroupNameEditCancelled();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString(KEY_GROUP_NAME, getGroupName());
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // Only a single loader so id is ignored.
        return new CursorLoader(getActivity(), Groups.CONTENT_SUMMARY_URI,
                new String[]{Groups.TITLE, Groups.SYSTEM_ID, Groups.ACCOUNT_TYPE,
                        Groups.SUMMARY_COUNT, Groups.GROUP_IS_READ_ONLY},
                getSelection(), getSelectionArgs(), null);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        mExistingGroups = new HashSet<>();
        final GroupUtil.GroupsProjection projection = new GroupUtil.GroupsProjection(data);
        // Initialize cursor's position. If Activity relaunched by orientation change,
        // only onLoadFinished is called. OnCreateLoader is not called.
        // The cursor's position is remain end position by moveToNext when the last onLoadFinished
        // was called. Therefore, if cursor position was not initialized mExistingGroups is empty.
        data.moveToPosition(-1);
        while (data.moveToNext()) {
            String title = projection.getTitle(data);
            // Trim existing group name.
            // When " Group " exists, do not save "Group".
            // This behavior is the same as Google Contacts.
            if (!TextUtils.isEmpty(title)) {
                title = title.trim();
            }
            // Empty system groups aren't shown in the nav drawer so it would be confusing to tell
            // the user that they already exist. Instead we allow them to create a duplicate
            // group in this case. This is how the web handles this case as well (it creates a
            // new non-system group if a new group with a title that matches a system group is
            // create).
            if (projection.isEmptyFFCGroup(data)) {
                continue;
            }
            mExistingGroups.add(title);
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
    }

    private void showInputMethod(View view) {
        if (getActivity() == null) {
            return;
        }
        final InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(
                Context.INPUT_METHOD_SERVICE);
        if (imm != null) {
            imm.showSoftInput(view, /* flags */ 0);
        }
    }

    private void hideInputMethod() {
        final InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(
                Context.INPUT_METHOD_SERVICE);
        if (imm != null && mGroupNameEditText != null) {
            imm.hideSoftInputFromWindow(mGroupNameEditText.getWindowToken(), /* flags */ 0);
        }
    }

    private Listener getListener() {
        if (mListener != null) {
            return mListener;
        } else if (getActivity() instanceof Listener) {
            return (Listener) getActivity();
        } else {
            return Listener.None;
        }
    }

    private String getGroupName() {
        return mGroupNameEditText == null || mGroupNameEditText.getText() == null
                ? null : mGroupNameEditText.getText().toString();
    }

    private String getSelection() {
        final StringBuilder builder = new StringBuilder();
        builder.append(Groups.ACCOUNT_NAME).append(mAccount.name == null ? " IS NULL " : "=?")
                .append(" AND ")
                .append(Groups.ACCOUNT_TYPE).append(mAccount.type == null ? " IS NULL " : "=?")
                .append(" AND ")
                .append(Groups.DATA_SET).append(mAccount.dataSet == null ? " IS NULL " : "=?")
                .append(" AND ")
                .append(Groups.DELETED).append("=0");
        return builder.toString();
    }

    private String[] getSelectionArgs() {
        if (mAccount.isNullAccount()) {
            return null;
        } else if (mAccount.dataSet == null) {
            return new String[]{
                    mAccount.name,
                    mAccount.type
            };
        } else {
            return new String[]{
                    mAccount.name,
                    mAccount.type,
                    mAccount.dataSet
            };
        }
    }
}
