/*
 * 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.dragndrop;

import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;

import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Path.Direction;
import android.graphics.Picture;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.Log;

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

import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.views.ActivityContext;

/**
 * {@link AdaptiveIconDrawable} representation of a {@link FolderIcon}
 */
@TargetApi(Build.VERSION_CODES.O)
public class FolderAdaptiveIcon extends AdaptiveIconDrawable {
    private static final String TAG = "FolderAdaptiveIcon";

    private final Drawable mBadge;
    private final Path mMask;
    private final ConstantState mConstantState;
    private static final Rect sTmpRect = new Rect();

    private FolderAdaptiveIcon(Drawable bg, Drawable fg, Drawable badge, Path mask) {
        super(bg, fg);
        mBadge = badge;
        mMask = mask;

        mConstantState = new MyConstantState(bg.getConstantState(), fg.getConstantState(),
                badge.getConstantState(), mask);
    }

    @Override
    public Path getIconMask() {
        return mMask;
    }

    public Drawable getBadge() {
        return mBadge;
    }

    public static @Nullable FolderAdaptiveIcon createFolderAdaptiveIcon(
            ActivityContext activity, int folderId, Point size) {
        Preconditions.assertNonUiThread();

        // assume square
        if (size.x != size.y) {
            return null;
        }
        int requestedSize = size.x;

        // Only use the size actually needed for drawing the folder icon
        int drawingSize = activity.getDeviceProfile().folderIconSizePx;
        int foregroundSize = Math.max(requestedSize, drawingSize);
        float shift = foregroundSize - requestedSize;

        Picture background = new Picture();
        Picture foreground = new Picture();
        Picture badge = new Picture();

        Canvas bgCanvas = background.beginRecording(requestedSize, requestedSize);
        Canvas badgeCanvas = badge.beginRecording(requestedSize, requestedSize);

        Canvas fgCanvas = foreground.beginRecording(foregroundSize, foregroundSize);
        fgCanvas.translate(shift, shift);

        // Do not clip the folder drawing since the icon previews extend outside the background.
        Path mask = new Path();
        mask.addRect(-shift, -shift, requestedSize + shift, requestedSize + shift,
                Direction.CCW);

        // Initialize the actual draw commands on the UI thread to avoid race conditions with
        // FolderIcon draw pass
        try {
            MAIN_EXECUTOR.submit(() -> {
                FolderIcon icon = activity.findFolderIcon(folderId);
                if (icon == null) {
                    throw new IllegalArgumentException("Folder not found with id: " + folderId);
                }
                initLayersOnUiThread(icon, requestedSize, bgCanvas, fgCanvas, badgeCanvas);
            }).get();
        } catch (Exception e) {
            Log.e(TAG, "Unable to create folder icon", e);
            return null;
        } finally {
            background.endRecording();
            foreground.endRecording();
            badge.endRecording();
        }

        // Only convert foreground to a bitmap as it can contain multiple draw commands. Other
        // layers either draw a nothing or a single draw call.
        Bitmap fgBitmap = Bitmap.createBitmap(foreground);
        Paint foregroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        // Do not use PictureDrawable as it moves the picture to the canvas bounds, whereas we want
        // to draw it at (0,0)
        return new FolderAdaptiveIcon(
                new BitmapRendererDrawable(c -> c.drawPicture(background)),
                new BitmapRendererDrawable(
                        c -> c.drawBitmap(fgBitmap, -shift, -shift, foregroundPaint)),
                new BitmapRendererDrawable(c -> c.drawPicture(badge)),
                mask);
    }

    @UiThread
    private static void initLayersOnUiThread(FolderIcon icon, int size,
            Canvas backgroundCanvas, Canvas foregroundCanvas, Canvas badgeCanvas) {
        icon.getPreviewBounds(sTmpRect);
        final int previewSize = sTmpRect.width();

        final int margin = (size - previewSize) / 2;
        final float previewShiftX = -sTmpRect.left + margin;
        final float previewShiftY = -sTmpRect.top + margin;

        // Initialize badge, which consists of the outline stroke, shadow and dot; these
        // must be rendered above the foreground
        badgeCanvas.save();
        badgeCanvas.translate(previewShiftX, previewShiftY);
        icon.drawDot(badgeCanvas);
        badgeCanvas.restore();

        // Draw foreground
        foregroundCanvas.save();
        foregroundCanvas.translate(previewShiftX, previewShiftY);
        icon.getPreviewItemManager().draw(foregroundCanvas);
        foregroundCanvas.restore();

        // Draw background
        backgroundCanvas.save();
        backgroundCanvas.translate(previewShiftX, previewShiftY);
        icon.getFolderBackground().drawBackground(backgroundCanvas);
        backgroundCanvas.restore();
    }

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

    private static class MyConstantState extends ConstantState {
        private final ConstantState mBg;
        private final ConstantState mFg;
        private final ConstantState mBadge;
        private final Path mMask;

        MyConstantState(ConstantState bg, ConstantState fg, ConstantState badge, Path mask) {
            mBg = bg;
            mFg = fg;
            mBadge = badge;
            mMask = mask;
        }

        @Override
        public Drawable newDrawable() {
            return new FolderAdaptiveIcon(mBg.newDrawable(), mFg.newDrawable(),
                    mBadge.newDrawable(), mMask);
        }

        @Override
        public int getChangingConfigurations() {
            return mBg.getChangingConfigurations() & mFg.getChangingConfigurations()
                    & mBadge.getChangingConfigurations();
        }
    }

    private static class BitmapRendererDrawable extends Drawable {

        private final BitmapRenderer mRenderer;

        BitmapRendererDrawable(BitmapRenderer renderer) {
            mRenderer = renderer;
        }

        @Override
        public void draw(Canvas canvas) {
            mRenderer.draw(canvas);
        }

        @Override
        public void setAlpha(int i) { }

        @Override
        public void setColorFilter(ColorFilter colorFilter) {  }

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

        @Override
        public ConstantState getConstantState() {
            return new MyConstantState(mRenderer);
        }

        private static class MyConstantState extends ConstantState {
            private final BitmapRenderer mRenderer;

            MyConstantState(BitmapRenderer renderer) {
                mRenderer = renderer;
            }

            @Override
            public Drawable newDrawable() {
                return new BitmapRendererDrawable(mRenderer);
            }

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