/*
 * Copyright (C) 2019 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.car.settings.common;

import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Parcelable;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;

import com.android.car.ui.AlertDialogBuilder;
import com.android.car.ui.preference.CarUiDialogFragment;

/**
 * Common dialog that can be used across the settings app to ask the user to confirm their desired
 * action.
 */
public class ConfirmationDialogFragment extends CarUiDialogFragment {

    /** Builder to help construct {@link ConfirmationDialogFragment}. */
    public static class Builder {

        private final Context mContext;
        private Bundle mArgs;
        private String mTitle;
        private String mMessage;
        private String mPosLabel;
        private String mNegLabel;
        private String mNeuLabel;
        private ConfirmListener mConfirmListener;
        private RejectListener mRejectListener;
        private NeutralListener mNeutralListener;
        private DismissListener mDismissListener;

        public Builder(Context context) {
            mContext = context;
        }

        /** Sets the title. */
        public Builder setTitle(String title) {
            mTitle = title;
            return this;
        }

        /** Sets the title. */
        public Builder setTitle(@StringRes int title) {
            mTitle = mContext.getString(title);
            return this;
        }

        /** Sets the message. */
        public Builder setMessage(String message) {
            mMessage = message;
            return this;
        }

        /** Sets the message. */
        public Builder setMessage(@StringRes int message) {
            mMessage = mContext.getString(message);
            return this;
        }

        /** Sets the positive button label. */
        public Builder setPositiveButton(String label, ConfirmListener confirmListener) {
            mPosLabel = label;
            mConfirmListener = confirmListener;
            return this;
        }

        /** Sets the positive button label. */
        public Builder setPositiveButton(@StringRes int label, ConfirmListener confirmListener) {
            mPosLabel = mContext.getString(label);
            mConfirmListener = confirmListener;
            return this;
        }

        /** Sets the negative button label. */
        public Builder setNegativeButton(String label, RejectListener rejectListener) {
            mNegLabel = label;
            mRejectListener = rejectListener;
            return this;
        }

        /** Sets the negative button label. */
        public Builder setNegativeButton(@StringRes int label, RejectListener rejectListener) {
            mNegLabel = mContext.getString(label);
            mRejectListener = rejectListener;
            return this;
        }

        /** Sets the neutral button label. */
        public Builder setNeutralButton(@StringRes int label, NeutralListener neutralListener) {
            mNeuLabel = mContext.getString(label);
            mNeutralListener = neutralListener;
            return this;
        }

        /** Sets the listener for dialog dismiss. */
        public Builder setDismissListener(DismissListener dismissListener) {
            mDismissListener = dismissListener;
            return this;
        }

        /** Adds an argument string to the argument bundle. */
        public Builder addArgumentString(String argumentKey, String argument) {
            if (mArgs == null) {
                mArgs = new Bundle();
            }
            mArgs.putString(argumentKey, argument);
            return this;
        }

        /** Adds an argument long to the argument bundle. */
        public Builder addArgumentLong(String argumentKey, long argument) {
            if (mArgs == null) {
                mArgs = new Bundle();
            }
            mArgs.putLong(argumentKey, argument);
            return this;
        }

        /** Adds an argument boolean to the argument bundle. */
        public Builder addArgumentBoolean(String argumentKey, boolean argument) {
            if (mArgs == null) {
                mArgs = new Bundle();
            }
            mArgs.putBoolean(argumentKey, argument);
            return this;
        }

        /** Adds an argument Parcelable to the argument bundle. */
        public Builder addArgumentParcelable(String argumentKey, Parcelable argument) {
            if (mArgs == null) {
                mArgs = new Bundle();
            }
            mArgs.putParcelable(argumentKey, argument);
            return this;
        }

        /** Constructs the {@link ConfirmationDialogFragment}. */
        public ConfirmationDialogFragment build() {
            return ConfirmationDialogFragment.init(this);
        }
    }

    /** Identifier used to launch the dialog fragment. */
    public static final String TAG = "ConfirmationDialogFragment";

    // Argument keys are prefixed with TAG in order to reduce the changes of collision with user
    // provided arguments.
    private static final String ALL_ARGUMENTS_KEY = TAG + "_all_arguments";
    private static final String ARGUMENTS_KEY = TAG + "_arguments";
    private static final String TITLE_KEY = TAG + "_title";
    private static final String MESSAGE_KEY = TAG + "_message";
    private static final String POSITIVE_KEY = TAG + "_positive";
    private static final String NEGATIVE_KEY = TAG + "_negative";
    private static final String NEUTRAL_KEY = TAG + "_neutral";

    private String mTitle;
    private String mMessage;
    private String mPosLabel;
    private String mNegLabel;
    private String mNeuLabel;
    private ConfirmListener mConfirmListener;
    private RejectListener mRejectListener;
    private NeutralListener mNeutralListener;
    private DismissListener mDismissListener;

    /** Constructs the dialog fragment from the arguments provided in the {@link Builder} */
    private static ConfirmationDialogFragment init(Builder builder) {
        ConfirmationDialogFragment dialogFragment = new ConfirmationDialogFragment();
        Bundle args = new Bundle();
        args.putBundle(ARGUMENTS_KEY, builder.mArgs);
        args.putString(TITLE_KEY, builder.mTitle);
        args.putString(MESSAGE_KEY, builder.mMessage);
        args.putString(POSITIVE_KEY, builder.mPosLabel);
        args.putString(NEGATIVE_KEY, builder.mNegLabel);
        args.putString(NEUTRAL_KEY, builder.mNeuLabel);
        dialogFragment.setArguments(args);
        dialogFragment.setConfirmListener(builder.mConfirmListener);
        dialogFragment.setRejectListener(builder.mRejectListener);
        dialogFragment.setNeutralListener(builder.mNeutralListener);
        dialogFragment.setDismissListener(builder.mDismissListener);
        return dialogFragment;
    }

