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

import android.annotation.TargetApi;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;

import com.android.launcher3.R;
import com.android.launcher3.Utilities;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;

/**
 * A drawable which adds shadow around a child drawable.
 */
@TargetApi(Build.VERSION_CODES.O)
public class ShadowDrawable extends Drawable {

    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);

    private final ShadowDrawableState mState;

    @SuppressWarnings("unused")
    public ShadowDrawable() {
        this(new ShadowDrawableState());
    }

    private ShadowDrawable(ShadowDrawableState state) {
        mState = state;
    }

    @Override
    public void draw(Canvas canvas) {
        Rect bounds = getBounds();
        if (bounds.isEmpty()) {
            return;
        }
        if (mState.mLastDrawnBitmap == null) {
            regenerateBitmapCache();
        }
        canvas.drawBitmap(mState.mLastDrawnBitmap, null, bounds, mPaint);
    }

    @Override
    public void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
        invalidateSelf();
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        mPaint.setColorFilter(colorFilter);
        invalidateSelf();
    }

    @Override
    public ConstantState getConstantState() {
        return mState;
    }

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

    @Override
    public int getIntrinsicHeight() {
        return mState.mIntrinsicHeight;
    }

    @Override
    public int getIntrinsicWidth() {
        return mState.mIntrinsicWidth;
    }

    @Override
    public boolean canApplyTheme() {
        return mState.canApplyTheme();
    }

    @Override
    public void applyTheme(Resources.Theme t) {
        TypedArray ta = t.obtainStyledAttributes(new int[] {R.attr.isWorkspaceDarkText});
        boolean isDark = ta.getBoolean(0, false);
        ta.recycle();
        if (mState.mIsDark != isDark) {
            mState.mIsDark = isDark;
            mState.mLastDrawnBitmap = null;
            invalidateSelf();
        }
    }

    private void regenerateBitmapCache() {
        Bitmap bitmap = Bitmap.createBitmap(mState.mIntrinsicWidth, mState.mIntrinsicHeight,
                Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);

        // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared.
        Drawable d = mState.mChildState.newDrawable().mutate();
        d.setBounds(mState.mShadowSize, mState.mShadowSize,
                mState.mIntrinsicWidth - mState.mShadowSize,
                mState.mIntrinsicHeight - mState.mShadowSize);
        d.setTint(mState.mIsDark ? mState.mDarkTintColor : Color.WHITE);
        d.draw(canvas);

        // Do not draw shadow on dark theme
        if (!mState.mIsDark) {
            Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
            paint.setMaskFilter(new BlurMaskFilter(mState.mShadowSize, BlurMaskFilter.Blur.NORMAL));
            int[] offset = new int[2];
            Bitmap shadow = bitmap.extractAlpha(paint, offset);

            paint.setMaskFilter(null);
            paint.setColor(mState.mShadowColor);
            bitmap.eraseColor(Color.TRANSPARENT);
            canvas.drawBitmap(shadow, offset[0], offset[1], paint);
            d.draw(canvas);
        }

        if (Utilities.ATLEAST_OREO) {
            bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false);
        }
        mState.mLastDrawnBitmap = bitmap;
    }

    @Override
    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs,
            Resources.Theme theme) throws XmlPullParserException, IOException {
        super.inflate(r, parser, attrs, theme);

        final TypedArray a = theme == null
                ? r.obtainAttributes(attrs, R.styleable.ShadowDrawable)
                : theme.obtainStyledAttributes(attrs, R.styleable.ShadowDrawable, 0, 0);
        try {
            Drawable d = a.getDrawable(R.styleable.ShadowDrawable_android_src);
            if (d == null) {
                throw new XmlPullParserException("missing src attribute");
            }
            mState.mShadowColor = a.getColor(
                    R.styleable.ShadowDrawable_android_shadowColor, Color.BLACK);
            mState.mShadowSize = a.getDimensionPixelSize(
                    R.styleable.ShadowDrawable_android_elevation, 0);
            mState.mDarkTintColor = a.getColor(
                    R.styleable.ShadowDrawable_darkTintColor, Color.BLACK);

            mState.mIntrinsicHeight = d.getIntrinsicHeight() + 2 * mState.mShadowSize;
            mState.mIntrinsicWidth = d.getIntrinsicWidth() + 2 * mState.mShadowSize;
            mState.mChangingConfigurations = d.getChangingConfigurations();

            mState.mChildState = d.getConstantState();
        } finally {
            a.recycle();
        }
    }

    private static class ShadowDrawableState extends ConstantState {

        int mChangingConfigurations;
        int mIntrinsicWidth;
        int mIntrinsicHeight;

        int mShadowColor;
        int mShadowSize;
        int mDarkTintColor;

        boolean mIsDark;
        Bitmap mLastDrawnBitmap;
        ConstantState mChildState;

        @Override
        public Drawable newDrawable() {
            return new ShadowDrawable(this);
        }

        @Override
        public int getChangingConfigurations() {
            return mChangingConfigurations;
        }

        @Override
        public boolean canApplyTheme() {
            return true;
        }
    }
}