/*
 * Copyright (C) 2017 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.keyguard;

import android.annotation.IntDef;
import android.app.IWallpaperManager;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.Trace;
import android.util.DisplayMetrics;

import androidx.annotation.Nullable;

import com.android.systemui.Dumpable;
import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.util.time.SystemClock;

import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import javax.inject.Inject;

/**
 * Tracks the wakefulness lifecycle, including why we're waking or sleeping.
 *
 * @deprecated Collect flows from {@link PowerInteractor} instead.
 */
@SysUISingleton
@Deprecated
public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observer> implements
        Dumpable {

    @IntDef(prefix = { "WAKEFULNESS_" }, value = {
            WAKEFULNESS_ASLEEP,
            WAKEFULNESS_WAKING,
            WAKEFULNESS_AWAKE,
            WAKEFULNESS_GOING_TO_SLEEP,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Wakefulness {}

    public static final int WAKEFULNESS_ASLEEP = 0;
    public static final int WAKEFULNESS_WAKING = 1;
    public static final int WAKEFULNESS_AWAKE = 2;
    public static final int WAKEFULNESS_GOING_TO_SLEEP = 3;

    private final Context mContext;
    private final DisplayMetrics mDisplayMetrics;
    private final SystemClock mSystemClock;

    @Nullable
    private final IWallpaperManager mWallpaperManagerService;

    private int mWakefulness = WAKEFULNESS_AWAKE;

    private @PowerManager.WakeReason int mLastWakeReason = PowerManager.WAKE_REASON_UNKNOWN;

    public static final long UNKNOWN_LAST_WAKE_TIME = -1;
    private long mLastWakeTime = UNKNOWN_LAST_WAKE_TIME;

    @Nullable
    private Point mLastWakeOriginLocation = null;

    private @PowerManager.GoToSleepReason int mLastSleepReason =
            PowerManager.GO_TO_SLEEP_REASON_MIN;

    @Nullable
    private Point mLastSleepOriginLocation = null;

    @Inject
    public WakefulnessLifecycle(
            Context context,
            @Nullable IWallpaperManager wallpaperManagerService,
            SystemClock systemClock,
            DumpManager dumpManager) {
        mContext = context;
        mDisplayMetrics = context.getResources().getDisplayMetrics();
        mWallpaperManagerService = wallpaperManagerService;
        mSystemClock = systemClock;

        dumpManager.registerDumpable(getClass().getSimpleName(), this);
    }

    public @Wakefulness int getWakefulness() {
        return mWakefulness;
    }

    /**
     * Returns the most recent reason the device woke up. This is one of PowerManager.WAKE_REASON_*.
     */
    public @PowerManager.WakeReason int getLastWakeReason() {
        return mLastWakeReason;
    }

    /**
     * Returns the most recent time (in device uptimeMillis) the display woke up.
     * Returns {@link UNKNOWN_LAST_WAKE_TIME} if there hasn't been a wakeup yet.
     */
    public long getLastWakeTime() {
        return mLastWakeTime;
    }

    /**
     * Returns the most recent reason the device went to sleep up. This is one of
     * PowerManager.GO_TO_SLEEP_REASON_*.
     */
    public @PowerManager.GoToSleepReason int getLastSleepReason() {
        return mLastSleepReason;
    }

    public void dispatchStartedWakingUp(@PowerManager.WakeReason int pmWakeReason) {
        if (getWakefulness() == WAKEFULNESS_WAKING) {
            return;
        }
        setWakefulness(WAKEFULNESS_WAKING);
        mLastWakeReason = pmWakeReason;
        mLastWakeTime = mSystemClock.uptimeMillis();
        updateLastWakeOriginLocation();

        if (mWallpaperManagerService != null) {
            try {
                mWallpaperManagerService.notifyWakingUp(
                        mLastWakeOriginLocation.x, mLastWakeOriginLocation.y, new Bundle());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        dispatch(Observer::onStartedWakingUp);
    }

    public void dispatchFinishedWakingUp() {
        if (getWakefulness() == WAKEFULNESS_AWAKE) {
            return;
        }
        setWakefulness(WAKEFULNESS_AWAKE);
        dispatch(Observer::onFinishedWakingUp);
        dispatch(Observer::onPostFinishedWakingUp);
    }

    public void dispatchStartedGoingToSleep(@PowerManager.GoToSleepReason int pmSleepReason) {
        if (getWakefulness() == WAKEFULNESS_GOING_TO_SLEEP) {
            return;
        }
        setWakefulness(WAKEFULNESS_GOING_TO_SLEEP);
        mLastSleepReason = pmSleepReason;
        updateLastSleepOriginLocation();

        if (mWallpaperManagerService != null) {
            try {
                mWallpaperManagerService.notifyGoingToSleep(
                        mLastSleepOriginLocation.x, mLastSleepOriginLocation.y, new Bundle());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        dispatch(Observer::onStartedGoingToSleep);
    }

    public void dispatchFinishedGoingToSleep() {
        if (getWakefulness() == WAKEFULNESS_ASLEEP) {
            return;
        }
        setWakefulness(WAKEFULNESS_ASLEEP);
        dispatch(Observer::onFinishedGoingToSleep);
    }

    @Override
    public void dump(PrintWriter pw, String[] args) {
        pw.println("WakefulnessLifecycle:");
        pw.println("  mWakefulness=" + mWakefulness);
    }

    private void setWakefulness(@Wakefulness int wakefulness) {
        mWakefulness = wakefulness;
        Trace.traceCounter(Trace.TRACE_TAG_APP, "wakefulness", wakefulness);
    }

    private void updateLastWakeOriginLocation() {
        mLastWakeOriginLocation = null;

        switch (mLastWakeReason) {
            case PowerManager.WAKE_REASON_POWER_BUTTON:
                mLastWakeOriginLocation = getPowerButtonOrigin();
                break;
            default:
                mLastWakeOriginLocation = getDefaultWakeSleepOrigin();
                break;
        }
    }

    private void updateLastSleepOriginLocation() {
        mLastSleepOriginLocation = null;

        switch (mLastSleepReason) {
            case PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON:
                mLastSleepOriginLocation = getPowerButtonOrigin();
                break;
            default:
                mLastSleepOriginLocation = getDefaultWakeSleepOrigin();
                break;
        }
    }

    /**
     * Returns the point on the screen closest to the physical power button.
     */
    private Point getPowerButtonOrigin() {
        final boolean isPortrait = mContext.getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_PORTRAIT;

        if (isPortrait) {
            return new Point(
                    mDisplayMetrics.widthPixels,
                    mContext.getResources().getDimensionPixelSize(
                            R.dimen.physical_power_button_center_screen_location_y));
        } else {
            return new Point(
                    mContext.getResources().getDimensionPixelSize(
                            R.dimen.physical_power_button_center_screen_location_y),
                    mDisplayMetrics.heightPixels);
        }
    }

    /**
     * Returns the point on the screen used as the default origin for wake/sleep events. This is the
     * middle-bottom of the screen.
     */
    private Point getDefaultWakeSleepOrigin() {
        return new Point(mDisplayMetrics.widthPixels / 2, mDisplayMetrics.heightPixels);
    }

    public interface Observer {
        default void onStartedWakingUp() {}
        default void onFinishedWakingUp() {}

        /**
         * Called after the finished waking up call, ensuring it's after all the other listeners,
         * reacting to {@link #onFinishedWakingUp()}
         */
        default void onPostFinishedWakingUp() {}
        default void onStartedGoingToSleep() {}
        default void onFinishedGoingToSleep() {}
    }
}
