/*
 * Copyright (C) 2020 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.car.systembar;

import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;

import static com.android.systemui.car.users.CarSystemUIUserUtil.getCurrentUserHandle;

import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.role.RoleManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.RemoteException;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;

import androidx.annotation.Nullable;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.AlphaOptimizedImageView;

import java.net.URISyntaxException;

/**
 * CarSystemBarButton is an image button that allows for a bit more configuration at the
 * xml file level. This allows for more control via overlays instead of having to update
 * code.
 */
public class CarSystemBarButton extends LinearLayout {

    private static final String TAG = "CarSystemBarButton";
    private static final String BUTTON_FILTER_DELIMITER = ";";
    private static final String EXTRA_BUTTON_CATEGORIES = "categories";
    private static final String EXTRA_BUTTON_PACKAGES = "packages";
    private static final String EXTRA_DIALOG_CLOSE_REASON = "reason";
    private static final String DIALOG_CLOSE_REASON_CAR_SYSTEMBAR_BUTTON = "carsystembarbutton";
    private static final float DEFAULT_SELECTED_ALPHA = 1f;
    private static final float DEFAULT_UNSELECTED_ALPHA = 0.75f;
    private static final float DISABLED_ALPHA = 0.25f;

    private final Context mContext;
    private final ActivityManager mActivityManager;
    @Nullable
    private UserTracker mUserTracker;
    private AlphaOptimizedImageView mIcon;
    private AlphaOptimizedImageView mMoreIcon;
    private ImageView mUnseenIcon;
    private String mIntent;
    private String mLongIntent;
    private boolean mBroadcastIntent;
    /** Whether to clear the backstack (i.e. put the home activity directly behind) when pressed */
    private boolean mClearBackStack;
    private boolean mHasUnseen = false;
    private boolean mSelected = false;
    private boolean mDisabled = false;
    private float mSelectedAlpha;
    private float mUnselectedAlpha;
    private int mSelectedIconResourceId;
    private int mIconResourceId;
    private Drawable mAppIcon;
    private boolean mIsDefaultAppIconForRoleEnabled;
    private boolean mToggleSelectedState;
    private String[] mComponentNames;
    /** App categories that are to be used with this widget */
    private String[] mButtonCategories;
    /** App packages that are allowed to be used with this widget */
    private String[] mButtonPackages;
    /** Whether to display more icon beneath the primary icon when the button is selected */
    private boolean mShowMoreWhenSelected = false;
    /** Whether to highlight the button if the active application is associated with it */
    private boolean mHighlightWhenSelected = false;
    private Runnable mOnClickWhileDisabledRunnable;

    public CarSystemBarButton(Context context, AttributeSet attrs) {
        super(context, attrs);

        // Do not move this init call. All logic should be carried out after this.
        init();

        mContext = context;
        mActivityManager = mContext.getSystemService(ActivityManager.class);
        View.inflate(mContext, R.layout.car_system_bar_button, /* root= */ this);
        // CarSystemBarButton attrs
        TypedArray typedArray = context.obtainStyledAttributes(attrs,
                R.styleable.CarSystemBarButton);

        setUpIntents(typedArray);
        setUpIcons(typedArray);
        typedArray.recycle();
    }

    /**
     * Initializer for child classes.
     */
    protected void init() {
    }

    /**
     * @param selected true if should indicate if this is a selected state, false otherwise
     */
    public void setSelected(boolean selected) {
        if (mDisabled) {
            // if the button is disabled, mSelected should not be modified and the button
            // should be unselectable
            return;
        }
        super.setSelected(selected);
        mSelected = selected;

        refreshIconAlpha(mIcon);

        if (mShowMoreWhenSelected && mMoreIcon != null) {
            mMoreIcon.setVisibility(selected ? VISIBLE : GONE);
        }
        updateImage(mIcon);
    }

    /** Gets whether the icon is in a selected state. */
    public boolean getSelected() {
        return mSelected;
    }

    /**
     * @param hasUnseen true if should indicate if this is a Unseen state, false otherwise.
     */
    public void setUnseen(boolean hasUnseen) {
        mHasUnseen = hasUnseen;
        updateImage(mIcon);
    }

    /**
     * @param disabled true if icon should be isabled, false otherwise.
     * @param runnable to run when button is clicked while disabled.
     */
    public void setDisabled(boolean disabled, @Nullable Runnable runnable) {
        mDisabled = disabled;
        mOnClickWhileDisabledRunnable = runnable;
        refreshIconAlpha(mIcon);
        updateImage(mIcon);
    }

    /** Gets whether the icon is disabled */
    public boolean getDisabled() {
        return mDisabled;
    }

    /** Runs the Runnable when the button is clicked while disabled */
    public void runOnClickWhileDisabled() {
        if (mOnClickWhileDisabledRunnable == null) {
            return;
        }
        mOnClickWhileDisabledRunnable.run();
    }

