/*
 * 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.storagemanager.deletionhelper;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.storage.StorageManager;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceFragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import android.text.format.Formatter;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.Preconditions;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.applications.AppUtils;
import com.android.storagemanager.ButtonBarProvider;
import com.android.storagemanager.R;
import com.android.storagemanager.overlay.DeletionHelperFeatureProvider;
import com.android.storagemanager.overlay.FeatureFactory;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

/**
 * Settings screen for the deletion helper, which manually removes data which is not recently used.
 */
public class DeletionHelperSettings extends PreferenceFragment
        implements DeletionType.FreeableChangedListener, View.OnClickListener {
    public static final boolean COUNT_UNCHECKED = true;
    public static final boolean COUNT_CHECKED_ONLY = false;

    protected static final String APPS_KEY = "apps_group";
    protected static final String KEY_DOWNLOADS_PREFERENCE = "delete_downloads";
    protected static final String KEY_PHOTOS_VIDEOS_PREFERENCE = "delete_photos";
    protected static final String KEY_GAUGE_PREFERENCE = "deletion_gauge";

    private static final String THRESHOLD_KEY = "threshold_key";
    private static final int DOWNLOADS_LOADER_ID = 1;
    private static final int NUM_DELETION_TYPES = 3;
    private static final long UNSET = -1;

    private List<DeletionType> mDeletableContentList;
    private AppDeletionPreferenceGroup mApps;
    @VisibleForTesting AppDeletionType mAppBackend;
    @VisibleForTesting DownloadsDeletionPreferenceGroup mDownloadsPreference;
    private DownloadsDeletionType mDownloadsDeletion;
    private PhotosDeletionPreference mPhotoPreference;
    private Preference mGaugePreference;
    private DeletionType mPhotoVideoDeletion;
    private Button mCancel, mFree;
    private DeletionHelperFeatureProvider mProvider;
    private int mThresholdType;
    @VisibleForTesting long mBytesToFree = UNSET;
    private int mResult;
    private LoadingSpinnerController mLoadingController;

    public static DeletionHelperSettings newInstance(int thresholdType) {
        DeletionHelperSettings instance = new DeletionHelperSettings();
        Bundle bundle = new Bundle(1);
        bundle.putInt(THRESHOLD_KEY, thresholdType);
        instance.setArguments(bundle);
        return instance;
    }

    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        addPreferencesFromResource(R.xml.deletion_helper_list);
        mThresholdType = getArguments().getInt(THRESHOLD_KEY, AppsAsyncLoader.NORMAL_THRESHOLD);
        mApps = (AppDeletionPreferenceGroup) findPreference(APPS_KEY);
        mPhotoPreference = (PhotosDeletionPreference) findPreference(KEY_PHOTOS_VIDEOS_PREFERENCE);
        mProvider = FeatureFactory.getFactory(getActivity()).getDeletionHelperFeatureProvider();
        mLoadingController = new LoadingSpinnerController((DeletionHelperActivity) getActivity());
        if (mProvider != null) {
            mPhotoVideoDeletion =
                    mProvider.createPhotoVideoDeletionType(getContext(), mThresholdType);
        }

        HashSet<String> checkedApplications = null;
        if (savedInstanceState != null) {
            checkedApplications =
                    (HashSet<String>) savedInstanceState.getSerializable(
                            AppDeletionType.EXTRA_CHECKED_SET);
        }
        mAppBackend = new AppDeletionType(this, checkedApplications, mThresholdType);
        mAppBackend.registerView(mApps);
        mAppBackend.registerFreeableChangedListener(this);
        mApps.setDeletionType(mAppBackend);

        mDeletableContentList = new ArrayList<>(NUM_DELETION_TYPES);

        mGaugePreference = findPreference(KEY_GAUGE_PREFERENCE);
        Activity activity = getActivity();
        if (activity != null && mGaugePreference != null) {
            Intent intent = activity.getIntent();
            if (intent != null) {
                CharSequence gaugeTitle =
                        getGaugeString(getContext(), intent, activity.getCallingPackage());
                if (gaugeTitle != null) {
                    mGaugePreference.setTitle(gaugeTitle);

                    long requestedBytes =
                            intent.getLongExtra(StorageManager.EXTRA_REQUESTED_BYTES, UNSET);
                    mBytesToFree = requestedBytes;
                } else {
                    getPreferenceScreen().removePreference(mGaugePreference);
                }
            }
        }
    }

    protected static CharSequence getGaugeString(
            Context context, Intent intent, String packageName) {
        Preconditions.checkNotNull(intent);
        long requestedBytes = intent.getLongExtra(StorageManager.EXTRA_REQUESTED_BYTES, UNSET);
        if (requestedBytes > 0) {
            CharSequence callerLabel =
                    AppUtils.getApplicationLabel(context.getPackageManager(), packageName);
            // I really hope this isn't the case, but I can't ignore the possibility that we cannot
            // determine what app the referrer is.
            if (callerLabel == null) {
                return null;
            }
            return context.getString(
                    R.string.app_requesting_space,
                    callerLabel,
                    Formatter.formatFileSize(context, requestedBytes));
        }
        return null;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        initializeButtons();
        setHasOptionsMenu(true);
        Activity activity = getActivity();
        if (activity.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            activity.requestPermissions(
                    new String[] {Manifest.permission.READ_EXTERNAL_STORAGE},
                    0);
        }

        if (mProvider != null && mPhotoVideoDeletion != null) {
            mPhotoPreference.setDaysToKeep(mProvider.getDaysToKeep(mThresholdType));
            mPhotoPreference.registerFreeableChangedListener(this);
            mPhotoPreference.registerDeletionService(mPhotoVideoDeletion);
            mDeletableContentList.add(mPhotoVideoDeletion);
        } else {
            getPreferenceScreen().removePreference(mPhotoPreference);
            mPhotoPreference.setEnabled(false);
        }

        String[] uncheckedFiles = null;
        if (savedInstanceState != null) {
            uncheckedFiles =
                    savedInstanceState.getStringArray(
                            DownloadsDeletionType.EXTRA_UNCHECKED_DOWNLOADS);
        }
        mDownloadsPreference =
                (DownloadsDeletionPreferenceGroup) findPreference(KEY_DOWNLOADS_PREFERENCE);
        mDownloadsDeletion = new DownloadsDeletionType(getActivity(), uncheckedFiles);
        mDownloadsPreference.registerFreeableChangedListener(this);
        mDownloadsPreference.registerDeletionService(mDownloadsDeletion);
        mDeletableContentList.add(mDownloadsDeletion);
        if (isEmptyState()) {
            setupEmptyState();
        }
        mDeletableContentList.add(mAppBackend);
        updateFreeButtonText();
    }

    @VisibleForTesting
    void setupEmptyState() {
        final PreferenceScreen screen = getPreferenceScreen();
        if (mDownloadsPreference != null) {
            mDownloadsPreference.setChecked(false);
            screen.removePreference(mDownloadsPreference);
        }
        screen.removePreference(mApps);

        // Nulling out the downloads preferences means we won't accidentally delete what isn't
        // visible.
        mDownloadsDeletion = null;
        mDownloadsPreference = null;
    }

    private boolean isEmptyState() {
        // We know we are in the empty state if our loader is not using a threshold.
        return mThresholdType == AppsAsyncLoader.NO_THRESHOLD;
    }

    @Override
    public void onResume() {
        super.onResume();

        mLoadingController.initializeLoading(getListView());

        for (int i = 0, size = mDeletableContentList.size(); i < size; i++) {
            mDeletableContentList.get(i).onResume();
        }

        if (mDownloadsDeletion != null
                && getActivity().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
                        == PackageManager.PERMISSION_GRANTED) {
            getLoaderManager().initLoader(DOWNLOADS_LOADER_ID, new Bundle(), mDownloadsDeletion);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        for (int i = 0, size = mDeletableContentList.size(); i < size; i++) {
            mDeletableContentList.get(i).onPause();
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        for (int i = 0, size = mDeletableContentList.size(); i < size; i++) {
            mDeletableContentList.get(i).onSaveInstanceStateBundle(outState);
        }
    }

    @Override
    public void onFreeableChanged(int numItems, long bytesFreeable) {
        if (numItems > 0 || bytesFreeable > 0 || allTypesEmpty()) {
            if (mLoadingController != null) {
                mLoadingController.onCategoryLoad();
            }
        }

        // bytesFreeable is the number of bytes freed by a single deletion type. If it is non-zero,
        // there is stuff to free and we can enable it. If it is zero, though, we still need to get
        // getTotalFreeableSpace to check all deletion types.
        if (mFree != null) {
            mFree.setEnabled(bytesFreeable != 0 || getTotalFreeableSpace(COUNT_CHECKED_ONLY) != 0);
        }
        updateFreeButtonText();

        // Transition to empty state if all types have reported there is nothing to delete. Skip
        // the transition if we are already in no threshold mode
        if (allTypesEmpty() && !isEmptyState()) {
            startEmptyState();
        }
    }

    private boolean allTypesEmpty() {
        return mAppBackend.isEmpty()
                && (mDownloadsDeletion == null || mDownloadsDeletion.isEmpty())
                && (mPhotoVideoDeletion == null || mPhotoVideoDeletion.isEmpty());
    }

    private void startEmptyState() {
        if (getActivity() instanceof DeletionHelperActivity) {
            DeletionHelperActivity activity = (DeletionHelperActivity) getActivity();
            activity.setIsEmptyState(true /* isEmptyState */);
        }
    }

    /** Clears out the selected apps and data from the device and closes the fragment. */
    protected void clearData() {
        long bytesFreed = getTotalFreeableSpace(COUNT_CHECKED_ONLY);
        if (mBytesToFree != UNSET && bytesFreed >= mBytesToFree) {
            setResultCode(Activity.RESULT_OK);
        }

        // This should be fine as long as there is only one extra deletion feature.
        // In the future, this should be done in an async queue in order to not
        // interfere with the simultaneous PackageDeletionTask.
        Activity activity = getActivity();
        if (mPhotoPreference != null && mPhotoPreference.isChecked()) {
            mPhotoVideoDeletion.clearFreeableData(activity);
        }
        if (mDownloadsPreference != null) {
            mDownloadsDeletion.clearFreeableData(activity);
        }
        if (mAppBackend != null) {
            mAppBackend.clearFreeableData(activity);
        }
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.next_button) {
            ConfirmDeletionDialog dialog =
                    ConfirmDeletionDialog.newInstance(getTotalFreeableSpace(COUNT_CHECKED_ONLY));
            // The 0 is a placeholder for an optional result code.
            dialog.setTargetFragment(this, 0);
            dialog.show(getFragmentManager(), ConfirmDeletionDialog.TAG);
            MetricsLogger.action(getContext(), MetricsEvent.ACTION_DELETION_HELPER_CLEAR);
        } else {
            MetricsLogger.action(getContext(), MetricsEvent.ACTION_DELETION_HELPER_CANCEL);
            getActivity().finish();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[],
                                           int[] grantResults) {
        if (requestCode == 0) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                mDownloadsDeletion.onResume();
                getLoaderManager().initLoader(DOWNLOADS_LOADER_ID, new Bundle(),
                        mDownloadsDeletion);
            }
        }
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
        Activity activity = getActivity();
        String mHelpUri = getResources().getString(R.string.help_uri_deletion_helper);
        if (mHelpUri != null && activity != null) {
            HelpUtils.prepareHelpMenuItem(activity, menu, mHelpUri, getClass().getName());
        }
    }

    @Override
    public View onCreateView(
            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = super.onCreateView(inflater, container, savedInstanceState);
        return view;
    }

    @VisibleForTesting
    void setDownloadsDeletionType(DownloadsDeletionType downloadsDeletion) {
        mDownloadsDeletion = downloadsDeletion;
    }

    private void initializeButtons() {
        ButtonBarProvider activity = (ButtonBarProvider) getActivity();
        activity.getButtonBar().setVisibility(View.VISIBLE);

        mCancel = activity.getSkipButton();
        mCancel.setText(R.string.cancel);
        mCancel.setOnClickListener(this);
        mCancel.setVisibility(View.VISIBLE);

        mFree = activity.getNextButton();
        mFree.setText(R.string.storage_menu_free);
        mFree.setOnClickListener(this);
        mFree.setEnabled(false);
    }

    private void updateFreeButtonText() {
        Activity activity = getActivity();
        if (activity == null) {
            return;
        }
        mFree.setText(
                String.format(
                        activity.getString(R.string.deletion_helper_free_button),
                        Formatter.formatFileSize(
                                activity, getTotalFreeableSpace(COUNT_CHECKED_ONLY))));
    }

    private long getTotalFreeableSpace(boolean countUnchecked) {
        long freeableSpace = 0;
        if (mAppBackend != null) {
            freeableSpace += mAppBackend.getTotalAppsFreeableSpace(countUnchecked);
        }
        if (mPhotoPreference != null) {
            freeableSpace += mPhotoPreference.getFreeableBytes(countUnchecked);
        }
        if (mDownloadsPreference != null) {
            freeableSpace += mDownloadsDeletion.getFreeableBytes(countUnchecked);
        }
        return freeableSpace;
    }

    private void setResultCode(int result) {
        mResult = result;
        Activity activity = getActivity();
        if (activity != null) {
            activity.setResult(result);
        }
    }

    @VisibleForTesting
    protected int getResultCode() {
        return mResult;
    }
}
