/*
 * Copyright (C) 2014 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.statusbar.phone;

import android.annotation.NonNull;
import android.os.Handler;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dependency;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;

import javax.inject.Inject;

/**
 * Controller which handles all the doze animations of the scrims.
 */
@SysUISingleton
public class DozeScrimController implements StateListener {
    private static final String TAG = "DozeScrimController";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    private final DozeLog mDozeLog;
    private final DozeParameters mDozeParameters;
    private final Handler mHandler = new Handler();

    private boolean mDozing;
    private DozeHost.PulseCallback mPulseCallback;
    private int mPulseReason;
    private boolean mFullyPulsing;

    private final ScrimController.Callback mScrimCallback = new ScrimController.Callback() {
        @Override
        public void onDisplayBlanked() {
            if (DEBUG) {
                Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
                        + DozeLog.reasonToString(mPulseReason));
            }
            if (!mDozing) {
                return;
            }

            // Signal that the pulse is ready to turn the screen on and draw.
            pulseStarted();
        }

        @Override
        public void onFinished() {
            if (DEBUG) {
                Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
            }
            if (!mDozing) {
                return;
            }
            // Notifications should time out on their own.  Pulses due to notifications should
            // instead be managed externally based off the notification's lifetime.
            // Dock also controls the time out by self.
            if (mPulseReason != DozeLog.PULSE_REASON_NOTIFICATION
                    && mPulseReason != DozeLog.PULSE_REASON_DOCKING) {
                mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
                mHandler.postDelayed(mPulseOutExtended,
                        mDozeParameters.getPulseVisibleDurationExtended());
            }
            mFullyPulsing = true;
        }

        /**
         * Transition was aborted before it was over.
         */
        @Override
        public void onCancelled() {
            pulseFinished();
        }
    };

    @Inject
    public DozeScrimController(DozeParameters dozeParameters, DozeLog dozeLog) {
        mDozeParameters = dozeParameters;
        //Never expected to be destroyed
        Dependency.get(StatusBarStateController.class).addCallback(this);
        mDozeLog = dozeLog;
    }

    @VisibleForTesting
    public void setDozing(boolean dozing) {
        if (mDozing == dozing) return;
        mDozing = dozing;
        if (!mDozing) {
            cancelPulsing();
        }
    }

    /** When dozing, fade screen contents in and out using the front scrim. */
    public void pulse(@NonNull DozeHost.PulseCallback callback, int reason) {
        if (callback == null) {
            throw new IllegalArgumentException("callback must not be null");
        }

        if (!mDozing || mPulseCallback != null) {
            if (DEBUG) {
                Log.d(TAG, "Pulse supressed. Dozing: " + mDozeParameters + " had callback? "
                        + (mPulseCallback != null));
            }
            // Pulse suppressed.
            callback.onPulseFinished();
            return;
        }

        // Begin pulse. Note that it's very important that the pulse finished callback
        // be invoked when we're done so that the caller can drop the pulse wakelock.
        mPulseCallback = callback;
        mPulseReason = reason;
    }

    public void pulseOutNow() {
        if (mPulseCallback != null && mFullyPulsing) {
            mPulseOut.run();
        }
    }

    public boolean isPulsing() {
        return mPulseCallback != null;
    }

    public boolean isDozing() {
        return mDozing;
    }

    public void extendPulse() {
        mHandler.removeCallbacks(mPulseOut);
    }

    /**
     * When pulsing, cancel any timeouts that would take you out of the pulsing state.
     */
    public void cancelPendingPulseTimeout() {
        mHandler.removeCallbacks(mPulseOut);
        mHandler.removeCallbacks(mPulseOutExtended);
    }

    private void cancelPulsing() {
        if (mPulseCallback != null) {
            if (DEBUG) Log.d(TAG, "Cancel pulsing");
            mFullyPulsing = false;
            mHandler.removeCallbacks(mPulseOut);
            mHandler.removeCallbacks(mPulseOutExtended);
            pulseFinished();
        }
    }

    private void pulseStarted() {
        mDozeLog.tracePulseStart(mPulseReason);
        if (mPulseCallback != null) {
            mPulseCallback.onPulseStarted();
        }
    }

    private void pulseFinished() {
        mDozeLog.tracePulseFinish();
        if (mPulseCallback != null) {
            mPulseCallback.onPulseFinished();
            mPulseCallback = null;
        }
    }

    private final Runnable mPulseOutExtended = new Runnable() {
        @Override
        public void run() {
            mHandler.removeCallbacks(mPulseOut);
            mPulseOut.run();
        }
    };

    private final Runnable mPulseOut = new Runnable() {
        @Override
        public void run() {
            mFullyPulsing = false;
            mHandler.removeCallbacks(mPulseOut);
            mHandler.removeCallbacks(mPulseOutExtended);
            if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing);
            if (!mDozing) return;
            pulseFinished();
        }
    };

    public ScrimController.Callback getScrimCallback() {
        return mScrimCallback;
    }

    @Override
    public void onStateChanged(int newState) {
        // don't care
    }

    @Override
    public void onDozingChanged(boolean isDozing) {
        setDozing(isDozing);
    }
}