/*
 * Copyright 2017-2020 Pranav Pandey
 *
 * 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.pranavpandey.android.dynamic.utils;

import android.annotation.TargetApi;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.view.View;

import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.drawable.DrawableCompat;

/**
 * Helper class to perform {@link Drawable} operations.
 */
public class DynamicDrawableUtils {

    /**
     * Set background of a given view in an efficient way by detecting the Android SDK
     * at runtime.
     *
     * @param view View to set the background.
     * @param drawable Background drawable for the view.
     *
     * @see DynamicSdkUtils
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public static void setBackground(@NonNull View view, @Nullable Drawable drawable) {
        if (DynamicSdkUtils.is16()) {
            view.setBackground(drawable);
        } else {
            view.setBackgroundDrawable(drawable);
        }
    }

    /**
     * Apply color filter and return the mutated drawable so that, all other references
     * do not change.
     *
     * @param drawable The drawable to be colorized.
     * @param wrap {@code true} to {@code wrap} the drawable so that it may be used for
     *             tinting across the different API levels.
     * @param colorFilter The color filter to be applied on the drawable.
     *
     * @return The drawable after applying the color filter.
     *
     * @see Drawable#setColorFilter(ColorFilter)
     * @see ColorFilter
     */
    public static @Nullable Drawable colorizeDrawable(@Nullable Drawable drawable,
            boolean wrap, @NonNull ColorFilter colorFilter) {
        if (drawable != null) {
            if (wrap) {
                drawable = drawable.mutate();
            }

            drawable.setColorFilter(colorFilter);
            drawable.invalidateSelf();
        }

        return drawable;
    }

    /**
     * Colorize and return the mutated drawable so that, all other references do not change.
     *
     * @param drawable The drawable to be colorized.
     * @param color The color to colorize the drawable.
     * @param wrap {@code true} to {@code wrap} the drawable so that it may be used for
     *             tinting across the different API levels.
     * @param mode The porter duff mode.
     *
     * @return The drawable after applying the color filter.
     *
     * @see Drawable#setColorFilter(int, PorterDuff.Mode)
     * @see PorterDuff.Mode
     */
    public static @Nullable Drawable colorizeDrawable(@Nullable Drawable drawable,
            boolean wrap, @ColorInt int color, @Nullable PorterDuff.Mode mode) {
        if (drawable != null) {
            if (mode == null) {
                mode = PorterDuff.Mode.SRC_IN;
            }

            // Handle issue with layer drawables.
            if (DynamicSdkUtils.is21(true)) {
                if (wrap) {
                    drawable = drawable.mutate();
                    drawable.setColorFilter(color, mode);
                }
            } else {
                if (wrap) {
                    drawable = DrawableCompat.wrap(drawable.mutate());
                }

                DrawableCompat.setTintMode(drawable, mode);
                DrawableCompat.setTint(drawable, color);
            }

            drawable.invalidateSelf();
        }

        return drawable;
    }

    /**
     * Colorize and return the mutated drawable so that, all other references do not change.
     *
     * @param drawable The drawable to be colorized.
     * @param color The color to colorize the drawable.
     * @param mode The porter duff mode.
     *
     * @return The drawable after applying the color filter.
     *
     * @see Drawable#setColorFilter(int, PorterDuff.Mode)
     * @see PorterDuff
     */
    public static @Nullable Drawable colorizeDrawable(@Nullable Drawable drawable,
            @ColorInt int color, @Nullable PorterDuff.Mode mode) {
        return colorizeDrawable(drawable, true, color, mode);
    }

    /**
     * Colorize and return the mutated drawable so that, all other references do not change.
     *
     * @param drawable The drawable to be colorized.
     * @param wrap {@code true} to {@code wrap} the drawable so that it may be used for
     *             tinting across the different API levels.
     * @param color The color to colorize the drawable.
     *
     * @return The drawable after applying the color filter.
     *
     * @see Drawable#setColorFilter(int, PorterDuff.Mode)
     * @see PorterDuff.Mode#SRC_IN
     */
    public static @Nullable Drawable colorizeDrawable(@Nullable Drawable drawable,
            boolean wrap, @ColorInt int color) {
        return colorizeDrawable(drawable, wrap, color, PorterDuff.Mode.SRC_IN);
    }

    /**
     * Colorize and return the mutated drawable so that, all other references do not change.
     *
     * @param drawable The drawable to be colorized.
     * @param color The color to colorize the drawable.
     *
     * @return The drawable after applying the color filter.
     */
    public static @Nullable Drawable colorizeDrawable(
            @Nullable Drawable drawable, @ColorInt int color) {
        return colorizeDrawable(drawable, true, color);
    }

