/*
 * 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.settings.dashboard.profileselector;

import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER;
import static android.app.admin.DevicePolicyResources.Strings.Settings.PRIVATE_CATEGORY_HEADER;
import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER;
import static android.content.Intent.EXTRA_USER_ID;

import android.annotation.IntDef;
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.Flags;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.Lifecycle;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;

import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.privatespace.PrivateSpaceMaintainer;

import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;

/**
 * Base fragment class for profile settings.
 */
public abstract class ProfileSelectFragment extends DashboardFragment {

    private static final String TAG = "ProfileSelectFragment";

    /**
     * Denotes the profile type.
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
            ProfileType.PERSONAL,
            ProfileType.WORK,
            ProfileType.ALL
    })
    public @interface ProfileType {
        /**
         * It is personal work profile.
         */
        int PERSONAL = 1;

        /**
         * It is work profile
         */
        int WORK = 1 << 1;

        /**
         * It is private profile
         */
        int PRIVATE = 1 << 2;

        /**
         * It is personal, work, and private profile
         */
        int ALL = PERSONAL | WORK | PRIVATE;
    }

    /**
     * Used in fragment argument and pass {@link ProfileType} to it
     */
    public static final String EXTRA_PROFILE = "profile";

    /**
     * Used in fragment argument with Extra key {@link SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB}
     */
    public static final int PERSONAL_TAB = 0;

    /**
     * Used in fragment argument with Extra key {@link SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB}
     */
    public static final int WORK_TAB = 1;

    /**
     * Used in fragment argument with Extra key {@link SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB}
     */
    public static final int PRIVATE_TAB = 2;

    private ViewGroup mContentView;

    private ViewPager2 mViewPager;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        mContentView = (ViewGroup) super.onCreateView(inflater, container, savedInstanceState);
        final Activity activity = getActivity();
        final int titleResId = getTitleResId();
        if (titleResId > 0) {
            activity.setTitle(titleResId);
        }

        final View tabContainer = mContentView.findViewById(R.id.tab_container);
        mViewPager = tabContainer.findViewById(R.id.view_pager);
        mViewPager.setAdapter(new ProfileSelectFragment.ViewPagerAdapter(this));
        final TabLayout tabs = tabContainer.findViewById(R.id.tabs);
        new TabLayoutMediator(tabs, mViewPager,
                (tab, position) -> tab.setText(getPageTitle(position))
        ).attach();
        mViewPager.registerOnPageChangeCallback(
                new ViewPager2.OnPageChangeCallback() {
                    @Override
                    public void onPageSelected(int position) {
                        super.onPageSelected(position);
                        updateHeight(position);
                    }
                }
        );
        tabContainer.setVisibility(View.VISIBLE);
        final int selectedTab = getTabId(activity, getArguments());
        final TabLayout.Tab tab = tabs.getTabAt(selectedTab);
        tab.select();

        final FrameLayout listContainer = mContentView.findViewById(android.R.id.list_container);
        listContainer.setLayoutParams(new LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT));

        final RecyclerView recyclerView = getListView();
        recyclerView.setOverScrollMode(View.OVER_SCROLL_NEVER);
        Utils.setActionBarShadowAnimation(activity, getSettingsLifecycle(), recyclerView);

        return mContentView;
    }

    protected boolean forceUpdateHeight() {
        return false;
    }

    private void updateHeight(int position) {
        if (!forceUpdateHeight()) {
            return;
        }
        ViewPagerAdapter adapter = (ViewPagerAdapter) mViewPager.getAdapter();
        if (adapter == null || adapter.getItemCount() <= position) {
            return;
        }

        Fragment fragment = adapter.createFragment(position);
        View newPage = fragment.getView();
        if (newPage != null) {
            int viewWidth = View.MeasureSpec.makeMeasureSpec(newPage.getWidth(),
                    View.MeasureSpec.EXACTLY);
            int viewHeight = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
            newPage.measure(viewWidth, viewHeight);
            int currentHeight = mViewPager.getLayoutParams().height;
            int newHeight = newPage.getMeasuredHeight();
            if (newHeight != 0 && currentHeight != newHeight) {
                ViewGroup.LayoutParams layoutParams = mViewPager.getLayoutParams();
                layoutParams.height = newHeight;
                mViewPager.setLayoutParams(layoutParams);
            }
        }
    }

    @Override
    public int getMetricsCategory() {
        return METRICS_CATEGORY_UNKNOWN;
    }

    /**
     * Returns an array of {@link Fragment} to display in the
     * {@link com.google.android.material.tabs.TabLayout}
     */
    public abstract Fragment[] getFragments();

    /**
     * Returns a resource ID of the title
     * Override this if the title needs to be updated dynamically.
     */
    public int getTitleResId() {
        return 0;
    }

    @Override
    protected int getPreferenceScreenResId() {
        return R.xml.placeholder_preference_screen;
    }

    @Override
    protected String getLogTag() {
        return TAG;
    }

    @VisibleForTesting
    int getTabId(Activity activity, Bundle bundle) {
        if (bundle != null) {
            final int extraTab = bundle.getInt(SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB, -1);
            if (extraTab != -1) {
                return ((ViewPagerAdapter) mViewPager.getAdapter())
                        .getPositionForProfileTab(extraTab);
            }
            final UserManager userManager = getSystemService(UserManager.class);
            UserHandle mainUser = userManager.getMainUser();
            if (mainUser == null) {
                mainUser = UserHandle.SYSTEM;
            }
            final int userId = bundle.getInt(EXTRA_USER_ID, mainUser.getIdentifier());
            final boolean isWorkProfile = UserManager.get(activity).isManagedProfile(userId);
            if (isWorkProfile) {
                return WORK_TAB;
            }
            UserInfo userInfo = UserManager.get(activity).getUserInfo(userId);
            if (Flags.allowPrivateProfile()
                    && android.multiuser.Flags.enablePrivateSpaceFeatures()
                    && userInfo != null && userInfo.isPrivateProfile()) {
                return PRIVATE_TAB;
            }
        }
        // Start intent from a specific user eg: adb shell --user 10
        final int intentUser = activity.getIntent().getContentUserHint();
        if (UserManager.get(activity).isManagedProfile(intentUser)) {
            return WORK_TAB;
        }
        UserInfo userInfo = UserManager.get(activity).getUserInfo(intentUser);
        if (Flags.allowPrivateProfile()
                && android.multiuser.Flags.enablePrivateSpaceFeatures()
                && userInfo != null && userInfo.isPrivateProfile()) {
            return PRIVATE_TAB;
        }

        return PERSONAL_TAB;
    }

    private CharSequence getPageTitle(int position) {
        final DevicePolicyManager devicePolicyManager =
                getContext().getSystemService(DevicePolicyManager.class);

        if (Flags.allowPrivateProfile() && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
            int tabForPosition =
                    ((ViewPagerAdapter) mViewPager.getAdapter()).getTabForPosition(position);

            if (tabForPosition == WORK_TAB) {
                return devicePolicyManager.getResources().getString(WORK_CATEGORY_HEADER,
                        () -> getContext().getString(
                                com.android.settingslib.R.string.category_work));
            }

            if (tabForPosition == PRIVATE_TAB) {
                return devicePolicyManager.getResources().getString(PRIVATE_CATEGORY_HEADER,
                        () -> getContext()
                                .getString(com.android.settingslib.R.string.category_private));
            }

        } else if (position == WORK_TAB) {
            return devicePolicyManager.getResources().getString(WORK_CATEGORY_HEADER,
                    () -> getContext().getString(com.android.settingslib.R.string.category_work));

        }
        return devicePolicyManager.getResources().getString(PERSONAL_CATEGORY_HEADER,
                () -> getContext().getString(
                        com.android.settingslib.R.string.category_personal));
    }

    /** Creates fragments of passed types, and returns them in an array. */
    @NonNull static Fragment[] getFragments(
            Context context,
            @Nullable Bundle bundle,
            FragmentConstructor personalFragmentConstructor,
            FragmentConstructor workFragmentConstructor,
            FragmentConstructor privateFragmentConstructor) {
        return getFragments(
                context,
                bundle,
                personalFragmentConstructor,
                workFragmentConstructor,
                privateFragmentConstructor,
                new PrivateSpaceInfoProvider() {});
    }

    /**
     * Creates fragments of passed types, and returns them in an array. This overload exists only
     * for helping with testing.
     */
    @NonNull static Fragment[] getFragments(
            Context context,
            @Nullable Bundle bundle,
            FragmentConstructor personalFragmentConstructor,
            FragmentConstructor workFragmentConstructor,
            FragmentConstructor privateFragmentConstructor,
            PrivateSpaceInfoProvider privateSpaceInfoProvider) {
        Fragment[] result = new Fragment[0];
        ArrayList<Fragment> fragments = new ArrayList<>();

        try {
            UserManager userManager = context.getSystemService(UserManager.class);
            List<UserInfo> userInfos = userManager.getProfiles(UserHandle.myUserId());

            for (UserInfo userInfo : userInfos) {
                if (userInfo.isMain()) {
                    fragments.add(
                            createAndGetFragment(
                                    ProfileType.PERSONAL,
                                    userInfo.id,
                                    bundle != null ? bundle : new Bundle(),
                                    personalFragmentConstructor));
                } else if (userInfo.isManagedProfile()) {
                    fragments.add(
                            createAndGetFragment(
                                    ProfileType.WORK,
                                    userInfo.id,
                                    bundle != null ? bundle.deepCopy() : new Bundle(),
                                    workFragmentConstructor));
                } else if (Flags.allowPrivateProfile()
                        && android.multiuser.Flags.enablePrivateSpaceFeatures()
                        && userInfo.isPrivateProfile()) {
                    if (!privateSpaceInfoProvider.isPrivateSpaceLocked(context)) {
                        fragments.add(
                                createAndGetFragment(
                                        ProfileType.PRIVATE,
                                        userInfo.id,
                                        bundle != null ? bundle.deepCopy() : new Bundle(),
                                        privateFragmentConstructor));
                    }
                } else {
                    Log.d(TAG, "Not showing tab for unsupported user " + userInfo);
                }
            }

            result = new Fragment[fragments.size()];
            fragments.toArray(result);
        } catch (Exception e) {
            Log.e(TAG, "Failed to create fragment");
        }

        return result;
    }

    private static Fragment createAndGetFragment(
            @ProfileType int profileType,
            int userId,
            Bundle bundle,
            FragmentConstructor fragmentConstructor) {
        bundle.putInt(EXTRA_PROFILE, profileType);
        bundle.putInt(EXTRA_USER_ID, userId);
        final Fragment fragment = fragmentConstructor.constructAndGetFragment();
        fragment.setArguments(bundle);
        return fragment;
    }

    @VisibleForTesting
    void setViewPager(ViewPager2 viewPager) {
        mViewPager = viewPager;
    }

    interface FragmentConstructor {
        Fragment constructAndGetFragment();
    }

    interface PrivateSpaceInfoProvider {
        default boolean isPrivateSpaceLocked(Context context) {
            return PrivateSpaceMaintainer.getInstance(context).isPrivateSpaceLocked();
        }
    }

    static class ViewPagerAdapter extends FragmentStateAdapter {

        private final Fragment[] mChildFragments;

        ViewPagerAdapter(ProfileSelectFragment fragment) {
            super(fragment);
            mChildFragments = fragment.getFragments();
        }

        @VisibleForTesting
        ViewPagerAdapter(
                @NonNull FragmentManager fragmentManager,
                @NonNull Lifecycle lifecycle,
                ProfileSelectFragment profileSelectFragment) {
            super(fragmentManager, lifecycle);
            mChildFragments = profileSelectFragment.getFragments();
        }

        @Override
        public Fragment createFragment(int position) {
            return mChildFragments[position];
        }

        @Override
        public int getItemCount() {
            return mChildFragments.length;
        }

        @VisibleForTesting
        int getTabForPosition(int position) {
            if (position >= mChildFragments.length) {
                Log.e(TAG, "tab requested for out of bound position " + position);
                return PERSONAL_TAB;
            }
            @ProfileType
            int profileType = mChildFragments[position].getArguments().getInt(EXTRA_PROFILE);
            return profileTypeToTab(profileType);
        }

        private int getPositionForProfileTab(int profileTab) {
            for (int i = 0; i < mChildFragments.length; ++i) {
                Bundle arguments = mChildFragments[i].getArguments();
                if (arguments != null
                        && profileTypeToTab(arguments.getInt(EXTRA_PROFILE)) == profileTab) {
                    return i;
                }
            }
            Log.e(TAG, "position requested for an unknown profile tab " + profileTab);
            return 0;
        }

        private int profileTypeToTab(@ProfileType int profileType) {
            if (profileType == ProfileType.WORK) {
                return WORK_TAB;
            }
            if (profileType == ProfileType.PRIVATE) {
                return PRIVATE_TAB;
            }
            return PERSONAL_TAB;
        }
    }
}
