/*
 * Copyright (C) 2023 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.systemui.accessibility.accessibilitymenu.view;

import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
import android.widget.GridView;

import androidx.viewpager.widget.ViewPager;

import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService;
import com.android.systemui.accessibility.accessibilitymenu.R;
import com.android.systemui.accessibility.accessibilitymenu.activity.A11yMenuSettingsActivity.A11yMenuPreferenceFragment;
import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut;
import com.android.systemui.accessibility.accessibilitymenu.view.A11yMenuFooter.A11yMenuFooterCallBack;

import java.util.ArrayList;
import java.util.List;

/**
 * This class handles UI for viewPager and footer.
 * It displays grid pages containing all shortcuts in viewPager,
 * and handles the click events from footer to switch between pages.
 */
public class A11yMenuViewPager {

    /** The default index of the ViewPager. */
    public static final int DEFAULT_PAGE_INDEX = 0;

    /**
     * The class holds the static parameters for grid view when large button settings is on/off.
     */
    public static final class GridViewParams {
        /** Total shortcuts count in the grid view when large button settings is off. */
        public static final int GRID_ITEM_COUNT = 9;

        /** The number of columns in the grid view when large button settings is off. */
        public static final int GRID_COLUMN_COUNT = 3;

        /** Total shortcuts count in the grid view when large button settings is on. */
        public static final int LARGE_GRID_ITEM_COUNT = 4;

        /** The number of columns in the grid view when large button settings is on. */
        public static final int LARGE_GRID_COLUMN_COUNT = 2;

        /**
         * Returns the number of items in the grid view.
         *
         * @param context The parent context
         * @return Grid item count
         */
        public static int getGridItemCount(Context context) {
            return A11yMenuPreferenceFragment.isLargeButtonsEnabled(context)
                   ? LARGE_GRID_ITEM_COUNT
                   : GRID_ITEM_COUNT;
        }

        /**
         * Returns the number of columns in the grid view.
         *
         * @param context The parent context
         * @return Grid column count
         */
        public static int getGridColumnCount(Context context) {
            return A11yMenuPreferenceFragment.isLargeButtonsEnabled(context)
                   ? LARGE_GRID_COLUMN_COUNT
                   : GRID_COLUMN_COUNT;
        }

        /**
         * Returns the number of rows in the grid view.
         *
         * @param context The parent context
         * @return Grid row count
         */
        public static int getGridRowCount(Context context) {
            return A11yMenuPreferenceFragment.isLargeButtonsEnabled(context)
                   ? (LARGE_GRID_ITEM_COUNT / LARGE_GRID_COLUMN_COUNT)
                   : (GRID_ITEM_COUNT / GRID_COLUMN_COUNT);
        }

        /**
         * Separates a provided list of accessibility shortcuts into multiple sub-lists.
         * Does not modify the original list.
         *
         * @param pageItemCount The maximum size of an individual sub-list.
         * @param shortcutList The list of shortcuts to be separated into sub-lists.
         * @return A list of shortcut sub-lists.
         */
        public static List<List<A11yMenuShortcut>> generateShortcutSubLists(
                int pageItemCount, List<A11yMenuShortcut> shortcutList) {
            int start = 0;
            int end;
            int shortcutListSize = shortcutList.size();
            List<List<A11yMenuShortcut>> subLists = new ArrayList<>();
            while (start < shortcutListSize) {
                end = Math.min(start + pageItemCount, shortcutListSize);
                subLists.add(shortcutList.subList(start, end));
                start = end;
            }
            return subLists;
        }

        private GridViewParams() {}
    }

    private final AccessibilityMenuService mService;

    /**
     * The pager widget, which handles animation and allows swiping horizontally to access previous
     * and next gridView pages.
     */
    protected ViewPager mViewPager;

    private ViewPagerAdapter<GridView> mViewPagerAdapter;
    private final List<GridView> mGridPageList = new ArrayList<>();

    /** The footer, which provides buttons to switch between pages */
    protected A11yMenuFooter mA11yMenuFooter;

    /** The shortcut list intended to show in grid pages of viewPager */
    private List<A11yMenuShortcut> mA11yMenuShortcutList;

