/*
 * 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.systemui.qs.tiles;

import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
import static android.provider.Settings.Global.ZEN_MODE_OFF;

import android.app.ActivityManager;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.UserManager;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ZenRule;
import android.service.quicksettings.Tile;
import android.text.TextUtils;
import android.util.Slog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Switch;
import android.widget.Toast;

import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.notification.EnableZenModeDialog;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SysUIToast;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.volume.ZenModePanel;

import javax.inject.Inject;

/** Quick settings tile: Do not disturb **/
public class DndTile extends QSTileImpl<BooleanState> {

    private static final Intent ZEN_SETTINGS =
            new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);

    private static final Intent ZEN_PRIORITY_SETTINGS =
            new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS);

    private static final String ACTION_SET_VISIBLE = "com.android.systemui.dndtile.SET_VISIBLE";
    private static final String EXTRA_VISIBLE = "visible";

    private final ZenModeController mController;
    private final DndDetailAdapter mDetailAdapter;
    private final ActivityStarter mActivityStarter;
    private final SharedPreferences mSharedPreferences;
    private final BroadcastDispatcher mBroadcastDispatcher;

    private boolean mListening;
    private boolean mShowingDetail;
    private boolean mReceiverRegistered;

    @Inject
    public DndTile(QSHost host, ZenModeController zenModeController,
            ActivityStarter activityStarter, BroadcastDispatcher broadcastDispatcher,
            @Main SharedPreferences sharedPreferences) {
        super(host);
        mController = zenModeController;
        mActivityStarter = activityStarter;
        mSharedPreferences = sharedPreferences;
        mDetailAdapter = new DndDetailAdapter();
        mBroadcastDispatcher = broadcastDispatcher;
        broadcastDispatcher.registerReceiver(mReceiver, new IntentFilter(ACTION_SET_VISIBLE));
        mReceiverRegistered = true;
        mController.observe(getLifecycle(), mZenCallback);
    }

    @Override
    protected void handleDestroy() {
        super.handleDestroy();
        if (mReceiverRegistered) {
            mBroadcastDispatcher.unregisterReceiver(mReceiver);
            mReceiverRegistered = false;
        }
    }

    public static void setVisible(Context context, boolean visible) {
        Prefs.putBoolean(context, Prefs.Key.DND_TILE_VISIBLE, visible);
    }

    public static boolean isVisible(SharedPreferences prefs) {
        return prefs.getBoolean(Prefs.Key.DND_TILE_VISIBLE, false /* defaultValue */);
    }

    public static void setCombinedIcon(Context context, boolean combined) {
        Prefs.putBoolean(context, Prefs.Key.DND_TILE_COMBINED_ICON, combined);
    }

    public static boolean isCombinedIcon(SharedPreferences sharedPreferences) {
        return sharedPreferences.getBoolean(Prefs.Key.DND_TILE_COMBINED_ICON,
                false /* defaultValue */);
    }

    @Override
    public DetailAdapter getDetailAdapter() {
        return mDetailAdapter;
    }

    @Override
    public BooleanState newTileState() {
        return new BooleanState();
    }

    @Override
    public Intent getLongClickIntent() {
        return ZEN_SETTINGS;
    }

    @Override
    protected void handleClick() {
        // Zen is currently on
        if (mState.value) {
            mController.setZen(ZEN_MODE_OFF, null, TAG);
        } else {
            showDetail(true);
        }
    }

    @Override
    public void showDetail(boolean show) {
        int zenDuration = Settings.Secure.getInt(mContext.getContentResolver(),
                Settings.Secure.ZEN_DURATION, 0);
        boolean showOnboarding = Settings.Secure.getInt(mContext.getContentResolver(),
                Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0) != 0
                && Settings.Secure.getInt(mContext.getContentResolver(),
                Settings.Secure.ZEN_SETTINGS_UPDATED, 0) != 1;
        if (showOnboarding) {
            // don't show on-boarding again or notification ever
            Settings.Secure.putInt(mContext.getContentResolver(),
                    Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
            // turn on DND
            mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
            // show on-boarding screen
            Intent intent = new Intent(Settings.ZEN_MODE_ONBOARDING);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
            mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
        } else {
            switch (zenDuration) {
                case Settings.Secure.ZEN_DURATION_PROMPT:
                    mUiHandler.post(() -> {
                        Dialog mDialog = new EnableZenModeDialog(mContext).createDialog();
                        mDialog.getWindow().setType(
                                WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
                        SystemUIDialog.setShowForAllUsers(mDialog, true);
                        SystemUIDialog.registerDismissListener(mDialog);
                        SystemUIDialog.setWindowOnTop(mDialog);
                        mUiHandler.post(() -> mDialog.show());
                        mHost.collapsePanels();
                    });
                    break;
                case Settings.Secure.ZEN_DURATION_FOREVER:
                    mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
                    break;
                default:
                    Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration,
                            ActivityManager.getCurrentUser(), true).id;
                    mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                            conditionId, TAG);
            }
        }
    }

    @Override
    protected void handleSecondaryClick() {
        if (mController.isVolumeRestricted()) {
            // Collapse the panels, so the user can see the toast.
            mHost.collapsePanels();
            SysUIToast.makeText(mContext, mContext.getString(
                    com.android.internal.R.string.error_message_change_not_allowed),
                    Toast.LENGTH_LONG).show();
            return;
        }
        if (!mState.value) {
            // Because of the complexity of the zen panel, it needs to be shown after
            // we turn on zen below.
            mController.addCallback(new ZenModeController.Callback() {
                @Override
                public void onZenChanged(int zen) {
                    mController.removeCallback(this);
                    showDetail(true);
                }
            });
            mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
        } else {
            showDetail(true);
        }
    }

    @Override
    public CharSequence getTileLabel() {
        return mContext.getString(R.string.quick_settings_dnd_label);
    }

    @Override
    protected void handleUpdateState(BooleanState state, Object arg) {
        if (mController == null) return;
        final int zen = arg instanceof Integer ? (Integer) arg : mController.getZen();
        final boolean newValue = zen != ZEN_MODE_OFF;
        final boolean valueChanged = state.value != newValue;
        if (state.slash == null) state.slash = new SlashState();
        state.dualTarget = true;
        state.value = newValue;
        state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
        state.slash.isSlashed = !state.value;
        state.label = getTileLabel();
        state.secondaryLabel = TextUtils.emptyIfNull(ZenModeConfig.getDescription(mContext,
                zen != Global.ZEN_MODE_OFF, mController.getConfig(), false));
        state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_dnd);
        checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME);
        // Keeping the secondaryLabel in contentDescription instead of stateDescription is easier
        // to understand.
        switch (zen) {
            case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
                state.contentDescription =
                        mContext.getString(R.string.accessibility_quick_settings_dnd) + ", "
                        + state.secondaryLabel;
                break;
            case Global.ZEN_MODE_NO_INTERRUPTIONS:
                state.contentDescription =
                        mContext.getString(R.string.accessibility_quick_settings_dnd) + ", " +
                        mContext.getString(R.string.accessibility_quick_settings_dnd_none_on)
                                + ", " + state.secondaryLabel;
                break;
            case ZEN_MODE_ALARMS:
                state.contentDescription =
                        mContext.getString(R.string.accessibility_quick_settings_dnd) + ", " +
                        mContext.getString(R.string.accessibility_quick_settings_dnd_alarms_on)
                                + ", " + state.secondaryLabel;
                break;
            default:
                state.contentDescription = mContext.getString(
                        R.string.accessibility_quick_settings_dnd);
                break;
        }
        if (valueChanged) {
            fireToggleStateChanged(state.value);
        }
        state.dualLabelContentDescription = mContext.getResources().getString(
                R.string.accessibility_quick_settings_open_settings, getTileLabel());
        state.expandedAccessibilityClassName = Switch.class.getName();
    }

    @Override
    public int getMetricsCategory() {
        return MetricsEvent.QS_DND;
    }

    @Override
    protected String composeChangeAnnouncement() {
        if (mState.value) {
            return mContext.getString(R.string.accessibility_quick_settings_dnd_changed_on);
        } else {
            return mContext.getString(R.string.accessibility_quick_settings_dnd_changed_off);
        }
    }

    @Override
    public void handleSetListening(boolean listening) {
        super.handleSetListening(listening);
        if (mListening == listening) return;
        mListening = listening;
        if (mListening) {
            Prefs.registerListener(mContext, mPrefListener);
        } else {
            Prefs.unregisterListener(mContext, mPrefListener);
        }
    }

    @Override
    public boolean isAvailable() {
        return isVisible(mSharedPreferences);
    }

    private final OnSharedPreferenceChangeListener mPrefListener
            = new OnSharedPreferenceChangeListener() {
        @Override
        public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
                @Prefs.Key String key) {
            if (Prefs.Key.DND_TILE_COMBINED_ICON.equals(key) ||
                    Prefs.Key.DND_TILE_VISIBLE.equals(key)) {
                refreshState();
            }
        }
    };

    private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
        public void onZenChanged(int zen) {
            refreshState(zen);
            if (isShowingDetail()) {
                mDetailAdapter.updatePanel();
            }
        }

        @Override
        public void onConfigChanged(ZenModeConfig config) {
            if (isShowingDetail()) {
                mDetailAdapter.updatePanel();
            }
        }
    };

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final boolean visible = intent.getBooleanExtra(EXTRA_VISIBLE, false);
            setVisible(mContext, visible);
            refreshState();
        }
    };

    private final class DndDetailAdapter implements DetailAdapter, OnAttachStateChangeListener {

        private ZenModePanel mZenPanel;
        private boolean mAuto;

        @Override
        public CharSequence getTitle() {
            return mContext.getString(R.string.quick_settings_dnd_label);
        }

        @Override
        public Boolean getToggleState() {
            return mState.value;
        }

        @Override
        public Intent getSettingsIntent() {
            return ZEN_SETTINGS;
        }

        @Override
        public void setToggleState(boolean state) {
            MetricsLogger.action(mContext, MetricsEvent.QS_DND_TOGGLE, state);
            if (!state) {
                mController.setZen(ZEN_MODE_OFF, null, TAG);
                mAuto = false;
            } else {
                mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
            }
        }

        @Override
        public int getMetricsCategory() {
            return MetricsEvent.QS_DND_DETAILS;
        }

        @Override
        public View createDetailView(Context context, View convertView, ViewGroup parent) {
            mZenPanel = convertView != null ? (ZenModePanel) convertView
                    : (ZenModePanel) LayoutInflater.from(context).inflate(
                            R.layout.zen_mode_panel, parent, false);
            if (convertView == null) {
                mZenPanel.init(mController);
                mZenPanel.addOnAttachStateChangeListener(this);
                mZenPanel.setCallback(mZenModePanelCallback);
                mZenPanel.setEmptyState(R.drawable.ic_qs_dnd_detail_empty, R.string.dnd_is_off);
            }
            updatePanel();
            return mZenPanel;
        }

        private void updatePanel() {
            if (mZenPanel == null) return;
            mAuto = false;
            if (mController.getZen() == ZEN_MODE_OFF) {
                mZenPanel.setState(ZenModePanel.STATE_OFF);
            } else {
                ZenModeConfig config = mController.getConfig();
                String summary = "";
                if (config.manualRule != null && config.manualRule.enabler != null) {
                    summary = getOwnerCaption(config.manualRule.enabler);
                }
                for (ZenRule automaticRule : config.automaticRules.values()) {
                    if (automaticRule.isAutomaticActive()) {
                        if (summary.isEmpty()) {
                            summary = mContext.getString(R.string.qs_dnd_prompt_auto_rule,
                                    automaticRule.name);
                        } else {
                            summary = mContext.getString(R.string.qs_dnd_prompt_auto_rule_app);
                        }
                    }
                }
                if (summary.isEmpty()) {
                    mZenPanel.setState(ZenModePanel.STATE_MODIFY);
                } else {
                    mAuto = true;
                    mZenPanel.setState(ZenModePanel.STATE_AUTO_RULE);
                    mZenPanel.setAutoText(summary);
                }
            }
        }

        private String getOwnerCaption(String owner) {
            final PackageManager pm = mContext.getPackageManager();
            try {
                final ApplicationInfo info = pm.getApplicationInfo(owner, 0);
                if (info != null) {
                    final CharSequence seq = info.loadLabel(pm);
                    if (seq != null) {
                        final String str = seq.toString().trim();
                        return mContext.getString(R.string.qs_dnd_prompt_app, str);
                    }
                }
            } catch (Throwable e) {
                Slog.w(TAG, "Error loading owner caption", e);
            }
            return "";
        }

        @Override
        public void onViewAttachedToWindow(View v) {
            mShowingDetail = true;
        }

        @Override
        public void onViewDetachedFromWindow(View v) {
            mShowingDetail = false;
            mZenPanel = null;
        }
    }

    private final ZenModePanel.Callback mZenModePanelCallback = new ZenModePanel.Callback() {
        @Override
        public void onPrioritySettings() {
            mActivityStarter.postStartActivityDismissingKeyguard(
                    ZEN_PRIORITY_SETTINGS, 0);
        }

        @Override
        public void onInteraction() {
            // noop
        }

        @Override
        public void onExpanded(boolean expanded) {
            // noop
        }
    };

}
