/*
 * Copyright (C) 2022 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.taskbar.allapps;

import static android.view.KeyEvent.ACTION_UP;
import static android.view.KeyEvent.KEYCODE_BACK;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;

import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;

import android.content.Context;
import android.graphics.Insets;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowInsets;

import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.allapps.search.DefaultSearchAdapterProvider;
import com.android.launcher3.allapps.search.SearchAdapterProvider;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.taskbar.BaseTaskbarContext;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarDragController;
import com.android.launcher3.taskbar.TaskbarStashController;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
import com.android.systemui.shared.system.ViewTreeObserverWrapper;
import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
import com.android.systemui.shared.system.ViewTreeObserverWrapper.OnComputeInsetsListener;

/**
 * Window context for the taskbar all apps overlay.
 * <p>
 * All apps has its own window and needs a window context. Some properties are delegated to the
 * {@link TaskbarActivityContext} such as {@link DeviceProfile} and {@link PopupDataProvider}.
 */
class TaskbarAllAppsContext extends BaseTaskbarContext {
    private final TaskbarActivityContext mTaskbarContext;
    private final OnboardingPrefs<TaskbarAllAppsContext> mOnboardingPrefs;

    private final TaskbarAllAppsController mWindowController;
    private final TaskbarAllAppsViewController mAllAppsViewController;
    private final TaskbarDragController mDragController;
    private final TaskbarAllAppsDragLayer mDragLayer;
    private final TaskbarAllAppsContainerView mAppsView;

    // We automatically stash taskbar when all apps is opened in gesture navigation mode.
    private final boolean mWillTaskbarBeVisuallyStashed;
    private final int mStashedTaskbarHeight;

    TaskbarAllAppsContext(
            TaskbarActivityContext taskbarContext,
            TaskbarAllAppsController windowController,
            TaskbarStashController taskbarStashController) {
        super(taskbarContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null));
        mTaskbarContext = taskbarContext;
        mWindowController = windowController;
        mDragController = new TaskbarDragController(this);
        mOnboardingPrefs = new OnboardingPrefs<>(this, Utilities.getPrefs(this));

        mDragLayer = new TaskbarAllAppsDragLayer(this);
        TaskbarAllAppsSlideInView slideInView = (TaskbarAllAppsSlideInView) mLayoutInflater.inflate(
                R.layout.taskbar_all_apps, mDragLayer, false);
        mAllAppsViewController = new TaskbarAllAppsViewController(
                this,
                slideInView,
                windowController,
                taskbarStashController);
        mAppsView = slideInView.getAppsView();

        mWillTaskbarBeVisuallyStashed = taskbarStashController.supportsVisualStashing();
        mStashedTaskbarHeight = taskbarStashController.getStashedHeight();
    }

    TaskbarAllAppsViewController getAllAppsViewController() {
        return mAllAppsViewController;
    }

    @Override
    public DeviceProfile getDeviceProfile() {
        return mWindowController.getDeviceProfile();
    }

    @Override
    public TaskbarDragController getDragController() {
        return mDragController;
    }

    @Override
    public TaskbarAllAppsDragLayer getDragLayer() {
        return mDragLayer;
    }

    @Override
    public TaskbarAllAppsContainerView getAppsView() {
        return mAppsView;
    }

    @Override
    public OnboardingPrefs<TaskbarAllAppsContext> getOnboardingPrefs() {
        return mOnboardingPrefs;
    }

    @Override
    public boolean isBindingItems() {
        return mTaskbarContext.isBindingItems();
    }

    @Override
    public View.OnClickListener getItemOnClickListener() {
        return mTaskbarContext.getItemOnClickListener();
    }

    @Override
    public PopupDataProvider getPopupDataProvider() {
        return mTaskbarContext.getPopupDataProvider();
    }

    @Override
    public DotInfo getDotInfoForItem(ItemInfo info) {
        return mTaskbarContext.getDotInfoForItem(info);
    }

    @Override
    public void onDragStart() {}

    @Override
    public void onDragEnd() {
        mWindowController.maybeCloseWindow();
    }

    @Override
    public void onPopupVisibilityChanged(boolean isVisible) {}

    @Override
    public SearchAdapterProvider<?> createSearchAdapterProvider(
            ActivityAllAppsContainerView<?> appsView) {
        return new DefaultSearchAdapterProvider(this);
    }

    /** Root drag layer for this context. */
    private static class TaskbarAllAppsDragLayer extends
            BaseDragLayer<TaskbarAllAppsContext> implements OnComputeInsetsListener {

        private TaskbarAllAppsDragLayer(Context context) {
            super(context, null, 1);
            setClipChildren(false);
            recreateControllers();
        }

        @Override
        protected void onAttachedToWindow() {
            super.onAttachedToWindow();
            ViewTreeObserverWrapper.addOnComputeInsetsListener(
                    getViewTreeObserver(), this);
        }

        @Override
        protected void onDetachedFromWindow() {
            super.onDetachedFromWindow();
            ViewTreeObserverWrapper.removeOnComputeInsetsListener(this);
        }

        @Override
        public void recreateControllers() {
            mControllers = new TouchController[]{mActivity.mDragController};
        }

        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
            return super.dispatchTouchEvent(ev);
        }

        @Override
        public boolean dispatchKeyEvent(KeyEvent event) {
            if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK) {
                AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
                if (topView != null && topView.onBackPressed()) {
                    return true;
                }
            }
            return super.dispatchKeyEvent(event);
        }

        @Override
        public void onComputeInsets(InsetsInfo inoutInfo) {
            if (mActivity.mDragController.isSystemDragInProgress()) {
                inoutInfo.touchableRegion.setEmpty();
                inoutInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
            }
        }

        @Override
        public WindowInsets onApplyWindowInsets(WindowInsets insets) {
            return updateInsetsDueToStashing(insets);
        }

        /**
         * Taskbar automatically stashes when opening all apps, but we don't report the insets as
         * changing to avoid moving the underlying app. But internally, the apps view should still
         * layout according to the stashed insets rather than the unstashed insets. So this method
         * does two things:
         * 1) Sets navigationBars bottom inset to stashedHeight.
         * 2) Sets tappableInsets bottom inset to 0.
         */
        private WindowInsets updateInsetsDueToStashing(WindowInsets oldInsets) {
            if (!mActivity.mWillTaskbarBeVisuallyStashed) {
                return oldInsets;
            }
            WindowInsets.Builder updatedInsetsBuilder = new WindowInsets.Builder(oldInsets);

            Insets oldNavInsets = oldInsets.getInsets(WindowInsets.Type.navigationBars());
            Insets newNavInsets = Insets.of(oldNavInsets.left, oldNavInsets.top, oldNavInsets.right,
                    mActivity.mStashedTaskbarHeight);
            updatedInsetsBuilder.setInsets(WindowInsets.Type.navigationBars(), newNavInsets);

            Insets oldTappableInsets = oldInsets.getInsets(WindowInsets.Type.tappableElement());
            Insets newTappableInsets = Insets.of(oldTappableInsets.left, oldTappableInsets.top,
                    oldTappableInsets.right, 0);
            updatedInsetsBuilder.setInsets(WindowInsets.Type.tappableElement(), newTappableInsets);

            return updatedInsetsBuilder.build();
        }
    }
}
