/*
 * Copyright (C) 2015 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.settings.applications;

import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Switch;

import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.applications.ApplicationsState.AppFilter;
import com.android.settingslib.utils.StringUtil;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

/**
 * Connects the info provided by ApplicationsState and UsageStatsManager.
 * Also provides app filters that can use the notification data.
 */
public class AppStateNotificationBridge extends AppStateBaseBridge {

    private final Context mContext;
    private IUsageStatsManager mUsageStatsManager;
    protected List<Integer> mUserIds;
    private NotificationBackend mBackend;
    private static final int DAYS_TO_CHECK = 7;

    public AppStateNotificationBridge(Context context, ApplicationsState appState,
            Callback callback, IUsageStatsManager usageStatsManager,
            UserManager userManager, NotificationBackend backend) {
        super(appState, callback);
        mContext = context;
        mUsageStatsManager = usageStatsManager;
        mBackend = backend;
        mUserIds = new ArrayList<>();
        mUserIds.add(mContext.getUserId());
        int workUserId = Utils.getManagedProfileId(userManager, mContext.getUserId());
        if (workUserId != UserHandle.USER_NULL) {
            mUserIds.add(workUserId);
        }
    }

    @Override
    protected void loadAllExtraInfo() {
        ArrayList<AppEntry> apps = mAppSession.getAllApps();
        if (apps == null) return;

        final Map<String, NotificationsSentState> map = getAggregatedUsageEvents();
        for (AppEntry entry : apps) {
            NotificationsSentState stats =
                    map.get(getKey(UserHandle.getUserId(entry.info.uid), entry.info.packageName));
            calculateAvgSentCounts(stats);
            addBlockStatus(entry, stats);
            entry.extraInfo = stats;
        }
    }

    @Override
    protected void updateExtraInfo(AppEntry entry, String pkg, int uid) {
        NotificationsSentState stats = getAggregatedUsageEvents(
                UserHandle.getUserId(entry.info.uid), entry.info.packageName);
        calculateAvgSentCounts(stats);
        addBlockStatus(entry, stats);
        entry.extraInfo = stats;
    }

    public static CharSequence getSummary(Context context, NotificationsSentState state,
            boolean sortByRecency) {
        if (sortByRecency) {
            if (state.lastSent == 0) {
                return context.getString(R.string.notifications_sent_never);
            }
            return StringUtil.formatRelativeTime(
                    context, System.currentTimeMillis() - state.lastSent, true);
        } else {
            if (state.avgSentWeekly > 0) {
                return context.getString(R.string.notifications_sent_weekly, state.avgSentWeekly);
            }
            return context.getString(R.string.notifications_sent_daily, state.avgSentDaily);
        }
    }

    private void addBlockStatus(AppEntry entry, NotificationsSentState stats) {
        if (stats != null) {
            stats.blocked = mBackend.getNotificationsBanned(entry.info.packageName, entry.info.uid);
            stats.systemApp = mBackend.isSystemApp(mContext, entry.info);
            stats.blockable = !stats.systemApp || (stats.systemApp && stats.blocked);
        }
    }

    private void calculateAvgSentCounts(NotificationsSentState stats) {
        if (stats != null) {
            stats.avgSentDaily = Math.round((float) stats.sentCount / DAYS_TO_CHECK);
            if (stats.sentCount < DAYS_TO_CHECK) {
                stats.avgSentWeekly = stats.sentCount;
            }
        }
    }