    /** The container layout for a11y menu. */
    private ViewGroup mA11yMenuLayout;

    /** Display context for inflating views. */
    private Context mDisplayContext;

    public A11yMenuViewPager(AccessibilityMenuService service, Context displayContext) {
        this.mService = service;
        this.mDisplayContext = displayContext;
    }

    /**
     * Configures UI for view pager and footer.
     *
     * @param a11yMenuLayout the container layout for a11y menu
     * @param shortcutDataList the data list need to show in view pager
     * @param pageIndex the index of ViewPager to show
     */
    public void configureViewPagerAndFooter(
            ViewGroup a11yMenuLayout, List<A11yMenuShortcut> shortcutDataList, int pageIndex) {
        this.mA11yMenuLayout = a11yMenuLayout;
        mA11yMenuShortcutList = shortcutDataList;
        initViewPager();
        initChildPage();
        mA11yMenuFooter = new A11yMenuFooter(a11yMenuLayout, mFooterCallbacks);
        updateFooterState();
        registerOnGlobalLayoutListener();
        goToPage(pageIndex);
    }

    /** Initializes viewPager and its adapter. */
    private void initViewPager() {
        mViewPager = mA11yMenuLayout.findViewById(R.id.view_pager);
        mViewPagerAdapter = new ViewPagerAdapter<>();
        mViewPager.setAdapter(mViewPagerAdapter);
        mViewPager.setOverScrollMode(View.OVER_SCROLL_NEVER);
        mViewPager.addOnPageChangeListener(
                new ViewPager.OnPageChangeListener() {
                    @Override
                    public void onPageScrollStateChanged(int state) {}

                    @Override
                    public void onPageScrolled(
                            int position, float positionOffset, int positionOffsetPixels) {}

                    @Override
                    public void onPageSelected(int position) {
                        updateFooterState();
                    }
                });
    }

    /** Creates child pages of viewPager by the length of shortcuts and initializes them. */
    private void initChildPage() {
        if (mA11yMenuShortcutList == null || mA11yMenuShortcutList.isEmpty()) {
            return;
        }

        if (!mGridPageList.isEmpty()) {
            mGridPageList.clear();
        }

        // Generate pages by calculating # of items per grid.
        for (List<A11yMenuShortcut> page : GridViewParams.generateShortcutSubLists(
                GridViewParams.getGridItemCount(mService), mA11yMenuShortcutList)
        ) {
            addGridPage(page);
        }

        mViewPagerAdapter.set(mGridPageList);
    }

    private void addGridPage(List<A11yMenuShortcut> shortcutDataListInPage) {
        LayoutInflater inflater = LayoutInflater.from(mDisplayContext);
        View view = inflater.inflate(R.layout.grid_view, null);
        GridView gridView = view.findViewById(R.id.gridview);
        A11yMenuAdapter adapter = new A11yMenuAdapter(
                mService, mDisplayContext, shortcutDataListInPage);
        gridView.setNumColumns(GridViewParams.getGridColumnCount(mService));
        gridView.setAdapter(adapter);
        mGridPageList.add(gridView);
    }

    /** Updates footer's state by index of current page in view pager. */
    private void updateFooterState() {
        int currentPage = mViewPager.getCurrentItem();
        int lastPage = mViewPager.getAdapter().getCount() - 1;
        mA11yMenuFooter.getPreviousPageBtn().setEnabled(currentPage > 0);
        mA11yMenuFooter.getNextPageBtn().setEnabled(currentPage < lastPage);
    }

    private void goToPage(int pageIndex) {
        if (mViewPager == null) {
            return;
        }
        if ((pageIndex >= 0) && (pageIndex < mViewPager.getAdapter().getCount())) {
            mViewPager.setCurrentItem(pageIndex);
        }
    }

