/*
 * 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.wm.shell.draganddrop;

import static com.android.wm.shell.animation.Interpolators.FAST_OUT_SLOW_IN;

import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Path;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;

import androidx.annotation.Nullable;

import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.R;

/**
 * Renders a drop zone area for items being dragged.
 */
public class DropZoneView extends FrameLayout {

    private static final float SPLASHSCREEN_ALPHA = 0.90f;
    private static final float HIGHLIGHT_ALPHA = 1f;
    private static final int MARGIN_ANIMATION_ENTER_DURATION = 400;
    private static final int MARGIN_ANIMATION_EXIT_DURATION = 250;

    private static final FloatProperty<DropZoneView> INSETS =
            new FloatProperty<DropZoneView>("insets") {
                @Override
                public void setValue(DropZoneView v, float percent) {
                    v.setMarginPercent(percent);
                }

                @Override
                public Float get(DropZoneView v) {
                    return v.getMarginPercent();
                }
            };

    private final Path mPath = new Path();
    private final float[] mContainerMargin = new float[4];
    private float mCornerRadius;
    private float mBottomInset;
    private boolean mIgnoreBottomMargin;
    private int mMarginColor; // i.e. color used for negative space like the container insets

    private boolean mShowingHighlight;
    private boolean mShowingSplash;
    private boolean mShowingMargin;

    private int mSplashScreenColor;
    private int mHighlightColor;

    private ObjectAnimator mBackgroundAnimator;
    private ObjectAnimator mMarginAnimator;
    private float mMarginPercent;

    // Renders a highlight or neutral transparent color
    private ColorDrawable mColorDrawable;
    // Renders the translucent splashscreen with the app icon in the middle
    private ImageView mSplashScreenView;
    // Renders the margin / insets around the dropzone container
    private MarginView mMarginView;

    public DropZoneView(Context context) {
        this(context, null);
    }

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

