/*
 * Copyright (C) 2018 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.setupwizardlib;

import android.annotation.CallSuper;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import androidx.annotation.LayoutRes;
import androidx.annotation.StyleRes;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;

import com.android.car.setupwizardlib.util.CarDrivingStateMonitor;
import com.android.car.setupwizardlib.util.CarWizardManagerHelper;


/**
 * Base Activity for CarSetupWizard screens that provides a variety of helper functions that make
 * it easier to work with the CarSetupWizardLayout and moving between Setup Wizard screens.
 *
 * <p>This activity sets an instance of {@link CarSetupWizardLayout} as the Content View.
 * <p>Provides helper methods like {@link #setContentFragment} and {@link #onContentFragmentSet} for
 * easy updating of the CarSetupWizard layout components based on the current Fragment being
 * displayed
 * <p>Provides helper methods like {@link #nextAction} and {@link #finishAction} for properly
 * moving to the next and previous screens in a Setup Wizard
 * <p>Provides setters {@link #setBackButtonVisible(boolean)} for setting CarSetupWizardLayout
 * component attributes
 *
 * @deprecated Use {@link BaseCompatActivity} or {@link BaseDesignActivity}.
 */
@Deprecated
public class BaseActivity extends FragmentActivity {
    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
    static final String CONTENT_FRAGMENT_TAG = "CONTENT_FRAGMENT_TAG";
    /**
     * Wizard Manager does not actually return an activity result, but if we invoke Wizard
     * Manager without requesting a result, the framework will choose not to issue a call to
     * onActivityResult with RESULT_CANCELED when navigating backward.
     */
    protected static final int REQUEST_CODE_NEXT = 10000;

    private boolean mNextActionAlreadyTriggered;

    /**
     * To implement a specific request code, see the following:
     *
     * <pre>{@code
     * static final int REQUEST_CODE_A = REQUEST_CODE_FIRST_USER;
     * static final int REQUEST_CODE_B = REQUEST_CODE_FIRST_USER + 1;</pre>
     * }
     */
    protected static final int REQUEST_CODE_FIRST_USER = 10001;

    private int mResultCode = RESULT_CANCELED;

    /**
     * Whether it is safe to make transactions on the
     * {@link androidx.fragment.app.FragmentManager}. This variable prevents a possible exception
     * when calling commit() on the FragmentManager.
     *
     * <p>The default value is {@code true} because it is only after
     * {@link #onSaveInstanceState(Bundle)} that fragment commits are not allowed.
     */
    private boolean mAllowFragmentCommits = true;
    private CarSetupWizardLayout mCarSetupWizardLayout;
    private Intent mResultData;

