/*
 * Copyright 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.wifi;

import static com.android.server.wifi.WakeupNotificationFactory.ACTION_DISMISS_NOTIFICATION;
import static com.android.server.wifi.WakeupNotificationFactory.ACTION_OPEN_WIFI_PREFERENCES;
import static com.android.server.wifi.WakeupNotificationFactory.ACTION_TURN_OFF_WIFI_WAKE;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.wifi.WifiContext;
import android.os.Handler;
import android.os.SystemClock;
import android.provider.Settings;
import android.text.format.DateUtils;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;

/**
 * Manages the WiFi Wake onboarding notification.
 *
 * <p>If a user disables wifi with Wifi Wake enabled, this notification is shown to explain that
 * wifi may turn back on automatically. It will be displayed up to 3 times, or until the
 * user either interacts with the onboarding notification in some way (e.g. dismiss, tap) or
 * manually enables/disables the feature in WifiSettings.
 */
public class WakeupOnboarding {

    private static final String TAG = "WakeupOnboarding";

    @VisibleForTesting
    static final int NOTIFICATIONS_UNTIL_ONBOARDED = 3;
    @VisibleForTesting
    static final long REQUIRED_NOTIFICATION_DELAY = DateUtils.DAY_IN_MILLIS;
    private static final long NOT_SHOWN_TIMESTAMP = -1;

    private final WifiContext mContext;
    private final WakeupNotificationFactory mWakeupNotificationFactory;
    private final WifiNotificationManager mNotificationManager;
    private final Handler mHandler;
    private final WifiConfigManager mWifiConfigManager;
    private final IntentFilter mIntentFilter;
    private final FrameworkFacade mFrameworkFacade;

    private boolean mIsOnboarded;
    private int mTotalNotificationsShown;
    private long mLastShownTimestamp = NOT_SHOWN_TIMESTAMP;
    private boolean mIsNotificationShowing;

    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            switch (intent.getAction()) {
                case ACTION_TURN_OFF_WIFI_WAKE:
                    mFrameworkFacade.setIntegerSetting(mContext,
                            Settings.Global.WIFI_WAKEUP_ENABLED, 0);
                    dismissNotification(true /* shouldOnboard */);
                    break;
                case ACTION_OPEN_WIFI_PREFERENCES:
                    // Close notification drawer before opening preferences.
                    mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
                    mContext.startActivity(new Intent(Settings.ACTION_WIFI_IP_SETTINGS)
                            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
                    dismissNotification(true /* shouldOnboard */);
                    break;
                case ACTION_DISMISS_NOTIFICATION:
                    dismissNotification(true /* shouldOnboard */);
                    break;
                default:
                    Log.e(TAG, "Unknown action " + intent.getAction());
            }
        }
    };

    public WakeupOnboarding(
            WifiContext context,
            WifiConfigManager wifiConfigManager,
            Handler handler,
            FrameworkFacade frameworkFacade,
            WakeupNotificationFactory wakeupNotificationFactory,
            WifiNotificationManager wifiNotificationManager) {
        mContext = context;
        mWifiConfigManager = wifiConfigManager;
        mHandler = handler;
        mFrameworkFacade = frameworkFacade;
        mWakeupNotificationFactory = wakeupNotificationFactory;
        mNotificationManager = wifiNotificationManager;

        mIntentFilter = new IntentFilter();
        mIntentFilter.addAction(ACTION_TURN_OFF_WIFI_WAKE);
        mIntentFilter.addAction(ACTION_DISMISS_NOTIFICATION);
        mIntentFilter.addAction(ACTION_OPEN_WIFI_PREFERENCES);
    }

    /** Returns whether the user is onboarded. */
    public boolean isOnboarded() {
        return mIsOnboarded;
    }

    /** Shows the onboarding notification if applicable. */
    public void maybeShowNotification() {
        maybeShowNotification(SystemClock.elapsedRealtime());
    }

    @VisibleForTesting
    void maybeShowNotification(long timestamp) {
        if (!shouldShowNotification(timestamp)) {
            return;
        }
        Log.d(TAG, "Showing onboarding notification.");

        incrementTotalNotificationsShown();
        mIsNotificationShowing = true;
        mLastShownTimestamp = timestamp;

        mContext.registerReceiver(mBroadcastReceiver, mIntentFilter,
                null /* broadcastPermission */, mHandler);
        mNotificationManager.notify(WakeupNotificationFactory.ONBOARD_ID,
                mWakeupNotificationFactory.createOnboardingNotification());
    }

    /**
     * Increment the total number of shown notifications and onboard the user if reached the
     * required amount.
     */
    private void incrementTotalNotificationsShown() {
        mTotalNotificationsShown++;
        if (mTotalNotificationsShown >= NOTIFICATIONS_UNTIL_ONBOARDED) {
            setOnboarded();
        } else {
            mWifiConfigManager.saveToStore(false /* forceWrite */);
        }
    }

    private boolean shouldShowNotification(long timestamp) {
        if (isOnboarded() || mIsNotificationShowing) {
            return false;
        }

        return mLastShownTimestamp == NOT_SHOWN_TIMESTAMP
                || (timestamp - mLastShownTimestamp) > REQUIRED_NOTIFICATION_DELAY;
    }

    /** Handles onboarding cleanup on stop. */
    public void onStop() {
        dismissNotification(false /* shouldOnboard */);
    }

    private void dismissNotification(boolean shouldOnboard) {
        if (!mIsNotificationShowing) {
            return;
        }

        if (shouldOnboard) {
            setOnboarded();
        }

        mContext.unregisterReceiver(mBroadcastReceiver);
        mNotificationManager.cancel(WakeupNotificationFactory.ONBOARD_ID);
        mIsNotificationShowing = false;
    }

    /** Sets the user as onboarded and persists to store. */
    public void setOnboarded() {
        if (mIsOnboarded) {
            return;
        }
        Log.d(TAG, "Setting user as onboarded.");
        mIsOnboarded = true;
        mWifiConfigManager.saveToStore(false /* forceWrite */);
    }

    /** Returns the {@link WakeupConfigStoreData.DataSource} for the onboarded status. */
    public WakeupConfigStoreData.DataSource<Boolean> getIsOnboadedDataSource() {
        return new IsOnboardedDataSource();
    }

    /** Returns the {@link WakeupConfigStoreData.DataSource} for the notification status. */
    public WakeupConfigStoreData.DataSource<Integer> getNotificationsDataSource() {
        return new NotificationsDataSource();
    }

    private class IsOnboardedDataSource implements WakeupConfigStoreData.DataSource<Boolean> {

        @Override
        public Boolean getData() {
            return mIsOnboarded;
        }

        @Override
        public void setData(Boolean data) {
            mIsOnboarded = data;
        }
    }

    private class NotificationsDataSource implements WakeupConfigStoreData.DataSource<Integer> {

        @Override
        public Integer getData() {
            return mTotalNotificationsShown;
        }

        @Override
        public void setData(Integer data) {
            mTotalNotificationsShown = data;
        }
    }
}
