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.animation;
  
  import android.animation.PropertyValuesHolder;
  import android.support.annotation.NonNull;
  import android.support.annotation.Nullable;
  import android.support.v4.util.ArrayMap;
  import android.text.TextUtils;
  import android.util.Pair;
  import android.util.Property;
  import android.view.View;
  
  import org.apache.weex.WXEnvironment;
  import org.apache.weex.common.Constants;
  import org.apache.weex.common.WXErrorCode;
  import org.apache.weex.utils.FunctionParser;
  import org.apache.weex.utils.WXDataStructureUtil;
  import org.apache.weex.utils.WXExceptionUtils;
  import org.apache.weex.utils.WXLogUtils;
  import org.apache.weex.utils.WXUtils;
  import org.apache.weex.utils.WXViewUtils;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Map;
  
  /**
   * Created by furture on 2017/10/24.
   */
  
  public class TransformParser {
  
      public final static String WX_TRANSLATE = "translate";
      public final static String WX_TRANSLATE_X = "translateX";
      public final static String WX_TRANSLATE_Y = "translateY";
      public final static String WX_ROTATE = "rotate";
      public final static String WX_ROTATE_X ="rotateX";
      public final static String WX_ROTATE_Y ="rotateY";
      public final static String WX_ROTATE_Z ="rotateZ";
      public final static String WX_SCALE = "scale";
      public final static String WX_SCALE_X = "scaleX";
      public final static String WX_SCALE_Y = "scaleY";
  
      public final static String BACKGROUND_COLOR = Constants.Name.BACKGROUND_COLOR;
      public final static String WIDTH = Constants.Name.WIDTH;
      public final static String HEIGHT = Constants.Name.HEIGHT;
      public final static String TOP = "top";
      public final static String BOTTOM = "bottom";
      public final static String RIGHT = "right";
      public final static String LEFT = "left";
      public final static String CENTER = "center";
      private static final String HALF = "50%";
      private static final String FULL = "100%";
      private static final String ZERO = "0%";
      private static final String PX = "px";
      private static final String DEG = "deg";
      public static Map<String, List<Property<View,Float>>> wxToAndroidMap = new ArrayMap<>();
  
  
      static {
          wxToAndroidMap.put(WX_TRANSLATE, Arrays.asList
                  (View.TRANSLATION_X, View.TRANSLATION_Y));
          wxToAndroidMap.put(WX_TRANSLATE_X, Collections.singletonList(View.TRANSLATION_X));
          wxToAndroidMap.put(WX_TRANSLATE_Y, Collections.singletonList(View.TRANSLATION_Y));
          wxToAndroidMap.put(WX_ROTATE, Collections.singletonList(View.ROTATION));
          wxToAndroidMap.put(WX_ROTATE_Z, Collections.singletonList(View.ROTATION));
          wxToAndroidMap.put(WX_ROTATE_X, Collections.singletonList(View.ROTATION_X));
          wxToAndroidMap.put(WX_ROTATE_Y, Collections.singletonList(View.ROTATION_Y));
          wxToAndroidMap.put(WX_SCALE, Arrays.asList(View.SCALE_X, View.SCALE_Y));
          wxToAndroidMap.put(WX_SCALE_X, Collections.singletonList(View.SCALE_X));
          wxToAndroidMap.put(WX_SCALE_Y, Collections.singletonList(View.SCALE_Y));
          wxToAndroidMap.put(Constants.Name.PERSPECTIVE, Collections.singletonList(CameraDistanceProperty.getInstance()));
          wxToAndroidMap = Collections.unmodifiableMap(wxToAndroidMap);
      }
  
      public static PropertyValuesHolder[] toHolders(Map<Property<View,Float>, Float> transformMap){
          PropertyValuesHolder[]  holders = new PropertyValuesHolder[transformMap.size()];
          int i=0;
          for (Map.Entry<Property<View, Float>, Float> entry : transformMap.entrySet()) {
              holders[i] = PropertyValuesHolder.ofFloat(entry.getKey(), entry.getValue());
              i++;
          }
          return holders;
      }
  
      public static Map<Property<View,Float>, Float> parseTransForm(String instanceId, @Nullable String rawTransform, final int width,
                                                                    final int height, final int viewportW) {
          try{
  
              if (!TextUtils.isEmpty(rawTransform)) {
                  FunctionParser<Property<View,Float>, Float> parser = new FunctionParser<>
                          (rawTransform, new FunctionParser.Mapper<Property<View,Float>, Float>() {
                              @Override
                              public Map<Property<View,Float>, Float> map(String functionName, List<String> raw) {
                                  if (raw != null && !raw.isEmpty()) {
                                      if (wxToAndroidMap.containsKey(functionName)) {
                                          return convertParam(width, height,viewportW, wxToAndroidMap.get(functionName), raw);
                                      }
                                  }
                                  return new HashMap<>();
                              }
  
                              private Map<Property<View,Float>, Float> convertParam(int width, int height, int viewportW,
                                                                                    @NonNull List<Property<View,Float>> propertyList,
                                                                                    @NonNull List<String> rawValue) {
  
                                  Map<Property<View,Float>, Float> result = WXDataStructureUtil.newHashMapWithExpectedSize(propertyList.size());
                                  List<Float> convertedList = new ArrayList<>(propertyList.size());
                                  if (propertyList.contains(View.ROTATION) ||
                                          propertyList.contains(View.ROTATION_X) ||
                                          propertyList.contains(View.ROTATION_Y)) {
                                      convertedList.addAll(parseRotationZ(rawValue));
                                  }else if (propertyList.contains(View.TRANSLATION_X) ||
                                          propertyList.contains(View.TRANSLATION_Y)) {
                                      convertedList.addAll(parseTranslation(propertyList, width, height, rawValue,viewportW));
                                  } else if (propertyList.contains(View.SCALE_X) ||
                                          propertyList.contains(View.SCALE_Y)) {
                                      convertedList.addAll(parseScale(propertyList.size(), rawValue));
                                  }
                                  else if(propertyList.contains(CameraDistanceProperty.getInstance())){
                                      convertedList.add(parseCameraDistance(rawValue));
                                  }
                                  if (propertyList.size() == convertedList.size()) {
                                      for (int i = 0; i < propertyList.size(); i++) {
                                          result.put(propertyList.get(i), convertedList.get(i));
                                      }
                                  }
                                  return result;
                              }
  
                              private List<Float> parseScale(int size, @NonNull List<String> rawValue) {
                                  List<Float> convertedList = new ArrayList<>(rawValue.size() * 2);
                                  List<Float> rawFloat = new ArrayList<>(rawValue.size());
                                  for (String item : rawValue) {
                                      rawFloat.add(WXUtils.fastGetFloat(item));
                                  }
                                  convertedList.addAll(rawFloat);
                                  if (size != 1 && rawValue.size() == 1) {
                                      convertedList.addAll(rawFloat);
                                  }
                                  return convertedList;
                              }
  
                              private @NonNull
                              List<Float> parseRotationZ(@NonNull List<String> rawValue) {
                                  List<Float> convertedList = new ArrayList<>(1);
                                  int suffix;
                                  for (String raw : rawValue) {
                                      if ((suffix = raw.lastIndexOf(DEG)) != -1) {
                                          convertedList.add(WXUtils.fastGetFloat(raw.substring(0, suffix)));
                                      } else {
                                          convertedList.add((float) Math.toDegrees(WXUtils.fastGetFloat(raw)));
                                      }
                                  }
                                  return convertedList;
                              }
  
                              /**
                               * As "translate(50%, 25%)" or "translate(25px, 30px)" both are valid,
                               * parsing translate is complicated than other method.
                               * Add your waste time here if you try to optimize this method like {@link #parseScale(int, List)}
                               * Time: 0.5h
                               */
                              private List<Float> parseTranslation(List<Property<View,Float>> propertyList,
                                                                   int width, int height,
                                                                   @NonNull List<String> rawValue, int viewportW) {
                                  List<Float> convertedList = new ArrayList<>(2);
                                  String first = rawValue.get(0);
                                  if (propertyList.size() == 1) {
                                      parseSingleTranslation(propertyList, width, height, convertedList, first,viewportW);
                                  } else {
                                      parseDoubleTranslation(width, height, rawValue, convertedList, first,viewportW);
                                  }
                                  return convertedList;
                              }
  
                              private void parseSingleTranslation(List<Property<View,Float>> propertyList, int width, int height,
                                                                  List<Float> convertedList, String first, int viewportW) {
                                  if (propertyList.contains(View.TRANSLATION_X)) {
                                      convertedList.add(parsePercentOrPx(first, width,viewportW));
                                  } else if (propertyList.contains(View.TRANSLATION_Y)) {
                                      convertedList.add(parsePercentOrPx(first, height,viewportW));
                                  }
                              }
  
                              private void parseDoubleTranslation(int width, int height,
                                                                  @NonNull List<String> rawValue,
                                                                  List<Float> convertedList, String first, int viewportW) {
                                  String second;
                                  if (rawValue.size() == 1) {
                                      second = first;
                                  } else {
                                      second = rawValue.get(1);
                                  }
                                  convertedList.add(parsePercentOrPx(first, width,viewportW));
                                  convertedList.add(parsePercentOrPx(second, height,viewportW));
                              }
  
                              private Float parseCameraDistance(List<String> rawValue){
                                  float ret= Float.MAX_VALUE;
                                  if(rawValue.size() == 1){
                                      float value = WXViewUtils.getRealPxByWidth(WXUtils.getFloat(rawValue.get(0)), viewportW);
                                      float scale = WXEnvironment.getApplication().getResources().getDisplayMetrics().density;
                                      if (!Float.isNaN(value) && value > 0) {
                                          ret = value * scale;
                                      }
                                  }
                                  return ret;
                              }
                          });
                  return parser.parse();
              }
          }catch (Exception e){
              WXLogUtils.e("TransformParser", e);
              WXExceptionUtils.commitCriticalExceptionRT(instanceId,
                      WXErrorCode.WX_RENDER_ERR_TRANSITION,
                      "parse animation transition",
                      WXErrorCode.WX_RENDER_ERR_TRANSITION.getErrorMsg() + "parse transition error: " +  e.getMessage(),
                      null);
          }
          return new LinkedHashMap<>();
      }
  
      private static Pair<Float, Float> parsePivot(@Nullable String transformOrigin,
                                                   int width, int height, int viewportW) {
          if (!TextUtils.isEmpty(transformOrigin)) {
              int firstSpace = transformOrigin.indexOf(FunctionParser.SPACE);
              if (firstSpace != -1) {
                  int i = firstSpace;
                  for (; i < transformOrigin.length(); i++) {
                      if (transformOrigin.charAt(i) != FunctionParser.SPACE) {
                          break;
                      }
                  }
                  if (i < transformOrigin.length() && transformOrigin.charAt(i) != FunctionParser.SPACE) {
                      List<String> list = new ArrayList<>(2);
                      list.add(transformOrigin.substring(0, firstSpace).trim());
                      list.add(transformOrigin.substring(i, transformOrigin.length()).trim());
                      return parsePivot(list, width, height,viewportW);
                  }
              }
          }
          return null;
      }
  
      private static Pair<Float, Float> parsePivot(@NonNull List<String> list, int width, int height, int viewportW) {
          return new Pair<>(
                  parsePivotX(list.get(0), width,viewportW), parsePivotY(list.get(1), height,viewportW));
      }
  
      private static float parsePivotX(String x, int width, int viewportW) {
          String value = x;
          if (WXAnimationBean.Style.LEFT.equals(x)) {
              value = ZERO;
          } else if (WXAnimationBean.Style.RIGHT.equals(x)) {
              value = FULL;
          } else if (WXAnimationBean.Style.CENTER.equals(x)) {
              value = HALF;
          }
          return parsePercentOrPx(value, width,viewportW);
      }
  
      private static float parsePivotY(String y, int height, int viewportW) {
          String value = y;
          if (WXAnimationBean.Style.TOP.equals(y)) {
              value = ZERO;
          } else if (WXAnimationBean.Style.BOTTOM.equals(y)) {
              value = FULL;
          } else if (WXAnimationBean.Style.CENTER.equals(y)) {
              value = HALF;
          }
          return parsePercentOrPx(value, height,viewportW);
      }
  
      private static float parsePercentOrPx(String raw, int unit, int viewportW) {
          final int precision = 1;
          int suffix;
          if ((suffix = raw.lastIndexOf(WXUtils.PERCENT)) != -1) {
              return parsePercent(raw.substring(0, suffix), unit, precision);
          } else if ((suffix = raw.lastIndexOf(PX)) != -1) {
              return WXViewUtils.getRealPxByWidth(WXUtils.fastGetFloat(raw.substring(0, suffix), precision),viewportW);
          }
          return WXViewUtils.getRealPxByWidth(WXUtils.fastGetFloat(raw, precision),viewportW);
      }
  
      private static float parsePercent(String percent, int unit, int precision) {
          return WXUtils.fastGetFloat(percent, precision) / 100 * unit;
      }
  }
  