    /**
     * Apply color filter and return the mutated drawable so that, all other references
     * do not change.
     *
     * @param drawable The drawable to be colorized.
     * @param colorFilter Color filter to be applied on the drawable.
     *
     * @return The drawable after applying the color filter.
     *
     * @see Drawable#setColorFilter(ColorFilter)
     * @see ColorFilter
     */
    public static @Nullable Drawable colorizeDrawable(@Nullable Drawable drawable,
            @NonNull ColorFilter colorFilter) {
        return colorizeDrawable(drawable, true, colorFilter);
    }

    /**
     * Get a gradient drawable according to the supplied drawable.
     *
     * @param width The width in dip for the drawable.
     * @param height The height in dip for the drawable.
     * @param drawable The drawable drawable to create the gradient drawable.
     * @param color The color for the drawable.
     *
     * @return The gradient drawable according to the supplied parameters.
     */
    public static @NonNull Drawable getCornerDrawable(int width, int height,
            @NonNull GradientDrawable drawable, @ColorInt int color) {
        drawable.setColor(color);

        if (width > 0 && height > 0) {
            drawable.setSize(DynamicUnitUtils.convertDpToPixels(width),
                    DynamicUnitUtils.convertDpToPixels(height));
        }

        return drawable;
    }

    /**
     * Get a gradient drawable according to the supplied drawable.
     *
     * @param width The width in dip for the drawable.
     * @param height The height in dip for the drawable.
     * @param drawable The drawable drawable to create the gradient drawable.
     *
     * @return The gradient drawable according to the supplied parameters.
     */
    public static @NonNull Drawable getCornerDrawable(int width,
            int height, @NonNull GradientDrawable drawable) {
        return getCornerDrawable(width, height, drawable, Color.WHITE);
    }

    /**
     * Get a gradient drawable according to the supplied drawable.
     * 
     * @param drawable The drawable drawable to create the gradient drawable.
     * @param color The color for the drawable.
     *
     * @return The gradient drawable according to the supplied parameters.
     */
    public static @NonNull Drawable getCornerDrawable(
            @NonNull GradientDrawable drawable, @ColorInt int color) {
        return getCornerDrawable(0, 0, drawable, color);
    }

    /**
     * Get a gradient drawable according to the supplied drawable.
     *
     * @param drawable The drawable drawable to create the gradient drawable.
     *
     * @return The gradient drawable according to the supplied parameters.
     */
    public static @NonNull Drawable getCornerDrawable(@NonNull GradientDrawable drawable) {
        return getCornerDrawable(0, 0, drawable, Color.WHITE);
    }

    /**
     * Get a gradient drawable according to the corner radius.
     *
     * @param width The width in dip for the drawable.
     * @param height The height in dip for the drawable.
     * @param cornerRadius The corner size in dip for the drawable.
     * @param color The color for the drawable.
     *
     * @return The gradient drawable according to the supplied parameters.
     */
    public static @NonNull Drawable getCornerDrawable(int width, int height,
            float cornerRadius, @ColorInt int color) {
        GradientDrawable drawable = new GradientDrawable();
        drawable.setCornerRadius(DynamicUnitUtils.convertDpToPixels(cornerRadius));
        
        return getCornerDrawable(width, height, drawable, color);
    }

    /**
     * Get a gradient drawable according to the corner radius.
     *
     * @param width The width in dip for the drawable.
     * @param height The height in dip for the drawable.
     * @param cornerRadius The corner size in dip for the drawable.
     *
     * @return The gradient drawable according to the supplied parameters.
     */
    public static @NonNull Drawable getCornerDrawable(int width, int height, float cornerRadius) {
        return getCornerDrawable(width, height, cornerRadius, Color.WHITE);
    }

    /**
     * Get a gradient drawable according to the corner radius.
     *
     * @param cornerRadius The corner size in dip for the drawable.
     * @param color The color for the drawable.
     *
     * @return The gradient drawable according to the supplied parameters.
     */
    public static @NonNull Drawable getCornerDrawable(float cornerRadius, @ColorInt int color) {
        return getCornerDrawable(0, 0, cornerRadius, color);
    }

    /**
     * Get a gradient drawable according to the corner radius.
     *
     * @param cornerRadius The corner size in dip for the drawable.
     *
     * @return The gradient drawable according to the supplied parameters.
     */
    public static @NonNull Drawable getCornerDrawable(float cornerRadius) {
        return getCornerDrawable(0, 0, cornerRadius, Color.WHITE);
    }
}