/*
 * 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.launcher3.folder;

import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;

import android.graphics.Point;

import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;

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

/**
 * Utility class for managing item positions in a folder based on rank
 */
public class FolderGridOrganizer {

    private final Point mPoint = new Point();
    private final int mMaxCountX;
    private final int mMaxCountY;
    private final int mMaxItemsPerPage;

    private int mNumItemsInFolder;
    private int mCountX;
    private int mCountY;
    private boolean mDisplayingUpperLeftQuadrant = false;

    /**
     * Note: must call {@link #setFolderInfo(FolderInfo)} manually for verifier to work.
     */
    public FolderGridOrganizer(InvariantDeviceProfile profile) {
        mMaxCountX = profile.numFolderColumns;
        mMaxCountY = profile.numFolderRows;
        mMaxItemsPerPage = mMaxCountX * mMaxCountY;
    }

    /**
     * Updates the organizer with the provided folder info
     */
    public FolderGridOrganizer setFolderInfo(FolderInfo info) {
        return setContentSize(info.contents.size());
    }

    /**
     * Updates the organizer to reflect the content size
     */
    public FolderGridOrganizer setContentSize(int contentSize) {
        if (contentSize != mNumItemsInFolder) {
            calculateGridSize(contentSize);

            mDisplayingUpperLeftQuadrant = contentSize > MAX_NUM_ITEMS_IN_PREVIEW;
            mNumItemsInFolder = contentSize;
        }
        return this;
    }

    public int getCountX() {
        return mCountX;
    }

    public int getCountY() {
        return mCountY;
    }

    public int getMaxItemsPerPage() {
        return mMaxItemsPerPage;
    }

    /**
     * Calculates the grid size such that {@param count} items can fit in the grid.
     * The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while
     * maintaining the restrictions of {@link #mMaxCountX} &amp; {@link #mMaxCountY}.
     */
    private void calculateGridSize(int count) {
        boolean done;
        int gridCountX = mCountX;
        int gridCountY = mCountY;

        if (count >= mMaxItemsPerPage) {
            gridCountX = mMaxCountX;
            gridCountY = mMaxCountY;
            done = true;
        } else {
            done = false;
        }

        while (!done) {
            int oldCountX = gridCountX;
            int oldCountY = gridCountY;
            if (gridCountX * gridCountY < count) {
                // Current grid is too small, expand it
                if ((gridCountX <= gridCountY || gridCountY == mMaxCountY)
                        && gridCountX < mMaxCountX) {
                    gridCountX++;
                } else if (gridCountY < mMaxCountY) {
                    gridCountY++;
                }
                if (gridCountY == 0) gridCountY++;
            } else if ((gridCountY - 1) * gridCountX >= count && gridCountY >= gridCountX) {
                gridCountY = Math.max(0, gridCountY - 1);
            } else if ((gridCountX - 1) * gridCountY >= count) {
                gridCountX = Math.max(0, gridCountX - 1);
            }
            done = gridCountX == oldCountX && gridCountY == oldCountY;
        }

        mCountX = gridCountX;
        mCountY = gridCountY;
    }

    /**
     * Updates the item's cellX, cellY and rank corresponding to the provided rank.
     * @return true if there was any change
     */
    public boolean updateRankAndPos(ItemInfo item, int rank) {
        Point pos = getPosForRank(rank);
        if (!pos.equals(item.cellX, item.cellY) || rank != item.rank) {
            item.rank = rank;
            item.cellX = pos.x;
            item.cellY = pos.y;
            return true;
        }
        return false;
    }

    /**
     * Returns the position of the item in the grid
     */
    public Point getPosForRank(int rank) {
        int pagePos = rank % mMaxItemsPerPage;
        mPoint.x = pagePos % mCountX;
        mPoint.y = pagePos / mCountX;
        return mPoint;
    }

    /**
     * Returns the preview items for the provided pageNo using the full list of contents
     */
    public <T, R extends T> ArrayList<R> previewItemsForPage(int page, List<T> contents) {
        ArrayList<R> result = new ArrayList<>();
        int itemsPerPage = mCountX * mCountY;
        int start = itemsPerPage * page;
        int end = Math.min(start + itemsPerPage, contents.size());

        for (int i = start, rank = 0; i < end; i++, rank++) {
            if (isItemInPreview(page, rank)) {
                result.add((R) contents.get(i));
            }

            if (result.size() == MAX_NUM_ITEMS_IN_PREVIEW) {
                break;
            }
        }
        return result;
    }

    /**
     * Returns whether the item with rank is in the default Folder icon preview.
     */
    public boolean isItemInPreview(int rank) {
        return isItemInPreview(0, rank);
    }

    /**
     * @param page The page the item is on.
     * @param rank The rank of the item.
     * @return True iff the icon is in the 2x2 upper left quadrant of the Folder.
     */
    public boolean isItemInPreview(int page, int rank) {
        // First page items are laid out such that the first 4 items are always in the upper
        // left quadrant. For all other pages, we need to check the row and col.
        if (page > 0 || mDisplayingUpperLeftQuadrant) {
            int col = rank % mCountX;
            int row = rank / mCountX;
            return col < 2 && row < 2;
        }
        return rank < MAX_NUM_ITEMS_IN_PREVIEW;
    }
}
