/**
 * Copyright (c) 2015-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.flat;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Animatable;
import android.net.Uri;
import com.facebook.drawee.controller.ControllerListener;
import com.facebook.drawee.drawable.ScalingUtils.ScaleType;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.generic.RoundingParams;
import com.facebook.imagepipeline.common.ResizeOptions;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.views.image.GlobalImageLoadListener;
import com.facebook.react.views.image.ImageLoadEvent;
import com.facebook.react.views.image.ImageResizeMode;
import com.facebook.react.views.image.ReactImageView;
import com.facebook.react.views.imagehelper.ImageSource;
import com.facebook.react.views.imagehelper.MultiSourceHelper;
import com.facebook.react.views.imagehelper.MultiSourceHelper.MultiSourceResult;
import java.util.LinkedList;
import java.util.List;
import javax.annotation.Nullable;

/**
 * DrawImageWithDrawee is a DrawCommand that can draw a local or remote image.
 * It uses DraweeRequestHelper internally to fetch and cache the images.
 */
/* package */ final class DrawImageWithDrawee extends AbstractDrawCommand
    implements DrawImage, ControllerListener {
  private static final String LOCAL_FILE_SCHEME = "file";
  private static final String LOCAL_CONTENT_SCHEME = "content";

  private final List<ImageSource> mSources = new LinkedList<>();
  private final @Nullable GlobalImageLoadListener mGlobalImageLoadListener;
  private @Nullable DraweeRequestHelper mRequestHelper;
  private @Nullable PorterDuffColorFilter mColorFilter;
  private ScaleType mScaleType = ImageResizeMode.defaultValue();
  private float mBorderWidth;
  private float mBorderRadius;
  private int mBorderColor;
  private int mReactTag;
  private boolean mProgressiveRenderingEnabled;
  private int mFadeDuration = ReactImageView.REMOTE_IMAGE_FADE_DURATION_MS;
  private @Nullable FlatViewGroup.InvalidateCallback mCallback;

  public DrawImageWithDrawee(@Nullable GlobalImageLoadListener globalImageLoadListener) {
    mGlobalImageLoadListener = globalImageLoadListener;
  }

  @Override
  public boolean hasImageRequest() {
    return !mSources.isEmpty();
  }

  @Override
  public void setSource(Context context, @Nullable ReadableArray sources) {
    mSources.clear();
    if (sources != null && sources.size() != 0) {
      // Optimize for the case where we have just one uri, case in which we don't need the sizes
      if (sources.size() == 1) {
        ReadableMap source = sources.getMap(0);
        mSources.add(new ImageSource(context, source.getString("uri")));
      } else {
        for (int idx = 0; idx < sources.size(); idx++) {
          ReadableMap source = sources.getMap(idx);
          mSources.add(new ImageSource(
              context,
              source.getString("uri"),
              source.getDouble("width"),
              source.getDouble("height")));
        }
      }
    }
  }

  @Override
  public void setTintColor(int tintColor) {
    if (tintColor == 0) {
      mColorFilter = null;
    } else {
      mColorFilter = new PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_ATOP);
    }
  }

  @Override
  public void setScaleType(ScaleType scaleType) {
    mScaleType = scaleType;
  }

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

  @Override
  public void setBorderWidth(float borderWidth) {
    mBorderWidth = borderWidth;
  }

  @Override
  public float getBorderWidth() {
    return mBorderWidth;
  }

  @Override
  public void setBorderRadius(float borderRadius) {
    mBorderRadius = borderRadius;
  }

  @Override
  public float getBorderRadius() {
    return mBorderRadius;
  }

  @Override
  public void setBorderColor(int borderColor) {
    mBorderColor = borderColor;
  }

  @Override
  public int getBorderColor() {
    return mBorderColor;
  }

  @Override
  public void setFadeDuration(int fadeDuration) {
    mFadeDuration = fadeDuration;
  }

  @Override
  public void setProgressiveRenderingEnabled(boolean enabled) {
    mProgressiveRenderingEnabled = enabled;
  }

  @Override
  public void setReactTag(int reactTag) {
    mReactTag = reactTag;
  }

  @Override
  public void onDraw(Canvas canvas) {
    if (mRequestHelper != null) {
      mRequestHelper.getDrawable().draw(canvas);
    }
  }

  @Override
  public void onAttached(FlatViewGroup.InvalidateCallback callback) {
    mCallback = callback;

    if (mRequestHelper == null) {
      // this is here to help us debug t12048319, in which we have a null request helper on attach
      throw new RuntimeException(
          "No DraweeRequestHelper - width: " +
              (getRight() - getLeft()) +
              " - height: " + (getBottom() - getTop() +
              " - number of sources: " + mSources.size()));
    }

    GenericDraweeHierarchy hierarchy = mRequestHelper.getHierarchy();

    RoundingParams roundingParams = hierarchy.getRoundingParams();
    if (shouldDisplayBorder()) {
      if (roundingParams == null) {
        roundingParams = new RoundingParams();
      }

      roundingParams.setBorder(mBorderColor, mBorderWidth);
      roundingParams.setCornersRadius(mBorderRadius);

      // changes won't take effect until we re-apply rounding params, so do it now.
      hierarchy.setRoundingParams(roundingParams);
    } else if (roundingParams != null) {
      // clear rounding params
      hierarchy.setRoundingParams(null);
    }

    hierarchy.setActualImageScaleType(mScaleType);
    hierarchy.setActualImageColorFilter(mColorFilter);
    hierarchy.setFadeDuration(mFadeDuration);

    hierarchy.getTopLevelDrawable().setBounds(
        Math.round(getLeft()),
        Math.round(getTop()),
        Math.round(getRight()),
        Math.round(getBottom()));

    mRequestHelper.attach(callback);
  }

  @Override
  public void onDetached() {
    if (mRequestHelper != null) {
      mRequestHelper.detach();
    }
  }

  @Override
  public void onSubmit(String id, Object callerContext) {
    if (mCallback != null && mReactTag != 0) {
      mCallback.dispatchImageLoadEvent(mReactTag, ImageLoadEvent.ON_LOAD_START);
    }
  }

  @Override
  public void onFinalImageSet(
      String id,
      @Nullable Object imageInfo,
      @Nullable Animatable animatable) {
    if (mCallback != null && mReactTag != 0) {
      mCallback.dispatchImageLoadEvent(mReactTag, ImageLoadEvent.ON_LOAD);
      mCallback.dispatchImageLoadEvent(mReactTag, ImageLoadEvent.ON_LOAD_END);
    }
  }

  @Override
  public void onIntermediateImageSet(String id, @Nullable Object imageInfo) {
  }

  @Override
  public void onIntermediateImageFailed(String id, Throwable throwable) {
  }

  @Override
  public void onFailure(String id, Throwable throwable) {
    if (mCallback != null && mReactTag != 0) {
      mCallback.dispatchImageLoadEvent(mReactTag, ImageLoadEvent.ON_ERROR);
      mCallback.dispatchImageLoadEvent(mReactTag, ImageLoadEvent.ON_LOAD_END);
    }
  }

  @Override
  public void onRelease(String id) {
  }

  @Override
  protected void onBoundsChanged() {
    super.onBoundsChanged();
    computeRequestHelper();
  }

  private void computeRequestHelper() {
    MultiSourceResult multiSource = MultiSourceHelper.getBestSourceForSize(
        Math.round(getRight() - getLeft()),
        Math.round(getBottom() - getTop()),
        mSources);
    ImageSource source = multiSource.getBestResult();
    ImageSource cachedSource = multiSource.getBestResultInCache();
    if (source == null) {
      mRequestHelper = null;
      return;
    }

    ResizeOptions resizeOptions = null;
    if (shouldResize(source)) {
      final int width = (int) (getRight() - getLeft());
      final int height = (int) (getBottom() - getTop());
      resizeOptions = new ResizeOptions(width, height);
    }

    ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(source.getUri())
        .setResizeOptions(resizeOptions)
        .setProgressiveRenderingEnabled(mProgressiveRenderingEnabled)
        .build();
    if (mGlobalImageLoadListener != null) {
      mGlobalImageLoadListener.onLoadAttempt(source.getUri());
    }

    ImageRequest cachedImageRequest = null;
    if (cachedSource != null) {
      cachedImageRequest = ImageRequestBuilder.newBuilderWithSource(cachedSource.getUri())
          .setResizeOptions(resizeOptions)
          .setProgressiveRenderingEnabled(mProgressiveRenderingEnabled)
          .build();
    }
    mRequestHelper = new
      DraweeRequestHelper(Assertions.assertNotNull(imageRequest), cachedImageRequest, this);
  }

  private boolean shouldDisplayBorder() {
    return mBorderColor != 0 || mBorderRadius >= 0.5f;
  }

  private static boolean shouldResize(ImageSource imageSource) {
    // Resizing is inferior to scaling. See http://frescolib.org/docs/resizing-rotating.html
    // We resize here only for images likely to be from the device's camera, where the app developer
    // has no control over the original size
    Uri uri = imageSource.getUri();
    String type = uri == null ? null : uri.getScheme();
    // one day, we can replace this with what non-Nodes does, which is:
    // UriUtil.isLocalContentUri || UriUtil.isLocalFileUri
    // not doing this just to save including eyt another BUCK dependency
    return LOCAL_FILE_SCHEME.equals(type) || LOCAL_CONTENT_SCHEME.equals(type);
  }

  @Override
  protected void onDebugDrawHighlight(Canvas canvas) {
    if (mCallback != null) {
      debugDrawCautionHighlight(canvas, "Invalidate Drawee");
    }
  }
}