    public DropZoneView(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public DropZoneView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        setContainerMargin(0, 0, 0, 0); // make sure it's populated

        mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
        mMarginColor = getResources().getColor(R.color.taskbar_background_dark);
        int c = getResources().getColor(android.R.color.system_accent1_500);
        mHighlightColor =  Color.argb(HIGHLIGHT_ALPHA, Color.red(c), Color.green(c), Color.blue(c));
        mSplashScreenColor = Color.argb(SPLASHSCREEN_ALPHA, 0, 0, 0);
        mColorDrawable = new ColorDrawable();
        setBackgroundDrawable(mColorDrawable);

        final int iconSize = context.getResources().getDimensionPixelSize(R.dimen.split_icon_size);
        mSplashScreenView = new ImageView(context);
        mSplashScreenView.setScaleType(ImageView.ScaleType.FIT_CENTER);
        addView(mSplashScreenView,
                new FrameLayout.LayoutParams(iconSize, iconSize, Gravity.CENTER));
        mSplashScreenView.setAlpha(0f);

        mMarginView = new MarginView(context);
        addView(mMarginView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));
    }

    public void onThemeChange() {
        mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(getContext());
        mMarginColor = getResources().getColor(R.color.taskbar_background_dark);
        mHighlightColor = getResources().getColor(android.R.color.system_accent1_500);

        if (mMarginPercent > 0) {
            mMarginView.invalidate();
        }
    }

    /** Sets the desired margins around the drop zone container when fully showing. */
    public void setContainerMargin(float left, float top, float right, float bottom) {
        mContainerMargin[0] = left;
        mContainerMargin[1] = top;
        mContainerMargin[2] = right;
        mContainerMargin[3] = bottom;
        if (mMarginPercent > 0) {
            mMarginView.invalidate();
        }
    }

    /** Ignores the bottom margin provided by the insets. */
    public void setForceIgnoreBottomMargin(boolean ignoreBottomMargin) {
        mIgnoreBottomMargin = ignoreBottomMargin;
        if (mMarginPercent > 0) {
            mMarginView.invalidate();
        }
    }

    /** Sets the bottom inset so the drop zones are above bottom navigation. */
    public void setBottomInset(float bottom) {
        mBottomInset = bottom;
        ((LayoutParams) mSplashScreenView.getLayoutParams()).bottomMargin = (int) bottom;
        if (mMarginPercent > 0) {
            mMarginView.invalidate();
        }
    }

    /** Sets the color and icon to use for the splashscreen when shown. */
    public void setAppInfo(int color, Drawable appIcon) {
        Color c = Color.valueOf(color);
        mSplashScreenColor = Color.argb(SPLASHSCREEN_ALPHA, c.red(), c.green(), c.blue());
        mSplashScreenView.setImageDrawable(appIcon);
    }

    /** @return an active animator for this view if one exists. */
    @Nullable
    public Animator getAnimator() {
        if (mMarginAnimator != null && mMarginAnimator.isRunning()) {
            return mMarginAnimator;
        } else if (mBackgroundAnimator != null && mBackgroundAnimator.isRunning()) {
            return mBackgroundAnimator;
        }
        return null;
    }

    /** Animates between highlight and splashscreen depending on current state. */
    public void animateSwitch() {
        mShowingHighlight = !mShowingHighlight;
        mShowingSplash = !mShowingHighlight;
        final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor;
        animateBackground(mColorDrawable.getColor(), newColor);
        animateSplashScreenIcon();
    }

    /** Animates the highlight indicating the zone is hovered on or not. */
    public void setShowingHighlight(boolean showingHighlight) {
        mShowingHighlight = showingHighlight;
        mShowingSplash = !mShowingHighlight;
        final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor;
        animateBackground(Color.TRANSPARENT, newColor);
        animateSplashScreenIcon();
    }

    /** Animates the margins around the drop zone to show or hide. */
    public void setShowingMargin(boolean visible) {
        if (mShowingMargin != visible) {
            mShowingMargin = visible;
            animateMarginToState();
        }
        if (!mShowingMargin) {
            mShowingHighlight = false;
            mShowingSplash = false;
            animateBackground(mColorDrawable.getColor(), Color.TRANSPARENT);
            animateSplashScreenIcon();
        }
    }

    private void animateBackground(int startColor, int endColor) {
        if (mBackgroundAnimator != null) {
            mBackgroundAnimator.cancel();
        }
        mBackgroundAnimator = ObjectAnimator.ofArgb(mColorDrawable,
                "color",
                startColor,
                endColor);
        if (!mShowingSplash && !mShowingHighlight) {
            mBackgroundAnimator.setInterpolator(FAST_OUT_SLOW_IN);
        }
        mBackgroundAnimator.start();
    }

    private void animateSplashScreenIcon() {
        mSplashScreenView.animate().alpha(mShowingSplash ? 1f : 0f).start();
    }

    private void animateMarginToState() {
        if (mMarginAnimator != null) {
            mMarginAnimator.cancel();
        }
        mMarginAnimator = ObjectAnimator.ofFloat(this, INSETS,
                mMarginPercent,
                mShowingMargin ? 1f : 0f);
        mMarginAnimator.setInterpolator(FAST_OUT_SLOW_IN);
        mMarginAnimator.setDuration(mShowingMargin
                ? MARGIN_ANIMATION_ENTER_DURATION
                : MARGIN_ANIMATION_EXIT_DURATION);
        mMarginAnimator.start();
    }

    private void setMarginPercent(float percent) {
        if (percent != mMarginPercent) {
            mMarginPercent = percent;
            mMarginView.invalidate();
        }
    }

    private float getMarginPercent() {
        return mMarginPercent;
    }

    /** Simple view that draws a rounded rect margin around its contents. **/
    private class MarginView extends View {

        MarginView(Context context) {
            super(context);
        }

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            mPath.reset();
            mPath.addRoundRect(mContainerMargin[0] * mMarginPercent,
                    mContainerMargin[1] * mMarginPercent,
                    getWidth() - (mContainerMargin[2] * mMarginPercent),
                    getHeight() - (mContainerMargin[3] * mMarginPercent)
                            - (mIgnoreBottomMargin ? 0 : mBottomInset),
                    mCornerRadius * mMarginPercent,
                    mCornerRadius * mMarginPercent,
                    Path.Direction.CW);
            mPath.setFillType(Path.FillType.INVERSE_EVEN_ODD);
            canvas.clipPath(mPath);
            canvas.drawColor(mMarginColor);
        }
    }
}