    /** Registers OnGlobalLayoutListener to adjust menu UI by running callback at first time. */
    private void registerOnGlobalLayoutListener() {
        mA11yMenuLayout
                .getViewTreeObserver()
                .addOnGlobalLayoutListener(
                        new OnGlobalLayoutListener() {

                            boolean mIsFirstTime = true;

                            @Override
                            public void onGlobalLayout() {
                                if (!mIsFirstTime) {
                                    return;
                                }

                                if (mGridPageList.isEmpty()) {
                                    return;
                                }

                                GridView firstGridView = mGridPageList.get(0);
                                if (firstGridView == null
                                        || firstGridView.getChildAt(0) == null) {
                                    return;
                                }

                                mIsFirstTime = false;

                                int gridItemHeight = firstGridView.getChildAt(0)
                                                .getMeasuredHeight();
                                adjustMenuUISize(gridItemHeight);
                            }
                        });
    }

    /**
     * Adjusts menu UI to fit both landscape and portrait mode.
     *
     * <ol>
     *   <li>Adjust view pager's height.
     *   <li>Adjust vertical interval between grid items.
     *   <li>Adjust padding in view pager.
     * </ol>
     */
    private void adjustMenuUISize(int gridItemHeight) {
        final int rowsInGridView = GridViewParams.getGridRowCount(mService);
        final int defaultMargin =
                (int) mService.getResources().getDimension(R.dimen.a11ymenu_layout_margin);
        final int topMargin = (int) mService.getResources().getDimension(R.dimen.table_margin_top);
        final int displayMode = mService.getResources().getConfiguration().orientation;
        int viewPagerHeight = mViewPager.getMeasuredHeight();

        if (displayMode == Configuration.ORIENTATION_PORTRAIT) {
            // In portrait mode, we only need to adjust view pager's height to match its
            // child's height.
            viewPagerHeight = gridItemHeight * rowsInGridView + defaultMargin + topMargin;
        } else if (displayMode == Configuration.ORIENTATION_LANDSCAPE) {
            // In landscape mode, we need to adjust view pager's height to match screen height
            // and adjust its child too,
            // because a11y menu layout height is limited by the screen height.
            DisplayMetrics displayMetrics = mService.getResources().getDisplayMetrics();
            float densityScale = (float) displayMetrics.densityDpi
                    / DisplayMetrics.DENSITY_DEVICE_STABLE;
            View footerLayout = mA11yMenuLayout.findViewById(R.id.footerlayout);
            // Keeps footer window height unchanged no matter the density is changed.
            footerLayout.getLayoutParams().height =
                    (int) (footerLayout.getLayoutParams().height / densityScale);
            // Adjust the view pager height for system bar and display cutout insets.
            WindowManager windowManager = mService.getSystemService(WindowManager.class);
            WindowMetrics windowMetric = windowManager.getCurrentWindowMetrics();
            Insets windowInsets = windowMetric.getWindowInsets().getInsetsIgnoringVisibility(
                    WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
            viewPagerHeight =
                    windowMetric.getBounds().height()
                            - footerLayout.getLayoutParams().height
                            - windowInsets.bottom;
            // Sets vertical interval between grid items.
            int interval =
                    (viewPagerHeight - topMargin - defaultMargin
                            - (rowsInGridView * gridItemHeight))
                            / (rowsInGridView + 1);
            for (GridView gridView : mGridPageList) {
                gridView.setVerticalSpacing(interval);
            }

            // Sets padding to view pager.
            final int finalMarginTop = interval + topMargin;
            mViewPager.setPadding(defaultMargin, finalMarginTop, defaultMargin, defaultMargin);
        }
        final ViewGroup.LayoutParams layoutParams = mViewPager.getLayoutParams();
        layoutParams.height = viewPagerHeight;
        mViewPager.setLayoutParams(layoutParams);
    }

    /** Callback object to handle click events from A11yMenuFooter */
    protected A11yMenuFooterCallBack mFooterCallbacks =
            new A11yMenuFooterCallBack() {
                @Override
                public void onLeftButtonClicked() {
                    // Moves to previous page.
                    int targetPage = mViewPager.getCurrentItem() - 1;
                    goToPage(targetPage);
                    updateFooterState();
                }

                @Override
                public void onRightButtonClicked() {
                    // Moves to next page.
                    int targetPage = mViewPager.getCurrentItem() + 1;
                    goToPage(targetPage);
                    updateFooterState();
                }
            };
}
