/*
 * 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);
    }
  }
}
