/*
 * Copyright (C) 2023 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.tv.privacy;

import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.util.MathUtils;
import android.view.Gravity;

import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.systemui.tv.res.R;

/**
 * Drawable that can go from being the background of the privacy icons to a small dot.
 * The icons are not included.
 */
public class PrivacyChipDrawable extends Drawable {
    private static final String TAG = PrivacyChipDrawable.class.getSimpleName();
    private static final boolean DEBUG = false;

    private final Paint mChipPaint;
    private final Paint mBgPaint;
    private final Rect mTmpRect = new Rect();
    private final Rect mBgRect = new Rect();
    private final RectF mTmpRectF = new RectF();
    private final Path mPath = new Path();
    private final Animator mCollapse;
    private final Animator mExpand;
    private final int mLayoutDirection;
    private final int mBgWidth;
    private final int mBgHeight;
    private final int mBgRadius;
    private final int mDotSize;
    private final float mExpandedChipRadius;
    private final float mCollapsedChipRadius;

    private final boolean mCollapseToDot;

    private boolean mIsExpanded = true;
    private float mCollapseProgress = 0f;

    public PrivacyChipDrawable(Context context, int chipColorRes, boolean collapseToDot) {
        mCollapseToDot = collapseToDot;

        mChipPaint = new Paint();
        mChipPaint.setStyle(Paint.Style.FILL);
        mChipPaint.setColor(context.getColor(chipColorRes));
        mChipPaint.setFlags(Paint.ANTI_ALIAS_FLAG);

        mBgPaint = new Paint();
        mBgPaint.setStyle(Paint.Style.FILL);
        mBgPaint.setColor(context.getColor(R.color.privacy_chip_dot_bg_tint));
        mBgPaint.setFlags(Paint.ANTI_ALIAS_FLAG);

        Resources res = context.getResources();
        mLayoutDirection = res.getConfiguration().getLayoutDirection();
        mBgWidth = res.getDimensionPixelSize(R.dimen.privacy_chip_dot_bg_width);
        mBgHeight = res.getDimensionPixelSize(R.dimen.privacy_chip_dot_bg_height);
        mBgRadius = res.getDimensionPixelSize(R.dimen.privacy_chip_dot_bg_radius);
        mDotSize = res.getDimensionPixelSize(R.dimen.privacy_chip_dot_size);

        mExpandedChipRadius = res.getDimensionPixelSize(R.dimen.privacy_chip_radius);
        mCollapsedChipRadius = res.getDimensionPixelSize(R.dimen.privacy_chip_dot_radius);

        mExpand = AnimatorInflater.loadAnimator(context, R.anim.privacy_chip_expand);
        mExpand.setTarget(this);
        mCollapse = AnimatorInflater.loadAnimator(context, R.anim.privacy_chip_collapse);
        mCollapse.setTarget(this);
    }

    /**
     * @return how far the chip is currently collapsed.
     * @see #setCollapseProgress(float)
     */
    @Keep
    public float getCollapseProgress() {
        return mCollapseProgress;
    }

    /**
     * Sets the collapsing progress of the chip to its collapsed state.
     * @param pct How far the chip is collapsed, in the range 0-1.
     *            0=fully expanded, 1=fully collapsed.
     */
    @Keep
    public void setCollapseProgress(float pct) {
        mCollapseProgress = pct;
        invalidateSelf();
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        if (mCollapseProgress > 0f) {
            // draw background
            getBackgroundBounds(mBgRect);
            mTmpRectF.set(mBgRect);
            canvas.drawRoundRect(mTmpRectF, mBgRadius, mBgRadius, mBgPaint);
        }

        getForegroundBounds(mTmpRectF);
        float radius = MathUtils.lerp(
                mExpandedChipRadius,
                mCollapseToDot ? mCollapsedChipRadius : mBgRadius,
                mCollapseProgress);

        canvas.drawRoundRect(mTmpRectF, radius, radius, mChipPaint);
    }

    private void getBackgroundBounds(Rect out) {
        Rect bounds = getBounds();
        Gravity.apply(Gravity.END, mBgWidth, mBgHeight, bounds, out, mLayoutDirection);
    }

    private void getCollapsedForegroundBounds(Rect out) {
        Rect bounds = getBounds();
        getBackgroundBounds(mBgRect);
        if (mCollapseToDot) {
            Gravity.apply(Gravity.CENTER, mDotSize, mDotSize, mBgRect, out);
        } else {
            out.set(bounds.left, mBgRect.top, bounds.right, mBgRect.bottom);
        }
    }

    private void getForegroundBounds(RectF out) {
        Rect bounds = getBounds();
        getCollapsedForegroundBounds(mTmpRect);
        lerpRect(bounds, mTmpRect, mCollapseProgress, out);
    }

    private void lerpRect(Rect start, Rect stop, float amount, RectF out) {
        float left = MathUtils.lerp(start.left, stop.left, amount);
        float top = MathUtils.lerp(start.top, stop.top, amount);
        float right = MathUtils.lerp(start.right, stop.right, amount);
        float bottom = MathUtils.lerp(start.bottom, stop.bottom, amount);
        out.set(left, top, right, bottom);
    }

    /**
     * Clips the given canvas to this chip's foreground shape.
     * @param canvas Canvas to clip.
     */
    public void clipToForeground(Canvas canvas) {
        getForegroundBounds(mTmpRectF);
        float radius = MathUtils.lerp(
                mExpandedChipRadius,
                mCollapseToDot ? mCollapsedChipRadius : mBgRadius,
                mCollapseProgress);

        mPath.reset();
        mPath.addRoundRect(mTmpRectF, radius, radius, Path.Direction.CW);
        canvas.clipPath(mPath);
    }

    @Override
    protected void onBoundsChange(@NonNull Rect bounds) {
        super.onBoundsChange(bounds);
        invalidateSelf();
    }

    @Override
    public void setAlpha(int alpha) {
        mChipPaint.setAlpha(alpha);
        mBgPaint.setAlpha(alpha);
    }

    /**
     * Transitions to a full chip.
     *
     * @param animate Whether to animate the change to a full chip, or expand instantly.
     */
    public void expand(boolean animate) {
        if (DEBUG) Log.d(TAG, "expanding");
        if (mIsExpanded) {
            return;
        }
        mIsExpanded = true;
        if (animate) {
            mCollapse.cancel();
            mExpand.start();
        } else {
            mCollapseProgress = 0f;
            invalidateSelf();
        }
    }

    /**
     * Starts the animation to a dot.
     */
    public void collapse() {
        if (DEBUG) Log.d(TAG, "collapsing");
        if (!mIsExpanded) {
            return;
        }
        mIsExpanded = false;
        mExpand.cancel();
        mCollapse.start();
    }

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {
        // no-op
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }
}