    /**
     * Sets the current icon of the default application associated with this button.
     */
    public void setAppIcon(Drawable appIcon) {
        mAppIcon = appIcon;
        updateImage(mIcon);
    }

    /** Gets the icon of the app currently associated to the role of this button. */
    @VisibleForTesting
    protected Drawable getAppIcon() {
        return mAppIcon;
    }

    /** Gets whether the icon is in an unseen state. */
    public boolean getUnseen() {
        return mHasUnseen;
    }

    /**
     * @return The app categories the component represents
     */
    public String[] getCategories() {
        if (mButtonCategories == null) {
            return new String[0];
        }
        return mButtonCategories;
    }

    /**
     * @return The valid packages that should be considered.
     */
    public String[] getPackages() {
        if (mButtonPackages == null) {
            return new String[0];
        }
        return mButtonPackages;
    }

    /**
     * @return The list of component names.
     */
    public String[] getComponentName() {
        if (mComponentNames == null) {
            return new String[0];
        }
        return mComponentNames;
    }

    /**
     * Subclasses should override this method to return the {@link RoleManager} role associated
     * with this button.
     */
    protected String getRoleName() {
        return null;
    }

    /**
     * @return true if this button should show the icon of the default application for the
     * role returned by {@link #getRoleName()}.
     */
    protected boolean isDefaultAppIconForRoleEnabled() {
        return mIsDefaultAppIconForRoleEnabled;
    }

    /**
     * @return The id of the display the button is on or Display.INVALID_DISPLAY if it's not yet on
     * a display.
     */
    protected int getDisplayId() {
        Display display = getDisplay();
        if (display == null) {
            return Display.INVALID_DISPLAY;
        }
        return display.getDisplayId();
    }

    protected boolean hasSelectionState() {
        return mHighlightWhenSelected || mShowMoreWhenSelected;
    }

    protected float getSelectedAlpha() {
        return mSelectedAlpha;
    }

    @VisibleForTesting
    protected float getUnselectedAlpha() {
        return mUnselectedAlpha;
    }

    @VisibleForTesting
    protected float getDisabledAlpha() {
        return DISABLED_ALPHA;
    }

    @VisibleForTesting
    protected float getIconAlpha() { return mIcon.getAlpha(); }

    /**
     * Sets up intents for click, long touch, and broadcast.
     */
    protected void setUpIntents(TypedArray typedArray) {
        mIntent = typedArray.getString(R.styleable.CarSystemBarButton_intent);
        mLongIntent = typedArray.getString(R.styleable.CarSystemBarButton_longIntent);
        mBroadcastIntent = typedArray.getBoolean(R.styleable.CarSystemBarButton_broadcast, false);

        mClearBackStack = typedArray.getBoolean(R.styleable.CarSystemBarButton_clearBackStack,
                false);

        String categoryString = typedArray.getString(R.styleable.CarSystemBarButton_categories);
        String packageString = typedArray.getString(R.styleable.CarSystemBarButton_packages);
        String componentNameString =
                typedArray.getString(R.styleable.CarSystemBarButton_componentNames);

        try {
            if (mIntent != null) {
                final Intent intent = Intent.parseUri(mIntent, Intent.URI_INTENT_SCHEME);
                setOnClickListener(getButtonClickListener(intent));
                if (packageString != null) {
                    mButtonPackages = packageString.split(BUTTON_FILTER_DELIMITER);
                    intent.putExtra(EXTRA_BUTTON_PACKAGES, mButtonPackages);
                }
                if (categoryString != null) {
                    mButtonCategories = categoryString.split(BUTTON_FILTER_DELIMITER);
                    intent.putExtra(EXTRA_BUTTON_CATEGORIES, mButtonCategories);
                }
                if (componentNameString != null) {
                    mComponentNames = componentNameString.split(BUTTON_FILTER_DELIMITER);
                }
            }
        } catch (URISyntaxException e) {
            throw new RuntimeException("Failed to attach intent", e);
        }

        try {
            if (mLongIntent != null && (Build.IS_ENG || Build.IS_USERDEBUG)) {
                final Intent intent = Intent.parseUri(mLongIntent, Intent.URI_INTENT_SCHEME);
                setOnLongClickListener(getButtonLongClickListener(intent));
            }
        } catch (URISyntaxException e) {
            throw new RuntimeException("Failed to attach long press intent", e);
        }
    }