    /**
     * Since it is possible for the listeners to be unregistered on configuration change, provide a
     * way to reattach the listeners.
     */
    public static void resetListeners(@Nullable ConfirmationDialogFragment dialogFragment,
            @Nullable ConfirmListener confirmListener,
            @Nullable RejectListener rejectListener,
            @Nullable NeutralListener neutralListener) {
        if (dialogFragment != null) {
            dialogFragment.setConfirmListener(confirmListener);
            dialogFragment.setRejectListener(rejectListener);
            dialogFragment.setNeutralListener(neutralListener);
        }
    }

    /** Sets the listeners which listens for the dialog dismissed */
    public void setDismissListener(DismissListener dismissListener) {
        mDismissListener = dismissListener;
    }

    /** Gets the listener which listens for the dialog dismissed */
    @Nullable
    public DismissListener getDismissListener() {
        return mDismissListener;
    }

    /** Sets the listener which listens to a click on the positive button. */
    private void setConfirmListener(ConfirmListener confirmListener) {
        mConfirmListener = confirmListener;
    }

    /** Gets the listener which listens to a click on the positive button */
    @Nullable
    public ConfirmListener getConfirmListener() {
        return mConfirmListener;
    }

    /** Sets the listener which listens to a click on the negative button. */
    private void setRejectListener(RejectListener rejectListener) {
        mRejectListener = rejectListener;
    }

    /** Gets the listener which listens to a click on the negative button. */
    @Nullable
    public RejectListener getRejectListener() {
        return mRejectListener;
    }

    /** Sets the listener which listens to a click on the neutral button. */
    private void setNeutralListener(NeutralListener neutralListener) {
        mNeutralListener = neutralListener;
    }

    /** Gets the listener which listens to a click on the neutral button. */
    @Nullable
    public NeutralListener getNeutralListener() {
        return mNeutralListener;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Bundle args = getArguments();
        if (savedInstanceState != null) {
            args = savedInstanceState.getBundle(ALL_ARGUMENTS_KEY);
        }

        if (args != null) {
            mTitle = getArguments().getString(TITLE_KEY);
            mMessage = getArguments().getString(MESSAGE_KEY);
            mPosLabel = getArguments().getString(POSITIVE_KEY);
            mNegLabel = getArguments().getString(NEGATIVE_KEY);
            mNeuLabel = getArguments().getString(NEUTRAL_KEY);
        }
    }

    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBundle(ALL_ARGUMENTS_KEY, getArguments());
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
        AlertDialogBuilder builder = new AlertDialogBuilder(getContext());
        if (!TextUtils.isEmpty(mTitle)) {
            builder.setTitle(mTitle);
        }
        if (!TextUtils.isEmpty(mMessage)) {
            builder.setMessage(mMessage);
        }
        if (!TextUtils.isEmpty(mPosLabel)) {
            builder.setPositiveButton(mPosLabel, this);
        }
        if (!TextUtils.isEmpty(mNegLabel)) {
            builder.setNegativeButton(mNegLabel, this);
        }
        if (!TextUtils.isEmpty(mNeuLabel)) {
            builder.setNeutralButton(mNeuLabel, this);
        }
        return builder.create();
    }

    @Override
    protected void onDialogClosed(boolean positiveResult) {
        if (mDismissListener != null) {
            mDismissListener.onDismiss(getArguments().getBundle(ARGUMENTS_KEY), positiveResult);
        }
    }

    @Override
    public void onClick(DialogInterface dialog, int which) {
        super.onClick(dialog, which);
        if (which == DialogInterface.BUTTON_POSITIVE) {
            if (mConfirmListener != null) {
                mConfirmListener.onConfirm(getArguments().getBundle(ARGUMENTS_KEY));
            }
        } else if (which == DialogInterface.BUTTON_NEGATIVE) {
            if (mRejectListener != null) {
                mRejectListener.onReject(getArguments().getBundle(ARGUMENTS_KEY));
            }
        } else if (which == DialogInterface.BUTTON_NEUTRAL) {
            if (mNeutralListener != null) {
                mNeutralListener.onNeutral(getArguments().getBundle(ARGUMENTS_KEY));
            }
        }
    }

    /** Listens to the confirmation action. */
    public interface ConfirmListener {
        /**
         * Defines the action to take on confirm. The bundle will contain the arguments added when
         * constructing the dialog through with {@link Builder#addArgumentString(String, String)}.
         */
        void onConfirm(@Nullable Bundle arguments);
    }

    /** Listens to the rejection action. */
    public interface RejectListener {
        /**
         * Defines the action to take on reject. The bundle will contain the arguments added when
         * constructing the dialog through with {@link Builder#addArgumentString(String, String)}.
         */
        void onReject(@Nullable Bundle arguments);
    }

    /** Listens to the neutral button action. */
    public interface NeutralListener {
        /**
         * Defines the action to take on neutral button click. The bundle will contain the arguments
         * added when constructing the dialog through with
         * {@link Builder#addArgumentString(String, String)}.
         */
        void onNeutral(@Nullable Bundle arguments);
    }

    /** Listens to the dismiss action. */
    public interface DismissListener {
        /**
         * Defines the action to take when the dialog is closed.
         * @param arguments - bundle containing the arguments added when constructing the dialog
         * through with {@link Builder#addArgumentString(String, String)}.
         * @param positiveResult - whether or not the dialog was dismissed because of a positive
         * button press.
         */
        void onDismiss(@Nullable Bundle arguments, boolean positiveResult);
    }
}