    @Override
    @CallSuper
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.base_activity);

        mCarSetupWizardLayout = findViewById(R.id.car_setup_wizard_layout);

        mCarSetupWizardLayout.setBackButtonListener(v -> {
            if (!handleBackButton()) {
                finish();
            }
        });

        resetPrimaryToolbarButtonOnClickListener();
        resetSecondaryToolbarButtonOnClickListener();

        /* If this activity has a saved instance and a content fragment, call onContentFragmentSet()
         * so the appropriate views/events are updated.
         */
        if (savedInstanceState != null && getContentFragment() != null) {
            onContentFragmentSet(getContentFragment());
        }
    }

    @Override
    @CallSuper
    protected void onStart() {
        super.onStart();
        // Fragment commits are not allowed once the Activity's state has been saved. Once
        // onStart() has been called, the FragmentManager should now allow commits.
        mAllowFragmentCommits = true;
        // Need to check for UX restrictions to setup wizard running and exit if they are enabled.
        CarDrivingStateMonitor.get(this).startMonitor();
    }

    @Override
    @CallSuper
    protected void onResume() {
        super.onResume();
        // Need to reset next buttons so that they can be pressed again.
        mNextActionAlreadyTriggered = false;
    }

    @Override
    @CallSuper
    protected void onPause() {
        super.onPause();
        // Need this for visibility for tests.
    }

    @Override
    @CallSuper
    protected void onStop() {
        super.onStop();
        // Trigger a stop to the CarDrivingStateMonitor. If the monitor is restarted soon by a
        // subsequent activity then this will do nothing so as not to thrash the monitor.
        CarDrivingStateMonitor.get(this).stopMonitor();
    }

    @Override
    @CallSuper
    protected void onSaveInstanceState(Bundle outState) {
        // A transaction can only be committed with this method prior to its containing activity
        // saving its state.
        mAllowFragmentCommits = false;
        super.onSaveInstanceState(outState);
    }

    // Content Fragment accessors

    /**
     * Sets the content fragment and adds it to the fragment backstack.
     */
    @CallSuper
    protected void setContentFragmentWithBackstack(Fragment fragment) {
        if (mAllowFragmentCommits) {
            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.car_setup_wizard_layout, fragment, CONTENT_FRAGMENT_TAG)
                    .addToBackStack(null)
                    .commit();
            getSupportFragmentManager().executePendingTransactions();
            onContentFragmentSet(getContentFragment());
        }
    }

    /**
     * Returns the fragment that is currently being displayed as the content view.
     */
    @CallSuper
    protected Fragment getContentFragment() {
        return getSupportFragmentManager().findFragmentByTag(CONTENT_FRAGMENT_TAG);
    }

    /**
     * Sets the fragment that will be shown as the main content of this Activity.
     */
    @CallSuper
    protected void setContentFragment(Fragment fragment) {
        if (mAllowFragmentCommits) {
            getSupportFragmentManager().beginTransaction()
                    .setCustomAnimations(
                            android.R.animator.fade_in,
                            android.R.animator.fade_out,
                            android.R.animator.fade_in,
                            android.R.animator.fade_out)
                    .replace(R.id.car_setup_wizard_layout, fragment, CONTENT_FRAGMENT_TAG)
                    .commitNow();
            onContentFragmentSet(getContentFragment());
        }
    }

    /**
     * Pops the top Fragment from the Fragment backstack (immediately executing the transaction) and
     * then updates the CarSetupWizardLayout toolbar for the current fragment.
     *
     * @return {@code true} if a fragment was popped.
     */
    @CallSuper
    protected boolean popBackStackImmediate() {
        if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
            getSupportFragmentManager().popBackStackImmediate();
            onContentFragmentSet(getContentFragment());
            return true;
        }
        return false;
    }

    /**
     * Method to be overwritten by subclasses wanting to perform any additional actions when the
     * content fragment is set (usually via adding or popping from fragment backstack). For example,
     * the CarSetupWizardLayout toolbar usually needs to be changed when the fragment changes.
     */
    protected void onContentFragmentSet(Fragment fragment) {

    }

    /**
     * Sets the layout view that will be shown as the main content of this Activity. Should be used
     * when the activity does not hold a fragment.
     */
    @CallSuper
    protected View setContentLayout(@LayoutRes int id) {
        return getLayoutInflater().inflate(id, mCarSetupWizardLayout);
    }

    /**
     * Method to be overwritten by subclasses wanting to implement their own back behavior.
     * Default behavior is to pop a fragment off of the backstack if one exists, otherwise call
     * finish()
     *
     * @return {@code true} whether to call finish()
     */
    protected boolean handleBackButton() {
        return popBackStackImmediate();
    }

    /**
     * Called when nextAction has been invoked, should be overridden on derived class when it is
     * needed perform work when nextAction has been invoked.
     */
    protected void onNextActionInvoked() {
    }

    /**
     * Moves to the next Activity in the SetupWizard flow.
     */
    protected void nextAction(int resultCode) {
        nextAction(resultCode, null);
    }

    /**
     * Moves to the next Activity in the SetupWizard flow, and save the intent data.
     */
    protected void nextAction(int resultCode, Intent data) {
        if (resultCode == RESULT_CANCELED) {
            throw new IllegalArgumentException("Cannot call nextAction with RESULT_CANCELED");
        }
        setResultCode(resultCode, data);
        if (mNextActionAlreadyTriggered) {
            Log.v("CarSetupWizard",
                    "BaseActivity: nextAction triggered multiple times without page refresh, "
                            + "ignoring.");
            return;
        }
        mNextActionAlreadyTriggered = true;
        onNextActionInvoked();
        Intent nextIntent =
                CarWizardManagerHelper.getNextIntent(getIntent(), mResultCode, mResultData);
        startActivity(nextIntent);
    }

    /**
     * Method for finishing an action. The default behavior is to close out the screen and
     * go back to the previous one.
     */
    protected void finishAction() {
        finishAction(RESULT_CANCELED);
    }

    /**
     * Method for finishing an action with a non-default result code. This is a convenience
     * method to replace nextAction(resultCode); finish();
     */
    protected void finishAction(int resultCode) {
        finishAction(resultCode, null);
    }

    /**
     * Convenience method for nextAction(resultCode, data); finish();
     */
    protected void finishAction(int resultCode, Intent data) {
        if (resultCode != RESULT_CANCELED) {
            nextAction(resultCode, data);
        }
        finish();
    }

    /**
     * Method to retrieve resultCode saved via {@link #setResultCode(int, Intent)}
     */
    protected int getResultCode() {
        return mResultCode;
    }

    /**
     * Use instead of {@link #setResult(int, Intent)} so that the resultCode
     * and data can be referenced later
     */
    protected void setResultCode(int resultCode, Intent data) {
        mResultCode = resultCode;
        mResultData = data;
        setResult(resultCode, data);
    }

    /**
     * Use instead of {@link #setResult(int)} so that the resultCode can be referenced later
     */
    protected void setResultCode(int resultCode) {
        setResultCode(resultCode, getResultData());
    }

    /**
     * Method to retrieve intent data saved via {@link #setResultCode(int, Intent)}
     */
    protected Intent getResultData() {
        return mResultData;
    }


    // CarSetupWizardLayout Accessors

    /**
     * Sets whether the back button is visible. If this value is {@code true}, clicking the
     * button will take the user back to the previous screen in the setup flow.
     */
    protected void setBackButtonVisible(boolean visible) {
        mCarSetupWizardLayout.setBackButtonVisible(visible);
    }

    /**
     * Sets whether the toolbar title is visible.
     */
    protected void setToolbarTitleVisible(boolean visible) {
        mCarSetupWizardLayout.setToolbarTitleVisible(visible);
    }

    /**
     * Sets the text for the toolbar title.
     */
    protected void setToolbarTitleText(String text) {
        mCarSetupWizardLayout.setToolbarTitleText(text);
    }

    /**
     * Sets the text appearance for the toolbar title.
     */
    protected void setToolbarTitleStyle(@StyleRes int style) {
        mCarSetupWizardLayout.setToolbarTitleStyle(style);
    }

    /**
     * Sets whether the primary continue button is visible.
     */
    protected void setPrimaryToolbarButtonVisible(boolean visible) {
        mCarSetupWizardLayout.setPrimaryToolbarButtonVisible(visible);
    }

    /**
     * Sets whether the primary continue button is enabled. If this value is {@code true},
     * clicking the button will take the user to the next screen in the setup flow.
     */
    protected void setPrimaryToolbarButtonEnabled(boolean enabled) {
        mCarSetupWizardLayout.setPrimaryToolbarButtonEnabled(enabled);
    }

    /**
     * Sets the text of the primary continue button.
     */
    protected void setPrimaryToolbarButtonText(String text) {
        mCarSetupWizardLayout.setPrimaryToolbarButtonText(text);
    }

    /**
     * Sets whether the primary button is displayed as a flat or raised button.
     */
    protected void setPrimaryToolbarButtonFlat(boolean flat) {
        mCarSetupWizardLayout.setPrimaryToolbarButtonFlat(flat);
    }

    /**
     * Sets the primary button onClick behavior to a custom method.
     *
     * <p>NOTE: This will overwrite the primary tool bar button's default action to call
     * {@link #nextAction} with RESULT_OK.
     */
    protected void setPrimaryToolbarButtonOnClickListener(View.OnClickListener listener) {
        mCarSetupWizardLayout.setPrimaryToolbarButtonListener(listener);
    }

    /**
     * Reset's the primary toolbar button's on click listener to call {@link #nextAction} with
     * RESULT_OK
     */
    protected void resetPrimaryToolbarButtonOnClickListener() {
        setPrimaryToolbarButtonOnClickListener(v -> {
            nextAction(RESULT_OK);
        });

    }

    /**
     * Sets whether the secondary continue button is visible.
     */
    protected void setSecondaryToolbarButtonVisible(boolean visible) {
        mCarSetupWizardLayout.setSecondaryToolbarButtonVisible(visible);
    }

    /**
     * Sets whether the secondary continue button is enabled. If this value is {@code true},
     * clicking the button will take the user to the next screen in the setup flow.
     */
    protected void setSecondaryToolbarButtonEnabled(boolean enabled) {
        mCarSetupWizardLayout.setSecondaryToolbarButtonEnabled(enabled);
    }

    /**
     * Sets the text of the secondary continue button.
     */
    protected void setSecondaryToolbarButtonText(String text) {
        mCarSetupWizardLayout.setSecondaryToolbarButtonText(text);
    }

    /**
     * Sets the secondary button onClick behavior to a custom method.
     *
     * <p>NOTE: This will overwrite the secondary tool bar button's default action to call
     * {@link #nextAction} with RESULT_OK.
     */
    protected void setSecondaryToolbarButtonOnClickListener(View.OnClickListener listener) {
        mCarSetupWizardLayout.setSecondaryToolbarButtonListener(listener);
    }

    /**
     * Reset's the secondary toolbar button's on click listener to call {@link #nextAction} with
     * RESULT_OK
     */
    protected void resetSecondaryToolbarButtonOnClickListener() {
        setSecondaryToolbarButtonOnClickListener(v -> {
            nextAction(RESULT_OK);
        });
    }

    /**
     * Sets whether the progress bar is visible.
     */
    protected void setProgressBarVisible(boolean visible) {
        mCarSetupWizardLayout.setProgressBarVisible(visible);
    }

    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
    boolean getAllowFragmentCommits() {
        return mAllowFragmentCommits;
    }

    protected CarSetupWizardLayout getCarSetupWizardLayout() {
        return mCarSetupWizardLayout;
    }
}
