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

import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.widget.TextView;

import androidx.annotation.Nullable;

import com.android.app.animation.Interpolators;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.dragndrop.DragLayer;

/**
 * A toast-like UI at the bottom of the screen with a label, button action, and dismiss action.
 */
public class Snackbar extends AbstractFloatingView {

    private static final long SHOW_DURATION_MS = 180;
    private static final long HIDE_DURATION_MS = 180;
    private static final int TIMEOUT_DURATION_MS = 4000;

    private final ActivityContext mActivity;
    private Runnable mOnDismissed;

    public Snackbar(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public Snackbar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mActivity = ActivityContext.lookupContext(context);
        inflate(context, R.layout.snackbar, this);
    }

    /** Show a snackbar with just a label. */
    public static <T extends Context & ActivityContext> void show(T activity, int labelStringRedId,
            Runnable onDismissed) {
        show(activity, labelStringRedId, NO_ID, onDismissed, null);
    }

    /** Show a snackbar with just a label. */
    public static void show(
            ActivityContext activity, CharSequence labelString, Runnable onDismissed) {
        show(activity, labelString, NO_ID, onDismissed, null);
    }

    /** Show a snackbar with a label and action. */
    public static <T extends Context & ActivityContext> void show(T activity, int labelStringResId,
            int actionStringResId, Runnable onDismissed, @Nullable Runnable onActionClicked) {
        show(
                activity,
                activity.getResources().getText(labelStringResId),
                actionStringResId,
                onDismissed,
                onActionClicked);
    }

    /** Show a snackbar with a label and action. */
    public static void show(ActivityContext activity, CharSequence labelString,
            int actionStringResId, Runnable onDismissed, @Nullable Runnable onActionClicked) {
        closeOpenViews(activity, true, TYPE_SNACKBAR);
        Snackbar snackbar = new Snackbar((Context) activity, null);
        // Set some properties here since inflated xml only contains the children.
        snackbar.setOrientation(HORIZONTAL);
        snackbar.setGravity(Gravity.CENTER_VERTICAL);
        Resources res = snackbar.getResources();
        snackbar.setElevation(res.getDimension(R.dimen.snackbar_elevation));
        int padding = res.getDimensionPixelSize(R.dimen.snackbar_padding);
        snackbar.setPadding(padding, padding, padding, padding);
        snackbar.setBackgroundResource(R.drawable.round_rect_primary);

        snackbar.mIsOpen = true;
        BaseDragLayer dragLayer = activity.getDragLayer();
        dragLayer.addView(snackbar);

        DragLayer.LayoutParams params = (DragLayer.LayoutParams) snackbar.getLayoutParams();
        params.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
        params.height = res.getDimensionPixelSize(R.dimen.snackbar_height);
        int maxMarginLeftRight = res.getDimensionPixelSize(R.dimen.snackbar_max_margin_left_right);
        int minMarginLeftRight = res.getDimensionPixelSize(R.dimen.snackbar_min_margin_left_right);
        int marginBottom = res.getDimensionPixelSize(R.dimen.snackbar_margin_bottom);
        int absoluteMaxWidth = res.getDimensionPixelSize(R.dimen.snackbar_max_width);
        Rect insets = activity.getDeviceProfile().getInsets();
        int maxWidth = Math.min(
                dragLayer.getWidth() - minMarginLeftRight * 2 - insets.left - insets.right,
                absoluteMaxWidth);
        int minWidth = Math.min(
                dragLayer.getWidth() - maxMarginLeftRight * 2 - insets.left - insets.right,
                absoluteMaxWidth);
        params.width = minWidth;
        DeviceProfile deviceProfile = activity.getDeviceProfile();
        params.setMargins(0, 0, 0, marginBottom
                + (deviceProfile.isTaskbarPresent
                ? deviceProfile.taskbarHeight + deviceProfile.getTaskbarOffsetY()
                : insets.bottom));

        TextView labelView = snackbar.findViewById(R.id.label);
        labelView.setText(labelString);

        TextView actionView = snackbar.findViewById(R.id.action);
        float actionWidth;
        if (actionStringResId != NO_ID) {
            String actionText = res.getString(actionStringResId);
            actionWidth = actionView.getPaint().measureText(actionText)
                    + actionView.getPaddingRight() + actionView.getPaddingLeft();
            actionView.setText(actionText);
            actionView.setOnClickListener(v -> {
                if (onActionClicked != null) {
                    onActionClicked.run();
                }
                snackbar.mOnDismissed = null;
                snackbar.close(true);
            });
        } else {
            actionWidth = 0;
            actionView.setVisibility(GONE);
        }

        int totalContentWidth = (int) (labelView.getPaint().measureText(labelString.toString())
                    + actionWidth)
                + labelView.getPaddingRight() + labelView.getPaddingLeft()
                + padding * 2;
        if (totalContentWidth > params.width) {
            // The text doesn't fit in our standard width so update width to accommodate.
            if (totalContentWidth <= maxWidth) {
                params.width = totalContentWidth;
            } else {
                // One line will be cut off, fallback to 2 lines and smaller font. (This should only
                // happen in some languages if system display and font size are set to largest.)
                int textHeight = res.getDimensionPixelSize(R.dimen.snackbar_content_height);
                float textSizePx = res.getDimension(R.dimen.snackbar_min_text_size);
                labelView.setLines(2);
                labelView.getLayoutParams().height = textHeight * 2;
                actionView.getLayoutParams().height = textHeight * 2;
                labelView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePx);
                actionView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePx);
                params.height += textHeight;
                params.width = maxWidth;
            }
        }

        snackbar.mOnDismissed = onDismissed;
        snackbar.setAlpha(0);
        snackbar.setScaleX(0.8f);
        snackbar.setScaleY(0.8f);
        snackbar.animate()
                .alpha(1f)
                .withLayer()
                .scaleX(1)
                .scaleY(1)
                .setDuration(SHOW_DURATION_MS)
                .setInterpolator(Interpolators.ACCELERATE_DECELERATE)
                .start();
        int timeout = AccessibilityManagerCompat.getRecommendedTimeoutMillis(snackbar.getContext(),
                TIMEOUT_DURATION_MS, FLAG_CONTENT_TEXT | FLAG_CONTENT_CONTROLS);
        snackbar.postDelayed(() -> snackbar.close(true), timeout);
    }

    @Override
    protected void handleClose(boolean animate) {
        if (mIsOpen) {
            if (animate) {
                animate().alpha(0f)
                        .withLayer()
                        .setStartDelay(0)
                        .setDuration(HIDE_DURATION_MS)
                        .setInterpolator(Interpolators.ACCELERATE)
                        .withEndAction(this::onClosed)
                        .start();
            } else {
                animate().cancel();
                onClosed();
            }
            mIsOpen = false;
        }
    }

    private void onClosed() {
        mActivity.getDragLayer().removeView(this);
        if (mOnDismissed != null) {
            mOnDismissed.run();
        }
    }

    @Override
    protected boolean isOfType(int type) {
        return (type & TYPE_SNACKBAR) != 0;
    }

    @Override
    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            BaseDragLayer dl = mActivity.getDragLayer();
            if (!dl.isEventOverView(this, ev)) {
                close(true);
            }
        }
        return false;
    }
}
