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

import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Rect;

import androidx.annotation.WorkerThread;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;

/**
 * Collection of static utility methods for decoding and processing Bitmaps.
 */
public class BitmapUtils {
    private static final float DEFAULT_CENTER_ALIGNMENT = 0.5f;

    // Suppress default constructor for noninstantiability.
    private BitmapUtils() {
        throw new AssertionError();
    }

    /**
     * Calculates the highest subsampling factor to scale the source image to the target view without
     * losing visible quality. Final result is based on powers of 2 because it should be set as
     * BitmapOptions#inSampleSize.
     *
     * @param srcWidth     Width of source image.
     * @param srcHeight    Height of source image.
     * @param targetWidth  Width of target view.
     * @param targetHeight Height of target view.
     * @return Highest subsampling factor as a power of 2.
     */
    public static int calculateInSampleSize(
            int srcWidth, int srcHeight, int targetWidth, int targetHeight) {
        int shift = 0;
        int halfHeight = srcHeight / 2;
        int halfWidth = srcWidth / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both the result
        // bitmap's height and width at least as large as the target height and width.
        while (((halfHeight >> shift) >= targetHeight) && ((halfWidth >> shift) >= targetWidth)) {
            shift++;
        }

        return 1 << shift;
    }

    /**
     * Generates a hash code for the given bitmap. Computation starts with a nonzero prime number,
     * then for the integer values of height, width, and a selection of pixel colors, multiplies the
     * result by 31 and adds said integer value. Multiply by 31 because it is prime and conveniently 1
     * less than 32 which is 2 ^ 5, allowing the VM to replace multiplication by a bit shift and
     * subtraction for performance.
     * <p>
     * This method should be called off the UI thread.
     */
    public static long generateHashCode(Bitmap bitmap) {
        long result = 17;

        int width = bitmap.getWidth();
        int height = bitmap.getHeight();

        result = 31 * result + width;
        result = 31 * result + height;

        // Traverse pixels exponentially so that hash code generation scales well with large images.
        for (int x = 0; x < width; x = x * 2 + 1) {
            for (int y = 0; y < height; y = y * 2 + 1) {
                result = 31 * result + bitmap.getPixel(x, y);
            }
        }

        return result;
    }

    /**
     * Calculates horizontal alignment of the rect within the supplied dimensions.
     *
     * @return A float value between 0 and 1 specifying horizontal alignment; 0 for left-aligned, 0.5
     * for horizontal center-aligned, and 1 for right-aligned.
     */
    public static float calculateHorizontalAlignment(Point dimensions, Rect rect) {
        int paddingLeft = rect.left;
        int paddingRight = dimensions.x - rect.right;
        int totalHorizontalPadding = paddingLeft + paddingRight;
        // Zero horizontal padding means that there is no room to crop horizontally so we just fall
        // back to a default center-alignment value.
        return (totalHorizontalPadding == 0)
                ? DEFAULT_CENTER_ALIGNMENT
                : paddingLeft / ((float) paddingLeft + paddingRight);
    }

    /**
     * Calculates vertical alignment of the rect within the supplied dimensions.
     *
     * @return A float value between 0 and 1 specifying vertical alignment; 0 for top-aligned, 0.5 for
     * vertical center-aligned, and 1 for bottom-aligned.
     */
    public static float calculateVerticalAlignment(Point dimensions, Rect rect) {
        int paddingTop = rect.top;
        int paddingBottom = dimensions.y - rect.bottom;
        int totalVerticalPadding = paddingTop + paddingBottom;
        // Zero vertical padding means that there is no room to crop vertically so we just fall back to
        // a default center-alignment value.
        return (totalVerticalPadding == 0)
                ? DEFAULT_CENTER_ALIGNMENT
                : paddingTop / ((float) paddingTop + paddingBottom);
    }

    /**
     * Converts the bitmap into an input stream with 100% quality.
     *
     * Should not be called from the main thread.
     */
    @WorkerThread
    public static InputStream bitmapToInputStream(Bitmap bitmap) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)) {
            return new ByteArrayInputStream(outputStream.toByteArray());
        } else {
            return null;
        }
    }
}
