/*
 * Copyright (C) 2021 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.launcher3.widget.picker;

import static android.animation.ValueAnimator.areAnimatorsEnabled;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.Nullable;
import androidx.annotation.UiThread;

import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.icons.PlaceHolderIconDrawable;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;

/**
 * A UI represents a header of an app shown in the full widgets tray.
 *
 * It is a {@link LinearLayout} which contains an app icon, an app name, a subtitle and a checkbox
 * which indicates if the widgets content view underneath this header should be shown.
 */
public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpdateReceiver {

    private static final int[] EXPANDED_DRAWABLE_STATE = new int[] {android.R.attr.state_expanded};

    private final int mIconSize;
    /**
     * Indicates if the header is collapsable. For example, when displayed in a two pane layout,
     * widget apps aren't collapsable.
    */
    private final boolean mIsCollapsable;
    @Nullable private CancellableTask mIconLoadRequest;
    @Nullable private Drawable mIconDrawable;
    @Nullable private WidgetsListDrawableState mListDrawableState;
    private ImageView mAppIcon;
    private TextView mTitle;
    private TextView mSubtitle;
    private boolean mEnableIconUpdateAnimation = false;
    private boolean mIsExpanded = false;

    public WidgetsListHeader(Context context) {
        this(context, /* attrs= */ null);
    }

    public WidgetsListHeader(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, /* defStyle= */ 0);
    }

    public WidgetsListHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        ActivityContext activity = ActivityContext.lookupContext(context);
        DeviceProfile grid = activity.getDeviceProfile();
        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.WidgetsListRowHeader, defStyleAttr, /* defStyleRes= */ 0);
        mIconSize = a.getDimensionPixelSize(R.styleable.WidgetsListRowHeader_appIconSize,
                grid.iconSizePx);
        mIsCollapsable = a.getBoolean(R.styleable.WidgetsListRowHeader_collapsable, true);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mAppIcon = findViewById(R.id.app_icon);
        mTitle = findViewById(R.id.app_title);
        mSubtitle = findViewById(R.id.app_subtitle);
        // Lists that cannot collapse, don't need EXPAND or COLLAPSE accessibility actions.
        if (mIsCollapsable) {
            setAccessibilityDelegate(new AccessibilityDelegate() {

                @Override
                public void onInitializeAccessibilityNodeInfo(View host,
                        AccessibilityNodeInfo info) {
                    if (mIsExpanded) {
                        info.removeAction(AccessibilityNodeInfo.ACTION_EXPAND);
                        info.addAction(AccessibilityNodeInfo.ACTION_COLLAPSE);
                    } else {
                        info.removeAction(AccessibilityNodeInfo.ACTION_COLLAPSE);
                        info.addAction(AccessibilityNodeInfo.ACTION_EXPAND);
                    }
                    super.onInitializeAccessibilityNodeInfo(host, info);
                }

                @Override
                public boolean performAccessibilityAction(View host, int action, Bundle args) {
                    switch (action) {
                        case AccessibilityNodeInfo.ACTION_EXPAND:
                        case AccessibilityNodeInfo.ACTION_COLLAPSE:
                            callOnClick();
                            return true;
                        default:
                            return super.performAccessibilityAction(host, action, args);
                    }
                }
            });
        }
    }

    /** Sets the expand toggle to expand / collapse. */
    @UiThread
    public void setExpanded(boolean isExpanded) {
        this.mIsExpanded = isExpanded;
        refreshDrawableState();
        refreshTextAppearance(isExpanded);
    }

    private void refreshTextAppearance(boolean isExpanded) {
        mTitle.setTextAppearance(isExpanded ? R.style.WidgetListHeader_Title_Selected
                : R.style.WidgetListHeader_Title);
        mSubtitle.setTextAppearance(isExpanded ? R.style.WidgetListHeader_SubTitle_Selected
                : R.style.WidgetListHeader_SubTitle);
    }

    /** @return true if this header is expanded. */
    public boolean isExpanded() {
        return mIsExpanded;
    }

    /** Sets the {@link WidgetsListDrawableState} and refreshes the background drawable. */
    @UiThread
    public void setListDrawableState(WidgetsListDrawableState state) {
        if (state == mListDrawableState) return;
        this.mListDrawableState = state;
        refreshDrawableState();
    }

    /** Apply app icon, labels and tag using a generic {@link WidgetsListHeaderEntry}. */
    @UiThread
    public void applyFromItemInfoWithIcon(WidgetsListHeaderEntry entry) {
        PackageItemInfo info = entry.mPkgItem;
        setIcon(info.newIcon(getContext()));
        setTitles(entry);
        setExpanded(entry.isWidgetListShown());

        super.setTag(info);

        verifyHighRes();
    }

    void setIcon(Drawable icon) {
        applyDrawables(icon);
        mIconDrawable = icon;
        if (mIconDrawable != null) {
            mIconDrawable.setVisible(
                    /* visible= */ getWindowVisibility() == VISIBLE && isShown(),
                    /* restart= */ false);
        }
    }

    private void applyDrawables(Drawable icon) {
        icon.setBounds(0, 0, mIconSize, mIconSize);

        LinearLayout.LayoutParams layoutParams =
                (LinearLayout.LayoutParams) mAppIcon.getLayoutParams();
        layoutParams.width = mIconSize;
        layoutParams.height = mIconSize;
        mAppIcon.setLayoutParams(layoutParams);
        mAppIcon.setImageDrawable(icon);

        // If the current icon is a placeholder color, animate its update.
        if (mIconDrawable != null
                && mIconDrawable instanceof PlaceHolderIconDrawable
                && mEnableIconUpdateAnimation) {
            ((PlaceHolderIconDrawable) mIconDrawable).animateIconUpdate(icon);
        }
    }

    private void setTitles(WidgetsListHeaderEntry entry) {
        mTitle.setText(entry.mPkgItem.title);

        String subtitle = entry.getSubtitle(getContext());
        if (TextUtils.isEmpty(subtitle)) {
            mSubtitle.setVisibility(GONE);
        } else {
            mSubtitle.setText(subtitle);
            mSubtitle.setVisibility(VISIBLE);
        }
    }

    @Override
    public void reapplyItemInfo(ItemInfoWithIcon info) {
        if (getTag() == info) {
            mIconLoadRequest = null;
            mEnableIconUpdateAnimation = areAnimatorsEnabled();

            // Optimization: Starting in N, pre-uploads the bitmap to RenderThread.
            info.bitmap.icon.prepareToDraw();

            setIcon(info.newIcon(getContext()));

            mEnableIconUpdateAnimation = false;
        }
    }

    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        // We create a drawable state with an additional two spaces to be able to fit expanded state
        // and the list drawable state.
        int[] drawableState = super.onCreateDrawableState(extraSpace + 2);
        if (mIsExpanded) {
            mergeDrawableStates(drawableState, EXPANDED_DRAWABLE_STATE);
        }
        if (mListDrawableState != null) {
            mergeDrawableStates(drawableState, mListDrawableState.mStateSet);
        }
        return drawableState;
    }

    /** Verifies that the current icon is high-res otherwise posts a request to load the icon. */
    public void verifyHighRes() {
        if (mIconLoadRequest != null) {
            mIconLoadRequest.cancel();
            mIconLoadRequest = null;
        }
        if (getTag() instanceof ItemInfoWithIcon info && info.getMatchingLookupFlag().useLowRes()) {
            mIconLoadRequest = LauncherAppState.getInstance(getContext()).getIconCache()
                    .updateIconInBackground(this, info);
        }
    }
}
