package com.ama.core.common.widget.roundedimageview;

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.ColorFilter;
import android.graphics.Shader;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.util.AttributeSet;
import android.widget.ImageView;

import androidx.annotation.ColorInt;
import androidx.annotation.DimenRes;
import androidx.annotation.DrawableRes;

import com.ama.core.common.R;


@SuppressWarnings("UnusedDeclaration")
public class RoundedImageView extends ImageView {

  // Constants for tile mode attributes
  private static final int TILE_MODE_UNDEFINED = -2;
  private static final int TILE_MODE_CLAMP = 0;
  private static final int TILE_MODE_REPEAT = 1;
  private static final int TILE_MODE_MIRROR = 2;

  public static final String TAG = "RoundedImageView";
  public static final float DEFAULT_RADIUS = 0f;
  public static final float DEFAULT_BORDER_WIDTH = 0f;
  public static final Shader.TileMode DEFAULT_TILE_MODE = Shader.TileMode.CLAMP;
  private static final ScaleType[] SCALE_TYPES = {
      ScaleType.MATRIX,
      ScaleType.FIT_XY,
      ScaleType.FIT_START,
      ScaleType.FIT_CENTER,
      ScaleType.FIT_END,
      ScaleType.CENTER,
      ScaleType.CENTER_CROP,
      ScaleType.CENTER_INSIDE
  };

  private final float[] mCornerRadii =
      new float[] { DEFAULT_RADIUS, DEFAULT_RADIUS, DEFAULT_RADIUS, DEFAULT_RADIUS };

  private Drawable mBackgroundDrawable;
  private ColorStateList mBorderColor =
      ColorStateList.valueOf(RoundedDrawable.DEFAULT_BORDER_COLOR);
  private float mBorderWidth = DEFAULT_BORDER_WIDTH;
  private ColorFilter mColorFilter = null;
  private boolean mColorMod = false;
  private Drawable mDrawable;
  private boolean mHasColorFilter = false;
  private boolean mIsOval = false;
  private boolean mMutateBackground = false;
  private int mResource;
  private int mBackgroundResource;
  private ScaleType mScaleType;
  private Shader.TileMode mTileModeX = DEFAULT_TILE_MODE;
  private Shader.TileMode mTileModeY = DEFAULT_TILE_MODE;

  public RoundedImageView(Context context) {
    super(context);
  }

