/*
 * Copyright (C) 2018 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.server.wm;

import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;

import static org.hamcrest.Matchers.equalTo;

import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.view.DisplayInfo;

import androidx.test.filters.SmallTest;

import com.android.window.flags.Flags;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;
import org.junit.runner.RunWith;

@SmallTest
@Presubmit
@WindowTestsBase.SetupWindows(
        addWindows = { WindowTestsBase.W_STATUS_BAR, WindowTestsBase.W_NAVIGATION_BAR })
@RunWith(WindowTestRunner.class)
public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase {

    @Rule
    public final ErrorCollector mErrorCollector = new ErrorCollector();

    @Test
    public void portrait() {
        final DisplayInfo di = displayInfoForRotation(ROTATION_0, false /* withCutout */);

        verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
        verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT);
        verifyConsistency(di);
    }

    @Test
    public void portrait_withCutout() {
        final DisplayInfo di = displayInfoForRotation(ROTATION_0, true /* withCutout */);

        verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
        verifyNonDecorInsets(di, 0, DISPLAY_CUTOUT_HEIGHT, 0, NAV_BAR_HEIGHT);
        verifyConsistency(di);
    }

    @Test
    public void landscape() {
        final DisplayInfo di = displayInfoForRotation(ROTATION_90, false /* withCutout */);

        if (mDisplayPolicy.navigationBarCanMove()) {
            verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
            verifyNonDecorInsets(di, 0, 0, NAV_BAR_HEIGHT, 0);
        } else {
            // if the navigation bar cannot move then it is always on the bottom
            verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
            verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT);
        }
        verifyConsistency(di);
    }

    @Test
    public void landscape_withCutout() {
        final DisplayInfo di = displayInfoForRotation(ROTATION_90, true /* withCutout */);

        if (mDisplayPolicy.navigationBarCanMove()) {
            verifyStableInsets(di, DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
            verifyNonDecorInsets(di, DISPLAY_CUTOUT_HEIGHT, 0, NAV_BAR_HEIGHT, 0);
        } else {
            // if the navigation bar cannot move then it is always on the bottom
            verifyStableInsets(di, DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
            verifyNonDecorInsets(di, DISPLAY_CUTOUT_HEIGHT, 0, 0, NAV_BAR_HEIGHT);
        }
        verifyConsistency(di);
    }

    @Test
    public void seascape() {
        final DisplayInfo di = displayInfoForRotation(ROTATION_270, false /* withCutout */);

        if (mDisplayPolicy.navigationBarCanMove()) {
            verifyStableInsets(di, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, 0, 0);
            verifyNonDecorInsets(di, NAV_BAR_HEIGHT, 0, 0, 0);
        } else {
            // if the navigation bar cannot move then it is always on the bottom
            verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
            verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT);
        }
        verifyConsistency(di);
    }

    @Test
    public void seascape_withCutout() {
        final DisplayInfo di = displayInfoForRotation(ROTATION_270, true /* withCutout */);

        if (mDisplayPolicy.navigationBarCanMove()) {
            verifyStableInsets(di, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, DISPLAY_CUTOUT_HEIGHT, 0);
            verifyNonDecorInsets(di, NAV_BAR_HEIGHT, 0, DISPLAY_CUTOUT_HEIGHT, 0);
        } else {
            // if the navigation bar cannot move then it is always on the bottom
            verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, DISPLAY_CUTOUT_HEIGHT, NAV_BAR_HEIGHT);
            verifyNonDecorInsets(di, 0, 0, DISPLAY_CUTOUT_HEIGHT, NAV_BAR_HEIGHT);
        }
        verifyConsistency(di);
    }

    @Test
    public void upsideDown() {
        final DisplayInfo di = displayInfoForRotation(ROTATION_180, false /* withCutout */);

        verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
        verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT);
        verifyConsistency(di);
    }

    @Test
    public void upsideDown_withCutout() {
        final DisplayInfo di = displayInfoForRotation(ROTATION_180, true /* withCutout */);

        verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT + DISPLAY_CUTOUT_HEIGHT);
        verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT + DISPLAY_CUTOUT_HEIGHT);
        verifyConsistency(di);
    }

    private void verifyStableInsets(DisplayInfo di, int left, int top,
            int right, int bottom) {
        if (Flags.insetsDecoupledConfiguration()) {
            // TODO: update the verification to match the new behavior.
            return;
        }
        mErrorCollector.checkThat("stableInsets", getStableInsets(di),
                equalTo(new Rect(left, top, right, bottom)));
    }

    private void verifyNonDecorInsets(DisplayInfo di, int left, int top,
            int right, int bottom) {
        if (Flags.insetsDecoupledConfiguration()) {
            // TODO: update the verification to match the new behavior.
            return;
        }
        mErrorCollector.checkThat("nonDecorInsets",
                getNonDecorInsets(di), equalTo(new Rect(left, top, right, bottom)));
    }

    private void verifyConsistency(DisplayInfo  di) {
        final DisplayPolicy.DecorInsets.Info info = mDisplayPolicy.getDecorInsetsInfo(
                di.rotation, di.logicalWidth, di.logicalHeight);
        verifyConsistency("configDisplay", di, info.mConfigInsets,
                info.mConfigFrame.width(), info.mConfigFrame.height());
        verifyConsistency("nonDecorDisplay", di, info.mNonDecorInsets,
                info.mNonDecorFrame.width(), info.mNonDecorFrame.height());
    }

    private void verifyConsistency(String what, DisplayInfo di, Rect insets, int width,
            int height) {
        mErrorCollector.checkThat(what + ":width", width,
                equalTo(di.logicalWidth - insets.left - insets.right));
        mErrorCollector.checkThat(what + ":height", height,
                equalTo(di.logicalHeight - insets.top - insets.bottom));
    }

    private Rect getStableInsets(DisplayInfo di) {
        return mDisplayPolicy.getDecorInsetsInfo(
                di.rotation, di.logicalWidth, di.logicalHeight).mConfigInsets;
    }

    private Rect getNonDecorInsets(DisplayInfo di) {
        return mDisplayPolicy.getDecorInsetsInfo(
                di.rotation, di.logicalWidth, di.logicalHeight).mNonDecorInsets;
    }

    private DisplayInfo displayInfoForRotation(int rotation, boolean withDisplayCutout) {
        return displayInfoAndCutoutForRotation(
                rotation, withDisplayCutout, false /* isLongEdgeCutout */);
    }
}
