/*
 * Copyright (C) 2019 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.google.android.material.elevation;

import com.google.android.material.R;

import android.content.Context;
import android.graphics.Color;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.resources.MaterialAttributes;
import androidx.core.graphics.ColorUtils;
import android.view.View;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.internal.ViewUtils;

/** Utility for calculating elevation overlay alpha values and colors. */
public class ElevationOverlayProvider {

  private static final float FORMULA_MULTIPLIER = 4.5f;
  private static final float FORMULA_OFFSET = 2f;

  private final boolean elevationOverlayEnabled;
  private final int elevationOverlayColor;
  private final int colorSurface;
  private final float displayDensity;

  public ElevationOverlayProvider(@NonNull Context context) {
    this.elevationOverlayEnabled =
        MaterialAttributes.resolveBoolean(context, R.attr.elevationOverlayEnabled, false);
    this.elevationOverlayColor =
        MaterialColors.getColor(context, R.attr.elevationOverlayColor, Color.TRANSPARENT);
    this.colorSurface = MaterialColors.getColor(context, R.attr.colorSurface, Color.TRANSPARENT);
    this.displayDensity = context.getResources().getDisplayMetrics().density;
  }

  /** See {@link #compositeOverlayIfNeeded(int, float, View)}. */
  @ColorInt
  public int compositeOverlayWithThemeSurfaceColorIfNeeded(float elevation) {
    return compositeOverlayWithThemeSurfaceColorIfNeeded(elevation, null);
  }

  /**
   * Blends the calculated elevation overlay color (@see #compositeOverlayIfNeeded(int, float)) with
   * the current theme's color int value for {@code R.attr.colorSurface} if needed.
   *
   * <p>If the provided {@code view} is not null, the absolute elevation of it's parent will be
   * factored in when determining the overlay color.
   */
  @ColorInt
  public int compositeOverlayWithThemeSurfaceColorIfNeeded(
      float elevation, @Nullable View overlayView) {
    if (overlayView != null) {
      elevation += getParentAbsoluteElevation(overlayView);
    }
    return compositeOverlayIfNeeded(colorSurface, elevation);
  }

  /** See {@link #compositeOverlayIfNeeded(int, float, View)}. */
  @ColorInt
  public int compositeOverlayIfNeeded(@ColorInt int backgroundColor, float elevation) {
    return compositeOverlayIfNeeded(backgroundColor, elevation, null);
  }

  /**
   * Blends the calculated elevation overlay color (@see #compositeOverlay(int, float)) with the
   * {@code backgroundColor}, only if the current theme's {@code R.attr.elevationOverlayEnabled} is
   * true and the {@code backgroundColor} matches the theme's surface color ({@code
   * R.attr.colorSurface}); otherwise returns the {@code backgroundColor}.
   *
   * <p>If the provided {@code view} is not null, the absolute elevation of it's parent will be
   * factored in when determining the overlay color.
   */
  @ColorInt
  public int compositeOverlayIfNeeded(
      @ColorInt int backgroundColor, float elevation, @Nullable View overlayView) {
    if (overlayView != null) {
      elevation += getParentAbsoluteElevation(overlayView);
    }
    if (elevationOverlayEnabled && isThemeSurfaceColor(backgroundColor)) {
      return compositeOverlay(backgroundColor, elevation);
    } else {
      return backgroundColor;
    }
  }

  /** See {@link #compositeOverlay(int, float, View)}. */
  @ColorInt
  public int compositeOverlay(@ColorInt int backgroundColor, float elevation) {
    return compositeOverlay(backgroundColor, elevation, null);
  }

  /**
   * Blends the calculated elevation overlay color with the provided {@code backgroundColor}.
   *
   * <p>An alpha level is applied to the theme's {@code R.attr.elevationOverlayColor} by using a
   * formula that is based on the provided {@code elevation} value. If the provided {@code view} is
   * not null, the absolute elevation of it's parent will be factored in when determining the
   * overlay color.
   */
  @ColorInt
  public int compositeOverlay(
      @ColorInt int backgroundColor, float elevation, @Nullable View overlayView) {
    if (overlayView != null) {
      elevation += getParentAbsoluteElevation(overlayView);
    }
    float overlayAlpha = calculateOverlayAlphaFraction(elevation);
    return MaterialColors.layer(backgroundColor, elevationOverlayColor, overlayAlpha);
  }

  /**
   * Calculates the alpha value, between 0 and 255, that should be used with the elevation overlay
   * color, based on the provided {@code elevation} value.
   */
  public int calculateOverlayAlpha(float elevation) {
    return Math.round(calculateOverlayAlphaFraction(elevation) * 255);
  }

  /**
   * Calculates the alpha fraction, between 0 and 1, that should be used with the elevation overlay
   * color, based on the provided {@code elevation} value.
   */
  public float calculateOverlayAlphaFraction(float elevation) {
    if (displayDensity <= 0 || elevation <= 0) {
      return 0;
    }
    float elevationDp = elevation / displayDensity;
    float alphaFraction =
        (FORMULA_MULTIPLIER * (float) Math.log1p(elevationDp) + FORMULA_OFFSET) / 100;
    return Math.min(alphaFraction, 1);
  }

  /** Returns the current theme's boolean value for {@code R.attr.elevationOverlayEnabled}. */
  public boolean isThemeElevationOverlayEnabled() {
    return elevationOverlayEnabled;
  }

  /** Returns the current theme's color int value for {@code R.attr.elevationOverlayColor}. */
  @ColorInt
  public int getThemeElevationOverlayColor() {
    return elevationOverlayColor;
  }

  /** Returns the current theme's color int value for {@code R.attr.colorSurface}. */
  @ColorInt
  public int getThemeSurfaceColor() {
    return colorSurface;
  }

  /**
   * Returns the absolute elevation of the parent of the provided {@code view}, or in other words,
   * the sum of the elevations of all ancestors of the {@code view}.
   */
  public float getParentAbsoluteElevation(@NonNull View overlayView) {
    return ViewUtils.getParentAbsoluteElevation(overlayView);
  }

  private boolean isThemeSurfaceColor(@ColorInt int color) {
    return ColorUtils.setAlphaComponent(color, 255) == colorSurface;
  }
}

@@ -2455,33 +2455,40 @@
 provided %7B@code 
-v
+overlayV
 iew%7D is not null
@@ -2527,32 +2527,32 @@
  parent will
- be
 %0A   *
+ be
  factored in
@@ -3519,25 +3519,32 @@
 ided %7B@code 
-v
+overlayV
 iew%7D is not 
@@ -3595,16 +3595,16 @@
 will
- be
 %0A   *
+ be
  fac
@@ -4588,22 +4588,29 @@
 code
- v
+%0A   * overlayV
 iew%7D is
-%0A   *
  not
@@ -4670,32 +4670,37 @@
 factored in when
+%0A   *
  determining the
@@ -4699,21 +4699,16 @@
 ning the
-%0A   *
  overlay
@@ -6541,25 +6541,32 @@
 ided %7B@code 
-v
+overlayV
 iew%7D, or in 
@@ -6574,20 +6574,20 @@
 ther
+%0A   *
  words,
-%0A   *
  the
@@ -6640,17 +6640,24 @@
  %7B@code 
-v
+overlayV
 iew%7D.%0A  

Fix ElevationOverlayProvider javadoc reference to absolute elevation @code view