    /** Defines the behavior of a button click. */
    protected OnClickListener getButtonClickListener(Intent toSend) {
        return v -> {
            if (mDisabled) {
                runOnClickWhileDisabled();
                return;
            }
            boolean startState = mSelected;
            Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
            intent.putExtra(EXTRA_DIALOG_CLOSE_REASON, DIALOG_CLOSE_REASON_CAR_SYSTEMBAR_BUTTON);
            mContext.sendBroadcastAsUser(intent, getCurrentUserHandle(mContext, mUserTracker));

            boolean intentLaunched = false;
            try {
                if (mBroadcastIntent) {
                    mContext.sendBroadcastAsUser(toSend,
                            getCurrentUserHandle(mContext, mUserTracker));
                    return;
                }
                ActivityOptions options = ActivityOptions.makeBasic();
                options.setLaunchDisplayId(mContext.getDisplayId());
                mContext.startActivityAsUser(toSend, options.toBundle(),
                        getCurrentUserHandle(mContext, mUserTracker));
                intentLaunched = true;
            } catch (Exception e) {
                Log.e(TAG, "Failed to launch intent", e);
            }

            if (intentLaunched && mClearBackStack) {
                try {
                    ActivityTaskManager.RootTaskInfo rootTaskInfo =
                            ActivityTaskManager.getService().getRootTaskInfoOnDisplay(
                                    WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_UNDEFINED,
                                    mContext.getDisplayId());
                    if (rootTaskInfo != null) {
                        mActivityManager.moveTaskToFront(rootTaskInfo.taskId,
                                ActivityManager.MOVE_TASK_WITH_HOME);
                    }
                } catch (RemoteException e) {
                    Log.e(TAG, "Failed getting root task info", e);
                }
            }

            if (mToggleSelectedState && (startState == mSelected)) {
                setSelected(!mSelected);
            }
        };
    }

    /** Defines the behavior of a long click. */
    protected OnLongClickListener getButtonLongClickListener(Intent toSend) {
        return v -> {
            Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
            intent.putExtra(EXTRA_DIALOG_CLOSE_REASON, DIALOG_CLOSE_REASON_CAR_SYSTEMBAR_BUTTON);
            mContext.sendBroadcastAsUser(intent, getCurrentUserHandle(mContext, mUserTracker));
            try {
                ActivityOptions options = ActivityOptions.makeBasic();
                options.setLaunchDisplayId(mContext.getDisplayId());
                mContext.startActivityAsUser(toSend, options.toBundle(),
                        getCurrentUserHandle(mContext, mUserTracker));
            } catch (Exception e) {
                Log.e(TAG, "Failed to launch intent", e);
            }
            // consume event either way
            return true;
        };
    }

    void setUserTracker(UserTracker userTracker) {
        mUserTracker = userTracker;
    }

    /**
     * Initializes view-related aspects of the button.
     */
    private void setUpIcons(TypedArray typedArray) {
        mSelectedAlpha = typedArray.getFloat(
                R.styleable.CarSystemBarButton_selectedAlpha, DEFAULT_SELECTED_ALPHA);
        mUnselectedAlpha = typedArray.getFloat(
                R.styleable.CarSystemBarButton_unselectedAlpha, DEFAULT_UNSELECTED_ALPHA);
        mHighlightWhenSelected = typedArray.getBoolean(
                R.styleable.CarSystemBarButton_highlightWhenSelected,
                mHighlightWhenSelected);
        mShowMoreWhenSelected = typedArray.getBoolean(
                R.styleable.CarSystemBarButton_showMoreWhenSelected,
                mShowMoreWhenSelected);

        mIconResourceId = typedArray.getResourceId(
                R.styleable.CarSystemBarButton_icon, 0);
        mSelectedIconResourceId = typedArray.getResourceId(
                R.styleable.CarSystemBarButton_selectedIcon, mIconResourceId);
        mIsDefaultAppIconForRoleEnabled = typedArray.getBoolean(
                R.styleable.CarSystemBarButton_useDefaultAppIconForRole, false);
        mToggleSelectedState = typedArray.getBoolean(
                R.styleable.CarSystemBarButton_toggleSelected, false);
        mIcon = findViewById(R.id.car_nav_button_icon_image);
        refreshIconAlpha(mIcon);
        mMoreIcon = findViewById(R.id.car_nav_button_more_icon);
        mUnseenIcon = findViewById(R.id.car_nav_button_unseen_icon);
        updateImage(mIcon);
    }

    protected void updateImage(AlphaOptimizedImageView icon) {
        if (mIsDefaultAppIconForRoleEnabled && mAppIcon != null) {
            icon.setImageDrawable(mAppIcon);
        } else {
            icon.setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId);
        }
        mUnseenIcon.setVisibility(mHasUnseen ? VISIBLE : GONE);
    }

    protected void refreshIconAlpha(AlphaOptimizedImageView icon) {
        if (mDisabled) {
            icon.setAlpha(DISABLED_ALPHA);
        } else {
            icon.setAlpha(mHighlightWhenSelected && mSelected ? mSelectedAlpha : mUnselectedAlpha);
        }
    }
}
