package com.bumptech.glide.load.resource.bitmap;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import java.security.MessageDigest;

/**
 * Applies a {@link Bitmap} {@link Transformation} to {@link Drawable}s by first attempting to
 * convert the {@link Drawable} to a {@link Bitmap} and then running the {@link Transformation}
 * on the converted {@link Bitmap}.
 *
 * <p>This class is relatively efficient for {@link BitmapDrawable} where the {@link Bitmap} is
 * readily accessible. For non-{@link Bitmap} based {@link Drawable}s, this class must first try to
 * draw the {@link Drawable} to a {@link Bitmap} using {@link android.graphics.Canvas}, which is
 * less efficient. {@link Drawable}s that implement {@link android.graphics.drawable.Animatable}
 * will fail with an exception. {@link Drawable}s that return <= 0 for
 * {@link Drawable#getIntrinsicHeight()} and/or {@link Drawable#getIntrinsicWidth()} will fail
 * with an exception if the requested size is
 * {@link com.bumptech.glide.request.target.Target#SIZE_ORIGINAL}. {@link Drawable}s without
 * intrinsic dimensions are drawn using the dimensions provided in
 * {@link Transformation#transform(Context, Resource, int, int)}. As a result, they may be
 * transformed incorrectly or in unexpected ways.
 */
public class DrawableTransformation implements Transformation<Drawable> {

  private final Transformation<Bitmap> wrapped;
  private final boolean isRequired;

  public DrawableTransformation(Transformation<Bitmap> wrapped, boolean isRequired) {
    this.wrapped = wrapped;
    this.isRequired = isRequired;
  }

  @SuppressWarnings("unchecked")
  public Transformation<BitmapDrawable> asBitmapDrawable() {
    return (Transformation<BitmapDrawable>) (Transformation<?>) this;
  }

  @NonNull
  @Override
  public Resource<Drawable> transform(@NonNull Context context,
      @NonNull Resource<Drawable> resource, int outWidth, int outHeight) {
    BitmapPool bitmapPool = Glide.get(context).getBitmapPool();
    Drawable drawable = resource.get();
    Resource<Bitmap> bitmapResourceToTransform =
        DrawableToBitmapConverter.convert(bitmapPool, drawable, outWidth, outHeight);
    if (bitmapResourceToTransform == null) {
      if (isRequired) {
        throw new IllegalArgumentException("Unable to convert " + drawable + " to a Bitmap");
      } else {
        return resource;
      }
    }
    Resource<Bitmap> transformedBitmapResource =
        wrapped.transform(context, bitmapResourceToTransform, outWidth, outHeight);

    if (transformedBitmapResource.equals(bitmapResourceToTransform)) {
      transformedBitmapResource.recycle();
      return resource;
    } else {
      return newDrawableResource(context, transformedBitmapResource);
    }
  }

  // It's clearer to cast the result in a separate line from obtaining it.
  @SuppressWarnings({"unchecked", "PMD.UnnecessaryLocalBeforeReturn"})
  private Resource<Drawable> newDrawableResource(
      Context context, Resource<Bitmap> transformed) {
    Resource<? extends Drawable> result =
        LazyBitmapDrawableResource.obtain(context.getResources(), transformed);
    return (Resource<Drawable>) result;
  }

  @Override
  public boolean equals(Object o) {
    if (o instanceof DrawableTransformation) {
      DrawableTransformation other = (DrawableTransformation) o;
      return wrapped.equals(other.wrapped);
    }
    return false;
  }

  @Override
  public int hashCode() {
    return wrapped.hashCode();
  }

  @Override
  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
    wrapped.updateDiskCacheKey(messageDigest);
  }
}
