content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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 org.apache.weex.ui.component;
  
  import android.support.annotation.RestrictTo;
  import android.support.annotation.RestrictTo.Scope;
  import org.apache.weex.dom.WXImageQuality;
  
  import java.lang.ref.WeakReference;
  import java.lang.reflect.InvocationTargetException;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  
  import android.Manifest;
  import android.app.Activity;
  import android.content.Context;
  import android.content.pm.PackageManager;
  import android.graphics.RectF;
  import android.graphics.drawable.Drawable;
  import android.net.Uri;
  import android.os.Build;
  import android.support.annotation.NonNull;
  import android.support.annotation.Nullable;
  import android.support.v4.app.ActivityCompat;
  import android.support.v4.content.ContextCompat;
  import android.text.TextUtils;
  import android.view.ViewGroup;
  import android.widget.ImageView;
  import android.widget.ImageView.ScaleType;
  import org.apache.weex.WXEnvironment;
  import org.apache.weex.WXSDKInstance;
  import org.apache.weex.adapter.IWXImgLoaderAdapter;
  import org.apache.weex.adapter.URIAdapter;
  import org.apache.weex.annotation.Component;
  import org.apache.weex.annotation.JSMethod;
  import org.apache.weex.bridge.JSCallback;
  import org.apache.weex.common.Constants;
  import org.apache.weex.common.WXImageSharpen;
  import org.apache.weex.common.WXImageStrategy;
  import org.apache.weex.common.WXRuntimeException;
  import org.apache.weex.performance.WXAnalyzerDataTransfer;
  import org.apache.weex.performance.WXInstanceApm;
  import org.apache.weex.ui.ComponentCreator;
  import org.apache.weex.ui.action.BasicComponentData;
  import org.apache.weex.ui.view.WXImageView;
  import org.apache.weex.ui.view.border.BorderDrawable;
  import org.apache.weex.utils.ImageDrawable;
  import org.apache.weex.utils.ImgURIUtil;
  import org.apache.weex.utils.SingleFunctionParser;
  import org.apache.weex.utils.WXDomUtils;
  import org.apache.weex.utils.WXLogUtils;
  import org.apache.weex.utils.WXUtils;
  import org.apache.weex.utils.WXViewToImageUtil;
  import org.apache.weex.utils.WXViewUtils;
  
  /**
   * Image component
   */
  @Component(lazyload = false)
  public class WXImage extends WXComponent<ImageView> {
  
    public static final String SUCCEED = "success";
    public static final String ERRORDESC = "errorDesc";
    private static final int WRITE_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 0x2;
  
    private String mSrc;
    private int mBlurRadius;
    private boolean mAutoRecycle = true;
  
    private static SingleFunctionParser.FlatMapper<Integer> BLUR_RADIUS_MAPPER = new SingleFunctionParser.FlatMapper<Integer>() {
      @Override
      public Integer map(String raw) {
        return WXUtils.getInteger(raw,0);
      }
    };
  
    public static class Creator implements ComponentCreator {
      @Override
      public WXComponent createInstance(WXSDKInstance instance, WXVContainer parent, BasicComponentData basicComponentData) throws IllegalAccessException, InvocationTargetException, InstantiationException {
        return new WXImage(instance, parent, basicComponentData);
      }
    }
  
    @Deprecated
    public WXImage(WXSDKInstance instance, WXVContainer parent, String instanceId, boolean isLazy, BasicComponentData basicComponentData) {
      this(instance, parent, basicComponentData);
    }
  
    public WXImage(WXSDKInstance instance, WXVContainer parent, BasicComponentData basicComponentData) {
      super(instance, parent, basicComponentData);
    }
  
    @Override
    protected ImageView initComponentHostView(@NonNull Context context) {
      WXImageView view = new WXImageView(context);
      view.setScaleType(ScaleType.FIT_XY);
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        view.setCropToPadding(true);
      }
      view.holdComponent(this);
      return view;
    }
  
    @Override
    protected boolean setProperty(String key, Object param) {
      switch (key) {
        case Constants.Name.RESIZE_MODE:
          String resizeMode = WXUtils.getString(param, null);
          if (resizeMode != null) { setResizeMode(resizeMode); }
          return true;
        case Constants.Name.RESIZE:
          String resize = WXUtils.getString(param, null);
          if (resize != null) { setResize(resize); }
          return true;
        case Constants.Name.SRC:
          String src = WXUtils.getString(param, null);
          if (src != null) { setSrc(src); }
          return true;
        case Constants.Name.IMAGE_QUALITY:
          return true;
        case Constants.Name.AUTO_RECYCLE:
          mAutoRecycle = WXUtils.getBoolean(param, mAutoRecycle);
          if (!mAutoRecycle && null != getInstance()){
            getInstance().getApmForInstance().updateDiffStats(WXInstanceApm.KEY_PAGE_STATS_IMG_UN_RECYCLE_NUM,1);
          }
          return true;
        case Constants.Name.FILTER:
          int blurRadius = 0;
          if (param != null && param instanceof String) {
            blurRadius = parseBlurRadius((String)param);
          }
          if (!TextUtils.isEmpty(this.mSrc)) {
            setBlurRadius(this.mSrc, blurRadius);
          }
          return true;
        default:
          return super.setProperty(key, param);
      }
    }
  
    @Override
    public void refreshData(WXComponent component) {
      super.refreshData(component);
      if (component instanceof WXImage) {
        setSrc(component.getAttrs().getImageSrc());
      }
    }
  
    @WXComponentProp(name = Constants.Name.RESIZE_MODE)
    public void setResizeMode(String resizeMode) {
      (getHostView()).setScaleType(getResizeMode(resizeMode));
      getHostView().setImageDrawable(getHostView().getDrawable());
    }
  
    @RestrictTo(Scope.LIBRARY_GROUP)
    protected ScaleType getResizeMode(String resizeMode) {
      ScaleType scaleType = ScaleType.FIT_XY;
      if (TextUtils.isEmpty(resizeMode)) {
        return scaleType;
      }
  
      switch (resizeMode) {
        case "cover":
          scaleType = ScaleType.CENTER_CROP;
          break;
        case "contain":
          scaleType = ScaleType.FIT_CENTER;
          break;
        case "stretch":
          scaleType = ScaleType.FIT_XY;
          break;
        default:
          break;
      }
      return scaleType;
    }
  
    @WXComponentProp(name = Constants.Name.RESIZE)
    public void setResize(String resize) {
      setResizeMode(resize);
    }
  
    /**
     * Process local scheme, load drawable.
     * @param rewrited
     */
    private void setLocalSrc(Uri rewrited) {
      ImageView imageView;
      Drawable localDrawable = ImgURIUtil.getDrawableFromLoaclSrc(getContext(), rewrited);
      if (localDrawable != null && (imageView = getHostView()) != null) {
        imageView.setImageDrawable(localDrawable);
      }
    }
  
    @WXComponentProp(name = Constants.Name.SRC)
    public void setSrc(String src) {
  
      if (getInstance().getImageNetworkHandler() != null) {
        String localUrl = getInstance().getImageNetworkHandler().fetchLocal(src);
        if (!TextUtils.isEmpty(localUrl)) {
          src = localUrl;
        }
      }
  
      if (src == null) {
        return;
      }
  
      ImageView image = getHostView();
      if("".equals(src) && image != null){
        image.setImageDrawable(null);
        return;
      }
  
      if(image != null){
        if(image.getDrawable() != null && !TextUtils.equals(mSrc, src)){
          image.setImageDrawable(null);
        }
      }
  
  
      this.mSrc = src;
      WXSDKInstance instance = getInstance();
      Uri rewrited = instance.rewriteUri(Uri.parse(src), URIAdapter.IMAGE);
  
      if (Constants.Scheme.LOCAL.equals(rewrited.getScheme())) {
        setLocalSrc(rewrited);
      } else {
        int blur = 0;
        String blurStr = getStyles().getBlur();
        blur = parseBlurRadius(blurStr);
        setRemoteSrc(rewrited, blur);
      }
    }
  
    private void setBlurRadius(@NonNull String src, int blurRadius) {
      if(getInstance() != null && blurRadius != mBlurRadius) {
        Uri parsedUri = getInstance().rewriteUri(Uri.parse(src), URIAdapter.IMAGE);
        if(!Constants.Scheme.LOCAL.equals(parsedUri.getScheme())) {
          setRemoteSrc(parsedUri,blurRadius);
        }
      }
    }
  
    private int parseBlurRadius(@Nullable String rawRadius) {
      if(rawRadius == null) {
        return 0;
      }
      SingleFunctionParser<Integer> parser = new SingleFunctionParser<Integer>(rawRadius,BLUR_RADIUS_MAPPER);
      List<Integer> list = null;
      try {
        list = parser.parse("blur");
      }catch (Exception e) {
        return 0;
      }
      if(list == null || list.isEmpty()) {
        return 0;
      }
      return list.get(0);
    }
  
    @Override
    public void recycled() {
      super.recycled();
  
      if (getInstance().getImgLoaderAdapter() != null) {
        getInstance().getImgLoaderAdapter().setImage(null, mHost,
                null, null);
      } else {
        if (WXEnvironment.isApkDebugable()) {
          throw new WXRuntimeException("getImgLoaderAdapter() == null");
        }
        WXLogUtils.e("Error getImgLoaderAdapter() == null");
      }
    }
  
    public void autoReleaseImage(){
      if(mAutoRecycle){
        if(getHostView() != null){
          if (getInstance().getImgLoaderAdapter() != null) {
            getInstance().getImgLoaderAdapter().setImage(null, mHost, null, null);
          }
        }
      }
    }
  
    public void autoRecoverImage() {
      if(mAutoRecycle) {
        setSrc(mSrc);
      }
    }
  
    private void setRemoteSrc(Uri rewrited, int blurRadius) {
  
      WXImageStrategy imageStrategy = new WXImageStrategy(getInstanceId());
      imageStrategy.isClipping = true;
  
      WXImageSharpen imageSharpen = getAttrs().getImageSharpen();
      imageStrategy.isSharpen = imageSharpen == WXImageSharpen.SHARPEN;
  
      imageStrategy.blurRadius = Math.max(0, blurRadius);
      this.mBlurRadius = blurRadius;
  
      final String rewritedStr = rewrited.toString();
      imageStrategy.setImageListener(new MyImageListener(this,rewritedStr));
  
      String placeholder=null;
      if(getAttrs().containsKey(Constants.Name.PLACEHOLDER)){
        placeholder= (String) getAttrs().get(Constants.Name.PLACEHOLDER);
      }else if(getAttrs().containsKey(Constants.Name.PLACE_HOLDER)){
        placeholder=(String)getAttrs().get(Constants.Name.PLACE_HOLDER);
      }
      if(placeholder!=null){
        imageStrategy.placeHolder = getInstance().rewriteUri(Uri.parse(placeholder),URIAdapter.IMAGE).toString();
      }
  
      imageStrategy.instanceId = getInstanceId();
      IWXImgLoaderAdapter imgLoaderAdapter = getInstance().getImgLoaderAdapter();
      if (imgLoaderAdapter != null) {
        imgLoaderAdapter.setImage(rewritedStr, getHostView(),
            getImageQuality(), imageStrategy);
      }
    }
  
    @RestrictTo(Scope.LIBRARY_GROUP)
    protected WXImageQuality getImageQuality(){
      return getAttrs().getImageQuality();
    }
  
    @Override
    protected void onFinishLayout() {
      super.onFinishLayout();
      updateBorderRadius();
    }
  
    @Override
    public void updateProperties(Map<String, Object> props) {
      super.updateProperties(props);
      updateBorderRadius();
    }
  
    private void updateBorderRadius() {
      if (getHostView() instanceof WXImageView) {
        final WXImageView imageView = (WXImageView)getHostView();
        BorderDrawable borderDrawable = WXViewUtils.getBorderDrawable(getHostView());
        float[] borderRadius;
        if (borderDrawable != null) {
          RectF borderBox = new RectF(0, 0, WXDomUtils.getContentWidth(this), WXDomUtils.getContentHeight(this));
          borderRadius = borderDrawable.getBorderInnerRadius(borderBox);
        } else {
          borderRadius = new float[] {0, 0, 0, 0, 0, 0, 0, 0};
        }
        imageView.setBorderRadius(borderRadius);
  
        if (imageView.getDrawable() instanceof ImageDrawable) {
          ImageDrawable imageDrawable = (ImageDrawable)imageView.getDrawable();
          float[] previousRadius = imageDrawable.getCornerRadii();
          if (!Arrays.equals(previousRadius, borderRadius)) {
            imageDrawable.setCornerRadii(borderRadius);
          }
        }
      }
    }
  
    /**
     * Need permission {android.permission.WRITE_EXTERNAL_STORAGE}
     */
    @JSMethod(uiThread = false)
    public void save(final JSCallback saveStatuCallback) {
  
      if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        if (getContext() instanceof Activity) {
          ActivityCompat.requestPermissions((Activity) getContext(),
                  new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, WRITE_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
        }
      }
  
      if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        if (saveStatuCallback != null) {
          Map<String, Object> result = new HashMap<>();
          result.put(SUCCEED, false);
          result.put(ERRORDESC,"Permission denied: android.permission.WRITE_EXTERNAL_STORAGE");
          saveStatuCallback.invoke(result);
        }
        return;
      }
  
      if (mHost == null) {
        if (saveStatuCallback != null) {
          Map<String, Object> result = new HashMap<>();
          result.put(SUCCEED, false);
          result.put(ERRORDESC,"Image component not initialized");
          saveStatuCallback.invoke(result);
        }
        return;
      }
  
      if (mSrc == null || mSrc.equals("")) {
        if (saveStatuCallback != null) {
          Map<String, Object> result = new HashMap<>();
          result.put(SUCCEED, false);
          result.put(ERRORDESC,"Image does not have the correct src");
          saveStatuCallback.invoke(result);
        }
        return;
      }
  
      WXViewToImageUtil.generateImage(mHost, 0, 0xfff8f8f8, new WXViewToImageUtil.OnImageSavedCallback() {
        @Override
        public void onSaveSucceed(String path) {
          if (saveStatuCallback != null) {
            Map<String, Object> result = new HashMap<>();
            result.put(SUCCEED, true);
            saveStatuCallback.invoke(result);
          }
        }
  
        @Override
        public void onSaveFailed(String errorDesc) {
          if (saveStatuCallback != null) {
            Map<String, Object> result = new HashMap<>();
            result.put(SUCCEED, false);
            result.put(ERRORDESC,errorDesc);
            saveStatuCallback.invoke(result);
          }
        }
      });
    }
  
    private String preImgUrlStr = "";
    private void monitorImgSize(ImageView imageView,String currentImgUrlStr){
      if (null == imageView){
        return;
      }
      WXSDKInstance instance = getInstance();
      if (null == instance){
        return;
      }
      ViewGroup.LayoutParams params =imageView.getLayoutParams();
      Drawable img = imageView.getDrawable();
      if (null == params || null ==img){
        return;
      }
      int imgHeight = img.getIntrinsicHeight();
      int imgWidth = img.getIntrinsicWidth();
      if (!preImgUrlStr.equals(currentImgUrlStr)){
        preImgUrlStr = currentImgUrlStr;
        if (imgHeight > 1081 && imgWidth > 721){
          instance.getApmForInstance().updateDiffStats(WXInstanceApm.KEY_PAGE_STATS_LARGE_IMG_COUNT,1);
          if (WXAnalyzerDataTransfer.isOpenPerformance){
            WXAnalyzerDataTransfer.transferPerformance(getInstanceId(),"details",WXInstanceApm.KEY_PAGE_STATS_LARGE_IMG_COUNT,
                imgWidth+"*"+imgHeight+","+currentImgUrlStr
                );
          }
        }
        long imgSize = imgHeight * imgWidth;
        long viewSize = imageView.getMeasuredHeight() * imageView.getMeasuredWidth();
        if (viewSize == 0){
            return;
        }
        double scaleSize =  imgSize/(double)viewSize;
        //max diff 40*40
        if (scaleSize >1.2 && imgSize-viewSize > 1600){
          instance.getWXPerformance().wrongImgSizeCount++;
          instance.getApmForInstance().updateDiffStats(WXInstanceApm.KEY_PAGE_STATS_WRONG_IMG_SIZE_COUNT,1);
  
          if (WXAnalyzerDataTransfer.isOpenPerformance){
            WXAnalyzerDataTransfer.transferPerformance(getInstanceId(),"details",WXInstanceApm.KEY_PAGE_STATS_WRONG_IMG_SIZE_COUNT,
                String.format(Locale.ROOT, "imgSize:[%d,%d],viewSize:[%d,%d],urL:%s",imgWidth,imgHeight,imageView.getMeasuredWidth(),imageView.getMeasuredHeight()
                ,currentImgUrlStr)
            );
          }
        }
      }
  
    }
  
    @Override
    public void destroy() {
      if(getHostView() instanceof WXImageView){
        if (getInstance().getImgLoaderAdapter() != null) {
          getInstance().getImgLoaderAdapter().setImage(null, mHost, null, null);
        }
      }
      super.destroy();
    }
  
    public interface Measurable {
      int getNaturalWidth();
      int getNaturalHeight();
    }
  
    public  static class MyImageListener implements WXImageStrategy.ImageListener {
  
      private WeakReference<WXImage> wxImageWeakReference;
  
      private String rewritedStr;
  
      MyImageListener(WXImage image,String rewritedStr) {
        this.wxImageWeakReference = new WeakReference<WXImage>(image);
        this.rewritedStr = rewritedStr;
      }
  
      @Override
      public void onImageFinish(String url, ImageView imageView, boolean result, Map extra) {
        WXImage image = wxImageWeakReference.get();
  
        if(image == null)
          return;
  
        if (image.getEvents().contains(Constants.Event.ONLOAD)) {
          Map<String, Object> params = new HashMap<String, Object>();
          Map<String, Object> size = new HashMap<>(2);
          if (imageView != null && imageView instanceof Measurable) {
            size.put("naturalWidth", ((Measurable) imageView).getNaturalWidth());
            size.put("naturalHeight", ((Measurable) imageView).getNaturalHeight());
          } else {
            size.put("naturalWidth", 0);
            size.put("naturalHeight", 0);
          }
          if (image.containsEvent(Constants.Event.ONLOAD)) {
            params.put("success", result);
            params.put("size", size);
            image.fireEvent(Constants.Event.ONLOAD, params);
          }
        }
        image.monitorImgSize(imageView,rewritedStr);
      }
    }
  }
  