    protected Map<String, NotificationsSentState> getAggregatedUsageEvents() {
        ArrayMap<String, NotificationsSentState> aggregatedStats = new ArrayMap<>();

        long now = System.currentTimeMillis();
        long startTime = now - (DateUtils.DAY_IN_MILLIS * DAYS_TO_CHECK);
        for (int userId : mUserIds) {
            UsageEvents events = null;
            try {
                events = mUsageStatsManager.queryEventsForUser(
                        startTime, now, userId, mContext.getPackageName());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            if (events != null) {
                UsageEvents.Event event = new UsageEvents.Event();
                while (events.hasNextEvent()) {
                    events.getNextEvent(event);
                    NotificationsSentState stats =
                            aggregatedStats.get(getKey(userId, event.getPackageName()));
                    if (stats == null) {
                        stats = new NotificationsSentState();
                        aggregatedStats.put(getKey(userId, event.getPackageName()), stats);
                    }

                    if (event.getEventType() == UsageEvents.Event.NOTIFICATION_INTERRUPTION) {
                        if (event.getTimeStamp() > stats.lastSent) {
                            stats.lastSent = event.getTimeStamp();
                        }
                        stats.sentCount++;
                    }

                }
            }
        }
        return aggregatedStats;
    }

    protected NotificationsSentState getAggregatedUsageEvents(int userId, String pkg) {
        NotificationsSentState stats = null;

        long now = System.currentTimeMillis();
        long startTime = now - (DateUtils.DAY_IN_MILLIS * DAYS_TO_CHECK);
        UsageEvents events = null;
        try {
            events = mUsageStatsManager.queryEventsForPackageForUser(
                    startTime, now, userId, pkg, mContext.getPackageName());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        if (events != null) {
            UsageEvents.Event event = new UsageEvents.Event();
            while (events.hasNextEvent()) {
                events.getNextEvent(event);

                if (event.getEventType() == UsageEvents.Event.NOTIFICATION_INTERRUPTION) {
                    if (stats == null) {
                        stats = new NotificationsSentState();
                    }
                    if (event.getTimeStamp() > stats.lastSent) {
                        stats.lastSent = event.getTimeStamp();
                    }
                    stats.sentCount++;
                }

            }
        }
        return stats;
    }

    private static NotificationsSentState getNotificationsSentState(AppEntry entry) {
        if (entry == null || entry.extraInfo == null) {
            return null;
        }
        if (entry.extraInfo instanceof NotificationsSentState) {
            return (NotificationsSentState) entry.extraInfo;
        }
        return null;
    }

    protected static String getKey(int userId, String pkg) {
        return userId + "|" + pkg;
    }

    public View.OnClickListener getSwitchOnClickListener(final AppEntry entry) {
        if (entry != null) {
            return v -> {
                ViewGroup view = (ViewGroup) v;
                Switch toggle = view.findViewById(R.id.switchWidget);
                if (toggle != null) {
                    if (!toggle.isEnabled()) {
                        return;
                    }
                    toggle.toggle();
                    mBackend.setNotificationsEnabledForPackage(
                            entry.info.packageName, entry.info.uid, toggle.isChecked());
                    NotificationsSentState stats = getNotificationsSentState(entry);
                    if (stats != null) {
                        stats.blocked = !toggle.isChecked();
                    }
                }
            };
        }
        return null;
    }

    public static final AppFilter FILTER_APP_NOTIFICATION_RECENCY = new AppFilter() {
        @Override
        public void init() {
        }

        @Override
        public boolean filterApp(AppEntry info) {
            NotificationsSentState state = getNotificationsSentState(info);
            if (state != null) {
                return state.lastSent != 0;
            }
            return false;
        }
    };

    public static final AppFilter FILTER_APP_NOTIFICATION_FREQUENCY = new AppFilter() {
        @Override
        public void init() {
        }

        @Override
        public boolean filterApp(AppEntry info) {
            NotificationsSentState state = getNotificationsSentState(info);
            if (state != null) {
                return state.sentCount != 0;
            }
            return false;
        }
    };

    public static final Comparator<AppEntry> RECENT_NOTIFICATION_COMPARATOR
            = new Comparator<AppEntry>() {
        @Override
        public int compare(AppEntry object1, AppEntry object2) {
            NotificationsSentState state1 = getNotificationsSentState(object1);
            NotificationsSentState state2 = getNotificationsSentState(object2);
            if (state1 == null && state2 != null) return -1;
            if (state1 != null && state2 == null) return 1;
            if (state1 != null && state2 != null) {
                if (state1.lastSent < state2.lastSent) return 1;
                if (state1.lastSent > state2.lastSent) return -1;
            }
            return ApplicationsState.ALPHA_COMPARATOR.compare(object1, object2);
        }
    };

    public static final Comparator<AppEntry> FREQUENCY_NOTIFICATION_COMPARATOR
            = new Comparator<AppEntry>() {
        @Override
        public int compare(AppEntry object1, AppEntry object2) {
            NotificationsSentState state1 = getNotificationsSentState(object1);
            NotificationsSentState state2 = getNotificationsSentState(object2);
            if (state1 == null && state2 != null) return -1;
            if (state1 != null && state2 == null) return 1;
            if (state1 != null && state2 != null) {
                if (state1.sentCount < state2.sentCount) return 1;
                if (state1.sentCount > state2.sentCount) return -1;
            }
            return ApplicationsState.ALPHA_COMPARATOR.compare(object1, object2);
        }
    };

    public static final boolean enableSwitch(AppEntry entry) {
        NotificationsSentState stats = getNotificationsSentState(entry);
        if (stats == null) {
            return false;
        }

        return stats.blockable;
    }

    public static final boolean checkSwitch(AppEntry entry) {
        NotificationsSentState stats = getNotificationsSentState(entry);
        if (stats == null) {
            return false;
        }

        return !stats.blocked;
    }

    /**
     * NotificationsSentState contains how often an app sends notifications and how recently it sent
     * one.
     */
    public static class NotificationsSentState {
        public int avgSentDaily = 0;
        public int avgSentWeekly = 0;
        public long lastSent = 0;
        public int sentCount = 0;
        public boolean blockable;
        public boolean blocked;
        public boolean systemApp;
    }
}
