package com.bumptech.glide.request.target;

import android.annotation.TargetApi;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Build;
import com.bumptech.glide.util.Preconditions;
import com.bumptech.glide.util.Synthetic;

/**
 * A wrapper drawable to square the wrapped drawable so that it expands to fill a square with
 * exactly the given side length. The goal of this drawable is to ensure that square thumbnail
 * drawables always match the size of the view they will be displayed in to avoid a costly
 * requestLayout call. This class should not be used with views or drawables that are not square.
 */
public class FixedSizeDrawable extends Drawable {
  private final Matrix matrix;
  private final RectF wrappedRect;
  private final RectF bounds;
  private Drawable wrapped;
  private State state;
  private boolean mutated;

  public FixedSizeDrawable(Drawable wrapped, int width, int height) {
    this(new State(wrapped.getConstantState(), width, height), wrapped);
  }

  FixedSizeDrawable(State state, Drawable wrapped) {
    this.state = Preconditions.checkNotNull(state);
    this.wrapped = Preconditions.checkNotNull(wrapped);

    // We will do our own scaling.
    wrapped.setBounds(0, 0, wrapped.getIntrinsicWidth(), wrapped.getIntrinsicHeight());

    matrix = new Matrix();
    wrappedRect = new RectF(0, 0, wrapped.getIntrinsicWidth(), wrapped.getIntrinsicHeight());
    bounds = new RectF();
  }

  @Override
  public void setBounds(int left, int top, int right, int bottom) {
    super.setBounds(left, top, right, bottom);
    bounds.set(left, top, right, bottom);
    updateMatrix();
  }

  @Override
  public void setBounds(Rect bounds) {
    super.setBounds(bounds);
    this.bounds.set(bounds);
    updateMatrix();
  }

  private void updateMatrix() {
    matrix.setRectToRect(wrappedRect, this.bounds, Matrix.ScaleToFit.CENTER);
  }

  @Override
  public void setChangingConfigurations(int configs) {
    wrapped.setChangingConfigurations(configs);
  }

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

  @Deprecated
  @Override
  public void setDither(boolean dither) {
    wrapped.setDither(dither);
  }

  @Override
  public void setFilterBitmap(boolean filter) {
    wrapped.setFilterBitmap(filter);
  }

  @Override
  public Callback getCallback() {
    return wrapped.getCallback();
  }

  @TargetApi(Build.VERSION_CODES.KITKAT)
  @Override
  public int getAlpha() {
    return wrapped.getAlpha();
  }

  @Override
  public void setColorFilter(int color, PorterDuff.Mode mode) {
    wrapped.setColorFilter(color, mode);
  }

  @Override
  public void clearColorFilter() {
    wrapped.clearColorFilter();
  }

  @Override
  public Drawable getCurrent() {
    return wrapped.getCurrent();
  }

  @Override
  public boolean setVisible(boolean visible, boolean restart) {
    return wrapped.setVisible(visible, restart);
  }

  @Override
  public int getIntrinsicWidth() {
    return state.width;
  }

  @Override
  public int getIntrinsicHeight() {
    return state.height;
  }

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

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

  @Override
  public boolean getPadding(Rect padding) {
    return wrapped.getPadding(padding);
  }

  @Override
  public void invalidateSelf() {
    super.invalidateSelf();
    wrapped.invalidateSelf();
  }

  @Override
  public void unscheduleSelf(Runnable what) {
    super.unscheduleSelf(what);
    wrapped.unscheduleSelf(what);
  }

  @Override
  public void scheduleSelf(Runnable what, long when) {
    super.scheduleSelf(what, when);
    wrapped.scheduleSelf(what, when);
  }

  @Override
  public void draw(Canvas canvas) {
    canvas.save();
    canvas.concat(matrix);
    wrapped.draw(canvas);
    canvas.restore();
  }

  @Override
  public void setAlpha(int i) {
    wrapped.setAlpha(i);
  }

  @Override
  public void setColorFilter(ColorFilter colorFilter) {
    wrapped.setColorFilter(colorFilter);
  }

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

  @Override
  public Drawable mutate() {
    if (!mutated && super.mutate() == this) {
      wrapped = wrapped.mutate();
      state = new State(state);
      mutated = true;
    }
    return this;
  }

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

  static class State extends ConstantState {
    private final ConstantState wrapped;
    @Synthetic final int width;
    @Synthetic final int height;

    State(State other) {
      this(other.wrapped, other.width, other.height);
    }

    State(ConstantState wrapped, int width, int height) {
      this.wrapped = wrapped;
      this.width = width;
      this.height = height;
    }

    @Override
    public Drawable newDrawable() {
      return new FixedSizeDrawable(this, wrapped.newDrawable());
    }

    @Override
    public Drawable newDrawable(Resources res) {
      return new FixedSizeDrawable(this, wrapped.newDrawable(res));
    }

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