/*
 * 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.systemui.recents.views.grid;

import static com.android.systemui.recents.views.TaskStackLayoutAlgorithm.*;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.view.WindowManager;

import com.android.systemui.R;
import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
import com.android.systemui.recents.views.TaskViewTransform;

import java.util.ArrayList;

public class TaskGridLayoutAlgorithm  {

    private final String TAG = "TaskGridLayoutAlgorithm";
    public static final int MAX_LAYOUT_TASK_COUNT = 8;

    /** The horizontal padding around the whole recents view. */
    private int mPaddingLeftRight;
    /** The vertical padding around the whole recents view. */
    private int mPaddingTopBottom;
    /** The padding between task views. */
    private int mPaddingTaskView;

    private Rect mWindowRect;
    private Point mScreenSize = new Point();

    private Rect mTaskGridRect;

    /** The height, in pixels, of each task view's title bar. */
    private int mTitleBarHeight;

    /** The aspect ratio of each task thumbnail, without the title bar. */
    private float mAppAspectRatio;
    private Rect mSystemInsets = new Rect();

    /** The thickness of the focused task view frame. */
    private int mFocusedFrameThickness;

    /**
     * When the amount of tasks is determined, the size and position of every task view can be
     * decided. Each instance of TaskGridRectInfo store the task view information for a certain
     * amount of tasks.
     */
    class TaskGridRectInfo {
        Rect size;
        int[] xOffsets;
        int[] yOffsets;
        int tasksPerLine;
        int lines;

        TaskGridRectInfo(int taskCount) {
            size = new Rect();
            xOffsets = new int[taskCount];
            yOffsets = new int[taskCount];

            int layoutTaskCount = Math.min(MAX_LAYOUT_TASK_COUNT, taskCount);
            tasksPerLine = getTasksPerLine(layoutTaskCount);
            lines = layoutTaskCount < 4 ? 1 : 2;

            // A couple of special cases.
            boolean landscapeWindow = mWindowRect.width() > mWindowRect.height();
            boolean landscapeTaskView = mAppAspectRatio > 1;
            // If we're in portrait but task views are landscape, show more lines of fewer tasks.
            if (!landscapeWindow && landscapeTaskView) {
                tasksPerLine = layoutTaskCount < 2 ? 1 : 2;
                lines = layoutTaskCount < 3 ? 1 : (
                        layoutTaskCount < 5 ? 2 : (
                                layoutTaskCount < 7 ? 3 : 4));
            }
            // If we're in landscape but task views are portrait, show fewer lines of more tasks.
            if (landscapeWindow && !landscapeTaskView) {
                tasksPerLine = layoutTaskCount < 7 ? layoutTaskCount : 6;
                lines = layoutTaskCount < 7 ? 1 : 2;
            }

            int taskWidth, taskHeight;
            int maxTaskWidth = (mWindowRect.width() - 2 * mPaddingLeftRight
                - (tasksPerLine - 1) * mPaddingTaskView) / tasksPerLine;
            int maxTaskHeight = (mWindowRect.height() - 2 * mPaddingTopBottom
                - (lines - 1) * mPaddingTaskView) / lines;

            if (maxTaskHeight >= maxTaskWidth / mAppAspectRatio + mTitleBarHeight) {
                // Width bound.
                taskWidth = maxTaskWidth;
                // Here we should round the height to the nearest integer.
                taskHeight = (int) (maxTaskWidth / mAppAspectRatio + mTitleBarHeight + 0.5);
            } else {
                // Height bound.
                taskHeight = maxTaskHeight;
                // Here we should round the width to the nearest integer.
                taskWidth = (int) ((taskHeight - mTitleBarHeight) * mAppAspectRatio + 0.5);
            }
            size.set(0, 0, taskWidth, taskHeight);

            int emptySpaceX = mWindowRect.width() - 2 * mPaddingLeftRight
                - (tasksPerLine * taskWidth) - (tasksPerLine - 1) * mPaddingTaskView;
            int emptySpaceY = mWindowRect.height() - 2 * mPaddingTopBottom
                - (lines * taskHeight) - (lines - 1) * mPaddingTaskView;
            for (int taskIndex = 0; taskIndex < taskCount; taskIndex++) {
                // We also need to invert the index in order to display the most recent tasks first.
                int taskLayoutIndex = taskCount - taskIndex - 1;

                int xIndex = taskLayoutIndex % tasksPerLine;
                int yIndex = taskLayoutIndex / tasksPerLine;
                xOffsets[taskIndex] = mWindowRect.left +
                    emptySpaceX / 2 + mPaddingLeftRight + (taskWidth + mPaddingTaskView) * xIndex;
                yOffsets[taskIndex] = mWindowRect.top +
                    emptySpaceY / 2 + mPaddingTopBottom + (taskHeight + mPaddingTaskView) * yIndex;
            }
        }

        private int getTasksPerLine(int taskCount) {
            switch(taskCount) {
                case 0:
                    return 0;
                case 1:
                    return 1;
                case 2:
                case 4:
                    return 2;
                case 3:
                case 5:
                case 6:
                    return 3;
                case 7:
                case 8:
                    return 4;
                default:
                    throw new IllegalArgumentException("Unsupported task count " + taskCount);
            }
        }
    }

    /**
     * We can find task view sizes and positions from mTaskGridRectInfoList[k - 1] when there
     * are k tasks.
     */
    private TaskGridRectInfo[] mTaskGridRectInfoList;

    public TaskGridLayoutAlgorithm(Context context) {
        reloadOnConfigurationChange(context);
    }

    public void reloadOnConfigurationChange(Context context) {
        Resources res = context.getResources();
        mPaddingTaskView = res.getDimensionPixelSize(R.dimen.recents_grid_padding_task_view);
        mFocusedFrameThickness = res.getDimensionPixelSize(
            R.dimen.recents_grid_task_view_focused_frame_thickness);

        mTaskGridRect = new Rect();
        mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_grid_task_view_header_height);

        WindowManager windowManager = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        windowManager.getDefaultDisplay().getRealSize(mScreenSize);

        updateAppAspectRatio();
    }

    /**
     * Returns the proper task view transform of a certain task view, according to its index and the
     * amount of task views.
     * @param taskIndex     The index of the task view whose transform we want. It's never greater
     *                      than {@link MAX_LAYOUT_TASK_COUNT}.
     * @param taskCount     The current amount of task views.
     * @param transformOut  The result transform that this method returns.
     * @param stackLayout   The base stack layout algorithm.
     * @return  The expected transform of the (taskIndex)th task view.
     */
    public TaskViewTransform getTransform(int taskIndex, int taskCount,
        TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout) {
        if (taskCount == 0) {
            transformOut.reset();
            return transformOut;
        }

        TaskGridRectInfo gridInfo = mTaskGridRectInfoList[taskCount - 1];
        mTaskGridRect.set(gridInfo.size);

        int x = gridInfo.xOffsets[taskIndex];
        int y = gridInfo.yOffsets[taskIndex];
        float z = stackLayout.mMaxTranslationZ;

        // We always set the dim alpha to 0, since we don't want grid task views to dim.
        float dimAlpha = 0f;
        // We always set the alpha of the view outline to 1, to make sure the shadow is visible.
        float viewOutlineAlpha = 1f;

        // We also need to invert the index in order to display the most recent tasks first.
        int taskLayoutIndex = taskCount - taskIndex - 1;
        boolean isTaskViewVisible = taskLayoutIndex < MAX_LAYOUT_TASK_COUNT;

        // Fill out the transform
        transformOut.scale = 1f;
        transformOut.alpha = isTaskViewVisible ? 1f : 0f;
        transformOut.translationZ = z;
        transformOut.dimAlpha = dimAlpha;
        transformOut.viewOutlineAlpha = viewOutlineAlpha;
        transformOut.rect.set(mTaskGridRect);
        transformOut.rect.offset(x, y);
        Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
        // We only show the 8 most recent tasks.
        transformOut.visible = isTaskViewVisible;
        return transformOut;
    }

    /**
     * Return the proper task index to focus for arrow key navigation.
     * @param taskCount             The amount of tasks.
     * @param currentFocusedIndex   The index of the currently focused task.
     * @param direction             The direction we're navigating.
     * @return  The index of the task that should get the focus.
     */
    public int navigateFocus(int taskCount, int currentFocusedIndex, Direction direction) {
        if (taskCount < 1 || taskCount > MAX_LAYOUT_TASK_COUNT) {
            return -1;
        }
        if (currentFocusedIndex == -1) {
            return 0;
        }
        int newIndex = currentFocusedIndex;
        final TaskGridRectInfo gridInfo = mTaskGridRectInfoList[taskCount - 1];
        final int currentLine = (taskCount - 1 - currentFocusedIndex) / gridInfo.tasksPerLine;
        switch (direction) {
            case UP:
                newIndex += gridInfo.tasksPerLine;
                newIndex = newIndex >= taskCount ? currentFocusedIndex : newIndex;
                break;
            case DOWN:
                newIndex -= gridInfo.tasksPerLine;
                newIndex = newIndex < 0 ? currentFocusedIndex : newIndex;
                break;
            case LEFT:
                newIndex++;
                final int leftMostIndex = (taskCount - 1) - currentLine * gridInfo.tasksPerLine;
                newIndex = newIndex > leftMostIndex ? currentFocusedIndex : newIndex;
                break;
            case RIGHT:
                newIndex--;
                int rightMostIndex =
                    (taskCount - 1) - (currentLine + 1) * gridInfo.tasksPerLine + 1;
                rightMostIndex = rightMostIndex < 0 ? 0 : rightMostIndex;
                newIndex = newIndex < rightMostIndex ? currentFocusedIndex : newIndex;
                break;
        }
        return newIndex;
    }

    public void initialize(Rect windowRect) {
        mWindowRect = windowRect;
        // Define paddings in terms of percentage of the total area.
        mPaddingLeftRight = (int) (0.025f * Math.min(mWindowRect.width(), mWindowRect.height()));
        mPaddingTopBottom = (int) (0.1 * mWindowRect.height());

        // Pre-calculate the positions and offsets of task views so that we can reuse them directly
        // in the future.
        mTaskGridRectInfoList = new TaskGridRectInfo[MAX_LAYOUT_TASK_COUNT];
        for (int i = 0; i < MAX_LAYOUT_TASK_COUNT; i++) {
            mTaskGridRectInfoList[i] = new TaskGridRectInfo(i + 1);
        }
    }

    public void setSystemInsets(Rect systemInsets) {
        mSystemInsets = systemInsets;
        updateAppAspectRatio();
    }

    private void updateAppAspectRatio() {
        int usableWidth = mScreenSize.x - mSystemInsets.left - mSystemInsets.right;
        int usableHeight = mScreenSize.y - mSystemInsets.top - mSystemInsets.bottom;
        mAppAspectRatio = (float) usableWidth / (float) usableHeight;
    }

    public Rect getStackActionButtonRect() {
        Rect buttonRect = new Rect(mWindowRect);
        buttonRect.right -= mPaddingLeftRight;
        buttonRect.left += mPaddingLeftRight;
        buttonRect.bottom = buttonRect.top + mPaddingTopBottom;
        return buttonRect;
    }

    public void updateTaskGridRect(int taskCount) {
        if (taskCount > 0) {
            TaskGridRectInfo gridInfo = mTaskGridRectInfoList[taskCount - 1];
            mTaskGridRect.set(gridInfo.size);
        }
    }

    public Rect getTaskGridRect() {
        return mTaskGridRect;
    }

    public int getFocusFrameThickness() {
        return mFocusedFrameThickness;
    }

    public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
        int visibleCount = Math.min(TaskGridLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT, tasks.size());
        return new VisibilityReport(visibleCount, visibleCount);
    }
}