  public RoundedImageView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }

  public RoundedImageView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);

    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.core_common_RoundedImageView, defStyle, 0);

    int index = a.getInt(R.styleable.core_common_RoundedImageView_android_scaleType, -1);
    if (index >= 0) {
      setScaleType(SCALE_TYPES[index]);
    } else {
      // default scaletype to FIT_CENTER
      setScaleType(ScaleType.FIT_CENTER);
    }

    float cornerRadiusOverride =
        a.getDimensionPixelSize(R.styleable.core_common_RoundedImageView_core_common_riv_corner_radius, -1);

    mCornerRadii[Corner.TOP_LEFT] =
        a.getDimensionPixelSize(R.styleable.core_common_RoundedImageView_core_common_riv_corner_radius_top_left, -1);
    mCornerRadii[Corner.TOP_RIGHT] =
        a.getDimensionPixelSize(R.styleable.core_common_RoundedImageView_core_common_riv_corner_radius_top_right, -1);
    mCornerRadii[Corner.BOTTOM_RIGHT] =
        a.getDimensionPixelSize(R.styleable.core_common_RoundedImageView_core_common_riv_corner_radius_bottom_right, -1);
    mCornerRadii[Corner.BOTTOM_LEFT] =
        a.getDimensionPixelSize(R.styleable.core_common_RoundedImageView_core_common_riv_corner_radius_bottom_left, -1);

    boolean any = false;
    for (int i = 0, len = mCornerRadii.length; i < len; i++) {
      if (mCornerRadii[i] < 0) {
        mCornerRadii[i] = 0f;
      } else {
        any = true;
      }
    }

    if (!any) {
      if (cornerRadiusOverride < 0) {
        cornerRadiusOverride = DEFAULT_RADIUS;
      }
      for (int i = 0, len = mCornerRadii.length; i < len; i++) {
        mCornerRadii[i] = cornerRadiusOverride;
      }
    }

    mBorderWidth = a.getDimensionPixelSize(R.styleable.core_common_RoundedImageView_core_common_riv_border_width, -1);
    if (mBorderWidth < 0) {
      mBorderWidth = DEFAULT_BORDER_WIDTH;
    }

    mBorderColor = a.getColorStateList(R.styleable.core_common_RoundedImageView_core_common_riv_border_color);
    if (mBorderColor == null) {
      mBorderColor = ColorStateList.valueOf(RoundedDrawable.DEFAULT_BORDER_COLOR);
    }

    mMutateBackground = a.getBoolean(R.styleable.core_common_RoundedImageView_core_common_riv_mutate_background, false);
    mIsOval = a.getBoolean(R.styleable.core_common_RoundedImageView_core_common_riv_oval, false);

    final int tileMode = a.getInt(R.styleable.core_common_RoundedImageView_core_common_riv_tile_mode, TILE_MODE_UNDEFINED);
    if (tileMode != TILE_MODE_UNDEFINED) {
      setTileModeX(parseTileMode(tileMode));
      setTileModeY(parseTileMode(tileMode));
    }

    final int tileModeX =
        a.getInt(R.styleable.core_common_RoundedImageView_core_common_riv_tile_mode_x, TILE_MODE_UNDEFINED);
    if (tileModeX != TILE_MODE_UNDEFINED) {
      setTileModeX(parseTileMode(tileModeX));
    }

    final int tileModeY =
        a.getInt(R.styleable.core_common_RoundedImageView_core_common_riv_tile_mode_y, TILE_MODE_UNDEFINED);
    if (tileModeY != TILE_MODE_UNDEFINED) {
      setTileModeY(parseTileMode(tileModeY));
    }

    updateDrawableAttrs();
    updateBackgroundDrawableAttrs(true);

    if (mMutateBackground) {
      //noinspection deprecation
      super.setBackgroundDrawable(mBackgroundDrawable);
    }

    a.recycle();
  }

  private static Shader.TileMode parseTileMode(int tileMode) {
    switch (tileMode) {
      case TILE_MODE_CLAMP:
        return Shader.TileMode.CLAMP;
      case TILE_MODE_REPEAT:
        return Shader.TileMode.REPEAT;
      case TILE_MODE_MIRROR:
        return Shader.TileMode.MIRROR;
      default:
        return null;
    }
  }

  @Override
  protected void drawableStateChanged() {
    super.drawableStateChanged();
    invalidate();
  }

  @Override
  public ScaleType getScaleType() {
    return mScaleType;
  }

  @Override
  public void setScaleType(ScaleType scaleType) {
    assert scaleType != null;

    if (mScaleType != scaleType) {
      mScaleType = scaleType;

      switch (scaleType) {
        case CENTER:
        case CENTER_CROP:
        case CENTER_INSIDE:
        case FIT_CENTER:
        case FIT_START:
        case FIT_END:
        case FIT_XY:
          super.setScaleType(ScaleType.FIT_XY);
          break;
        default:
          super.setScaleType(scaleType);
          break;
      }

      updateDrawableAttrs();
      updateBackgroundDrawableAttrs(false);
      invalidate();
    }
  }

  @Override
  public void setImageDrawable(Drawable drawable) {
    mResource = 0;
    mDrawable = RoundedDrawable.fromDrawable(drawable);
    updateDrawableAttrs();
    super.setImageDrawable(mDrawable);
  }

  @Override
  public void setImageBitmap(Bitmap bm) {
    mResource = 0;
    mDrawable = RoundedDrawable.fromBitmap(bm);
    updateDrawableAttrs();
    super.setImageDrawable(mDrawable);
  }

  @Override
  public void setImageResource(@DrawableRes int resId) {
    if (mResource != resId) {
      mResource = resId;
      mDrawable = resolveResource();
      updateDrawableAttrs();
      super.setImageDrawable(mDrawable);
    }
  }

  @Override public void setImageURI(Uri uri) {
    super.setImageURI(uri);
    setImageDrawable(getDrawable());
  }

  private Drawable resolveResource() {
    Resources rsrc = getResources();
    if (rsrc == null) { return null; }

    Drawable d = null;

    if (mResource != 0) {
      try {
        d = rsrc.getDrawable(mResource);
      } catch (Exception e) {
        // Don't try again.
        mResource = 0;
      }
    }
    return RoundedDrawable.fromDrawable(d);
  }

  @Override
  public void setBackground(Drawable background) {
    setBackgroundDrawable(background);
  }

  @Override
  public void setBackgroundResource(@DrawableRes int resId) {
    if (mBackgroundResource != resId) {
      mBackgroundResource = resId;
      mBackgroundDrawable = resolveBackgroundResource();
      setBackgroundDrawable(mBackgroundDrawable);
    }
  }

  @Override
  public void setBackgroundColor(int color) {
    mBackgroundDrawable = new ColorDrawable(color);
    setBackgroundDrawable(mBackgroundDrawable);
  }

  private Drawable resolveBackgroundResource() {
    Resources rsrc = getResources();
    if (rsrc == null) { return null; }

    Drawable d = null;

    if (mBackgroundResource != 0) {
      try {
        d = rsrc.getDrawable(mBackgroundResource);
      } catch (Exception e) {
        // Don't try again.
        mBackgroundResource = 0;
      }
    }
    return RoundedDrawable.fromDrawable(d);
  }

  private void updateDrawableAttrs() {
    updateAttrs(mDrawable, mScaleType);
  }

  private void updateBackgroundDrawableAttrs(boolean convert) {
    if (mMutateBackground) {
      if (convert) {
        mBackgroundDrawable = RoundedDrawable.fromDrawable(mBackgroundDrawable);
      }
      updateAttrs(mBackgroundDrawable, ScaleType.FIT_XY);
    }
  }

  @Override public void setColorFilter(ColorFilter cf) {
    if (mColorFilter != cf) {
      mColorFilter = cf;
      mHasColorFilter = true;
      mColorMod = true;
      applyColorMod();
      invalidate();
    }
  }

  private void applyColorMod() {
    // Only mutate and apply when modifications have occurred. This should
    // not reset the mColorMod flag, since these filters need to be
    // re-applied if the Drawable is changed.
    if (mDrawable != null && mColorMod) {
      mDrawable = mDrawable.mutate();
      if (mHasColorFilter) {
        mDrawable.setColorFilter(mColorFilter);
      }
      //mDrawable.setXfermode(mXfermode);
      //mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8);
    }
  }

  private void updateAttrs(Drawable drawable, ScaleType scaleType) {
    if (drawable == null) { return; }

    if (drawable instanceof RoundedDrawable) {
      ((RoundedDrawable) drawable)
              .setScaleType(scaleType)
              .setBorderWidth(mBorderWidth)
              .setBorderColor(mBorderColor)
              .setOval(mIsOval)
              .setTileModeX(mTileModeX)
              .setTileModeY(mTileModeY);

      if (mCornerRadii != null) {
        ((RoundedDrawable) drawable).setCornerRadius(
            mCornerRadii[Corner.TOP_LEFT],
            mCornerRadii[Corner.TOP_RIGHT],
            mCornerRadii[Corner.BOTTOM_RIGHT],
            mCornerRadii[Corner.BOTTOM_LEFT]);
      }

      applyColorMod();
    } else if (drawable instanceof LayerDrawable) {
      // loop through layers to and set drawable attrs
      LayerDrawable ld = ((LayerDrawable) drawable);
      for (int i = 0, layers = ld.getNumberOfLayers(); i < layers; i++) {
        updateAttrs(ld.getDrawable(i), scaleType);
      }
    }
  }

  @Override
  @Deprecated
  public void setBackgroundDrawable(Drawable background) {
    mBackgroundDrawable = background;
    updateBackgroundDrawableAttrs(true);
    //noinspection deprecation
    super.setBackgroundDrawable(mBackgroundDrawable);
  }

  /**
   * @return the largest corner radius.
   */
  public float getCornerRadius() {
    return getMaxCornerRadius();
  }

  /**
   * @return the largest corner radius.
   */
  public float getMaxCornerRadius() {
    float maxRadius = 0;
    for (float r : mCornerRadii) {
      maxRadius = Math.max(r, maxRadius);
    }
    return maxRadius;
  }

  /**
   * Get the corner radius of a specified corner.
   *
   * @param corner the corner.
   * @return the radius.
   */
  public float getCornerRadius(@Corner int corner) {
    return mCornerRadii[corner];
  }

  /**
   * Set all the corner radii from a dimension resource id.
   *
   * @param resId dimension resource id of radii.
   */
  public void setCornerRadiusDimen(@DimenRes int resId) {
    float radius = getResources().getDimension(resId);
    setCornerRadius(radius, radius, radius, radius);
  }

  /**
   * Set the corner radius of a specific corner from a dimension resource id.
   *
   * @param corner the corner to set.
   * @param resId the dimension resource id of the corner radius.
   */
  public void setCornerRadiusDimen(@Corner int corner, @DimenRes int resId) {
    setCornerRadius(corner, getResources().getDimensionPixelSize(resId));
  }

  /**
   * Set the corner radii of all corners in px.
   *
   * @param radius the radius to set.
   */
  public void setCornerRadius(float radius) {
    setCornerRadius(radius, radius, radius, radius);
  }

  /**
   * Set the corner radius of a specific corner in px.
   *
   * @param corner the corner to set.
   * @param radius the corner radius to set in px.
   */
  public void setCornerRadius(@Corner int corner, float radius) {
    if (mCornerRadii[corner] == radius) {
      return;
    }
    mCornerRadii[corner] = radius;

    updateDrawableAttrs();
    updateBackgroundDrawableAttrs(false);
    invalidate();
  }

  /**
   * Set the corner radii of each corner individually. Currently only one unique nonzero value is
   * supported.
   *
   * @param topLeft radius of the top left corner in px.
   * @param topRight radius of the top right corner in px.
   * @param bottomRight radius of the bottom right corner in px.
   * @param bottomLeft radius of the bottom left corner in px.
   */
  public void setCornerRadius(float topLeft, float topRight, float bottomLeft, float bottomRight) {
    if (mCornerRadii[Corner.TOP_LEFT] == topLeft
        && mCornerRadii[Corner.TOP_RIGHT] == topRight
        && mCornerRadii[Corner.BOTTOM_RIGHT] == bottomRight
        && mCornerRadii[Corner.BOTTOM_LEFT] == bottomLeft) {
      return;
    }

    mCornerRadii[Corner.TOP_LEFT] = topLeft;
    mCornerRadii[Corner.TOP_RIGHT] = topRight;
    mCornerRadii[Corner.BOTTOM_LEFT] = bottomLeft;
    mCornerRadii[Corner.BOTTOM_RIGHT] = bottomRight;

    updateDrawableAttrs();
    updateBackgroundDrawableAttrs(false);
    invalidate();
  }

  public float getBorderWidth() {
    return mBorderWidth;
  }

  public void setBorderWidth(@DimenRes int resId) {
    setBorderWidth(getResources().getDimension(resId));
  }

  public void setBorderWidth(float width) {
    if (mBorderWidth == width) { return; }

    mBorderWidth = width;
    updateDrawableAttrs();
    updateBackgroundDrawableAttrs(false);
    invalidate();
  }

  @ColorInt
  public int getBorderColor() {
    return mBorderColor.getDefaultColor();
  }

  public void setBorderColor(@ColorInt int color) {
    setBorderColor(ColorStateList.valueOf(color));
  }

  public ColorStateList getBorderColors() {
    return mBorderColor;
  }

  public void setBorderColor(ColorStateList colors) {
    if (mBorderColor.equals(colors)) { return; }

    mBorderColor =
        (colors != null) ? colors : ColorStateList.valueOf(RoundedDrawable.DEFAULT_BORDER_COLOR);
    updateDrawableAttrs();
    updateBackgroundDrawableAttrs(false);
    if (mBorderWidth > 0) {
      invalidate();
    }
  }

  /**
   * Return true if this view should be oval and always set corner radii to half the height or
   * width.
   *
   * @return if this {@link RoundedImageView} is set to oval.
   */
  public boolean isOval() {
    return mIsOval;
  }

  /**
   * Set if the drawable should ignore the corner radii set and always round the source to
   * exactly half the height or width.
   *
   * @param oval if this {@link RoundedImageView} should be oval.
   */
  public void setOval(boolean oval) {
    mIsOval = oval;
    updateDrawableAttrs();
    updateBackgroundDrawableAttrs(false);
    invalidate();
  }

  public Shader.TileMode getTileModeX() {
    return mTileModeX;
  }

  public void setTileModeX(Shader.TileMode tileModeX) {
    if (this.mTileModeX == tileModeX) { return; }

    this.mTileModeX = tileModeX;
    updateDrawableAttrs();
    updateBackgroundDrawableAttrs(false);
    invalidate();
  }

  public Shader.TileMode getTileModeY() {
    return mTileModeY;
  }

  public void setTileModeY(Shader.TileMode tileModeY) {
    if (this.mTileModeY == tileModeY) { return; }

    this.mTileModeY = tileModeY;
    updateDrawableAttrs();
    updateBackgroundDrawableAttrs(false);
    invalidate();
  }

  /**
   * If {@code true}, we will also round the background drawable according to the settings on this
   * ImageView.
   *
   * @return whether the background is mutated.
   */
  public boolean mutatesBackground() {
    return mMutateBackground;
  }

  /**
   * Set whether the {@link RoundedImageView} should round the background drawable according to
   * the settings in addition to the source drawable.
   *
   * @param mutate true if this view should mutate the background drawable.
   */
  public void mutateBackground(boolean mutate) {
    if (mMutateBackground == mutate) { return; }

    mMutateBackground = mutate;
    updateBackgroundDrawableAttrs(true);
    invalidate();
  }
}
