/*
 * Copyright (C) 2021 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.unfold

import android.annotation.BinderThread
import android.content.ContentResolver
import android.content.Context
import android.hardware.devicestate.DeviceStateManager
import android.hardware.input.InputManagerGlobal
import android.os.Handler
import android.os.Trace
import com.android.systemui.Flags.unfoldAnimationBackgroundProgress
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.LinearLightRevealEffect
import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.ALPHA_OPAQUE
import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.ALPHA_TRANSPARENT
import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.isVerticalRotation
import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.FOLD
import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.UNFOLD
import com.android.systemui.unfold.dagger.UnfoldBg
import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled
import com.android.systemui.util.concurrency.ThreadFactory
import java.util.concurrent.Executor
import java.util.function.Consumer
import javax.inject.Inject
import javax.inject.Provider

@SysUIUnfoldScope
class UnfoldLightRevealOverlayAnimation
@Inject
constructor(
    private val context: Context,
    private val featureFlags: FeatureFlagsClassic,
    private val contentResolver: ContentResolver,
    @UnfoldBg private val unfoldProgressHandler: Handler,
    @UnfoldBg
    private val unfoldTransitionBgProgressProvider: Provider<UnfoldTransitionProgressProvider>,
    private val unfoldTransitionProgressProvider: Provider<UnfoldTransitionProgressProvider>,
    private val deviceStateManager: DeviceStateManager,
    private val threadFactory: ThreadFactory,
    private val fullscreenLightRevealAnimationControllerFactory:
        FullscreenLightRevealAnimationController.Factory
) : FullscreenLightRevealAnimation {

    private val transitionListener = TransitionListener()
    private var isFolded: Boolean = false
    private var isUnfoldHandled: Boolean = true
    private var overlayAddReason: AddOverlayReason = UNFOLD
    private lateinit var controller: FullscreenLightRevealAnimationController
    private lateinit var bgExecutor: Executor

    override fun init() {
        // This method will be called only on devices where this animation is enabled,
        // so normally this thread won't be created

        controller =
            fullscreenLightRevealAnimationControllerFactory.create(
                displaySelector = { maxByOrNull { it.naturalWidth } },
                effectFactory = { LinearLightRevealEffect(it.isVerticalRotation()) },
                overlayContainerName = SURFACE_CONTAINER_NAME,
            )
        controller.init()
        bgExecutor = threadFactory.buildDelayableExecutorOnHandler(unfoldProgressHandler)
        deviceStateManager.registerCallback(bgExecutor, FoldListener())
        if (unfoldAnimationBackgroundProgress()) {
            unfoldTransitionBgProgressProvider.get().addCallback(transitionListener)
        } else {
            unfoldTransitionProgressProvider.get().addCallback(transitionListener)
        }
    }

    /**
     * Called when screen starts turning on, the contents of the screen might not be visible yet.
     * This method reports back that the overlay is ready in [onOverlayReady] callback.
     *
     * @param onOverlayReady callback when the overlay is drawn and visible on the screen
     * @see [com.android.systemui.keyguard.KeyguardViewMediator]
     */
    @BinderThread
    override fun onScreenTurningOn(onOverlayReady: Runnable) {
        executeInBackground {
            Trace.beginSection("$TAG#onScreenTurningOn")
            try {
                // Add the view only if we are unfolding and this is the first screen on
                if (!isFolded && !isUnfoldHandled && contentResolver.areAnimationsEnabled()) {
                    overlayAddReason = UNFOLD
                    controller.addOverlay(calculateRevealAmount(), onOverlayReady)
                    isUnfoldHandled = true
                } else {
                    // No unfold transition, immediately report that overlay is ready
                    controller.ensureOverlayRemoved()
                    onOverlayReady.run()
                }
            } finally {
                Trace.endSection()
            }
        }
    }

    private fun calculateRevealAmount(animationProgress: Float? = null): Float {
        val overlayAddReason = overlayAddReason

        if (animationProgress == null) {
            // Animation progress unknown, calculate the initial value based on the overlay
            // add reason
            return when (overlayAddReason) {
                FOLD -> ALPHA_TRANSPARENT
                UNFOLD -> ALPHA_OPAQUE
            }
        }

        val showVignetteWhenFolding =
            featureFlags.isEnabled(Flags.ENABLE_DARK_VIGNETTE_WHEN_FOLDING)

        return if (!showVignetteWhenFolding && overlayAddReason == FOLD) {
            // Do not darken the content when SHOW_VIGNETTE_WHEN_FOLDING flag is off
            // and we are folding the device. We still add the overlay to block touches
            // while the animation is running but the overlay is transparent.
            ALPHA_TRANSPARENT
        } else {
            animationProgress
        }
    }

    private inner class TransitionListener :
        UnfoldTransitionProgressProvider.TransitionProgressListener {

        override fun onTransitionProgress(progress: Float) = executeInBackground {
            controller.updateRevealAmount(calculateRevealAmount(progress))
            // When unfolding unblock touches a bit earlier than the animation end as the
            // interpolation has a long tail of very slight movement at the end which should not
            // affect much the usage of the device
            controller.isTouchBlocked =
                overlayAddReason == FOLD || progress < UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS
        }

        override fun onTransitionFinished() = executeInBackground {
            controller.ensureOverlayRemoved()
        }

        override fun onTransitionStarted() {
            // Add view for folding case (when unfolding the view is added earlier)
            if (controller.isOverlayVisible()) {
                executeInBackground {
                    overlayAddReason = FOLD
                    controller.addOverlay(calculateRevealAmount())
                }
            }
            // Disable input dispatching during transition.
            InputManagerGlobal.getInstance().cancelCurrentTouch()
        }
    }

    private fun executeInBackground(f: () -> Unit) {
        // This is needed to allow progresses to be received both from the main thread (that will
        // schedule a runnable on the bg thread), and from the bg thread directly (no reposting).
        if (unfoldProgressHandler.looper.isCurrentThread) {
            f()
        } else {
            unfoldProgressHandler.post(f)
        }
    }

    private inner class FoldListener :
        DeviceStateManager.FoldStateListener(
            context,
            Consumer { isFolded ->
                if (isFolded) {
                    controller.ensureOverlayRemoved()
                    isUnfoldHandled = false
                }
                this.isFolded = isFolded
            }
        )

    private enum class AddOverlayReason {
        FOLD,
        UNFOLD
    }

    private companion object {
        const val TAG = "UnfoldLightRevealOverlayAnimation"
        const val SURFACE_CONTAINER_NAME = "unfold-overlay-container"
        const val UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS = 0.8f
    }
}