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.richtext.node;
  
  import static org.apache.weex.dom.WXStyle.UNSET;
  
  import android.content.Context;
  import android.graphics.Color;
  import android.support.annotation.NonNull;
  import android.support.annotation.Nullable;
  import android.support.v4.util.ArrayMap;
  import android.text.Spannable;
  import android.text.SpannableString;
  import android.text.SpannableStringBuilder;
  import android.text.Spanned;
  import android.text.TextUtils;
  import android.text.style.AbsoluteSizeSpan;
  import android.text.style.BackgroundColorSpan;
  import android.text.style.ForegroundColorSpan;
  import com.alibaba.fastjson.JSON;
  import com.alibaba.fastjson.JSONArray;
  import com.alibaba.fastjson.JSONObject;
  import org.apache.weex.WXSDKInstance;
  import org.apache.weex.WXSDKManager;
  import org.apache.weex.common.Constants;
  import org.apache.weex.dom.WXCustomStyleSpan;
  import org.apache.weex.dom.WXStyle;
  import org.apache.weex.utils.WXLogUtils;
  import org.apache.weex.utils.WXResourceUtils;
  import java.util.ArrayList;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map;
  
  public abstract class RichTextNode {
  
      public static final String TYPE = "type";
      public static final String STYLE = "style";
      public static final String ATTR = "attr";
      public static final String CHILDREN = "children";
      public static final String VALUE = Constants.Name.VALUE;
      public static final String ITEM_CLICK="itemclick";
      public static final String PSEUDO_REF="pseudoRef";
      private static final int MAX_LEVEL = Spanned.SPAN_PRIORITY >> Spanned.SPAN_PRIORITY_SHIFT;
  
      protected final Context mContext;
      protected final String mInstanceId;
      protected final String mComponentRef;
      protected final String mRef;
      protected Map<String, Object> style;
      protected Map<String, Object> attr;
      protected List<RichTextNode> children;
  
      protected RichTextNode(Context context, String instanceId, String componentRef) {
          mContext = context;
          mInstanceId = instanceId;
          mComponentRef = componentRef;
          mRef = null;
      }
      protected RichTextNode(Context context, String instanceId, String componentRef, String ref, Map<String,Object> styles, Map<String,Object> attrs) {
          mContext = context;
          mInstanceId = instanceId;
          mComponentRef = componentRef;
          mRef = ref;
          if(styles != null){
              style = styles;
          }
          else {
              style = new ArrayMap<>(0);
          }
          if (attrs != null) {
              attr = attrs;
          } else {
              attr = new ArrayMap<>(0);
          }
          children = new LinkedList<>();
      }
  
      public static
      @NonNull
      Spannable parse(@NonNull Context context, @NonNull String instanceId, @NonNull String componentRef, String json) {
          JSONArray jsonArray = JSON.parseArray(json);
          JSONObject jsonObject;
          List<RichTextNode> nodes;
          RichTextNode node;
          if (jsonArray != null && !jsonArray.isEmpty()) {
              nodes = new ArrayList<>(jsonArray.size());
              for (int i = 0; i < jsonArray.size(); i++) {
                  jsonObject = jsonArray.getJSONObject(i);
                  if (jsonObject != null) {
                      node = RichTextNodeManager.createRichTextNode(context, instanceId, componentRef, jsonObject);
                      if (node != null) {
                          nodes.add(node);
                      }
                  }
              }
              return parse(nodes);
          }
          return new SpannableString("");
      }
  
      public static int createSpanFlag(int level) {
          return createPriorityFlag(level) | Spanned.SPAN_INCLUSIVE_EXCLUSIVE;
      }
  
      @Override
      public abstract String toString();
  
      protected abstract boolean isInternalNode();
  
      public String getRef(){
          return mRef;
      }
  
      final void parse(@NonNull Context context, @NonNull String instanceId, @NonNull String componentRef, JSONObject jsonObject) {
          JSONObject jsonStyle, jsonAttr, child;
          JSONArray jsonChildren;
          RichTextNode node;
          if ((jsonStyle = jsonObject.getJSONObject(STYLE)) != null) {
              style = new ArrayMap<>();
              style.putAll(jsonStyle);
          } else {
              style = new ArrayMap<>(0);
          }
  
          if ((jsonAttr = jsonObject.getJSONObject(ATTR)) != null) {
              attr = new ArrayMap<>(jsonAttr.size());
              attr.putAll(jsonAttr);
          } else {
              attr = new ArrayMap<>(0);
          }
  
          if ((jsonChildren=jsonObject.getJSONArray(CHILDREN))!=null) {
              children = new ArrayList<>(jsonChildren.size());
              for (int i = 0; i < jsonChildren.size(); i++) {
                  child = jsonChildren.getJSONObject(i);
                  node = RichTextNodeManager.createRichTextNode(context, instanceId, componentRef, child);
                  if (node != null) {
                      children.add(node);
                  }
              }
          } else {
              children = new ArrayList<>(0);
          }
      }
      public void addChildNode(RichTextNode child){
          if(children == null){
              children = new LinkedList<>();
          }
          if(child != null && isInternalNode()){
              children.add(child);
          }
      }
      public void removeChildNode(String ref){
          if(children != null && !children.isEmpty() && !TextUtils.isEmpty(ref)){
              try {
                  for (RichTextNode child : children) {
                      if (TextUtils.equals(child.mRef, ref)) {
                          children.remove(child);
                      }
                  }
              }catch(Exception e){
                  WXLogUtils.getStackTrace(e);
              }
          }
      }
      public void updateStyles(Map<String,Object> styles){
          if(styles != null && !styles.isEmpty()){
              style.putAll(styles);
          }
      }
      public void updateAttrs(Map<String,Object> attrs){
          if(attr != null && !attrs.isEmpty()){
              attr.putAll(attrs);
          }
      }
  
      protected void updateSpans(SpannableStringBuilder spannableStringBuilder, int level) {
          WXSDKInstance instance = WXSDKManager.getInstance().getSDKInstance(mInstanceId);
          if (style != null && instance != null) {
              List<Object> spans = new LinkedList<>();
  
              WXCustomStyleSpan customStyleSpan = createCustomStyleSpan();
              if (customStyleSpan != null) {
                  spans.add(customStyleSpan);
              }
  
              if (style.containsKey(Constants.Name.FONT_SIZE)) {
                  spans.add(new AbsoluteSizeSpan(WXStyle.getFontSize(style, instance.getInstanceViewPortWidth())));
              }
  
              if (style.containsKey(Constants.Name.BACKGROUND_COLOR)) {
                  int color = WXResourceUtils.getColor(style.get(Constants.Name.BACKGROUND_COLOR).toString(),
                      Color.TRANSPARENT);
                  if (color != Color.TRANSPARENT) {
                      spans.add(new BackgroundColorSpan(color));
                  }
              }
  
              if (style.containsKey(Constants.Name.COLOR)) {
                  spans.add(new ForegroundColorSpan(WXResourceUtils.getColor(WXStyle.getTextColor(style))));
              }
  
              int spanFlag = createSpanFlag(level);
              for (Object span : spans) {
                  spannableStringBuilder.setSpan(span, 0, spannableStringBuilder.length(), spanFlag);
              }
          }
      }
  
      private static int createPriorityFlag(int level) {
          return level <= MAX_LEVEL ?
              (MAX_LEVEL - level) << Spanned.SPAN_PRIORITY_SHIFT :
              MAX_LEVEL << Spanned.SPAN_PRIORITY_SHIFT;
      }
  
  
      private static
      @NonNull
      Spannable parse(@NonNull List<RichTextNode> list) {
          SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
          for (RichTextNode richTextNode : list) {
              spannableStringBuilder.append(richTextNode.toSpan(1));
          }
          return spannableStringBuilder;
      }
  
      public Spannable toSpan(int level) {
          SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
          spannableStringBuilder.append(toString());
          if (isInternalNode() && children != null) {
              for (RichTextNode child : children) {
                  spannableStringBuilder.append(child.toSpan(level + 1));
              }
          }
          updateSpans(spannableStringBuilder, level);
          return spannableStringBuilder;
      }
  
      private
      @Nullable
      WXCustomStyleSpan createCustomStyleSpan() {
          int fontWeight = UNSET, fontStyle = UNSET;
          String fontFamily = null;
          if (style.containsKey(Constants.Name.FONT_WEIGHT)) {
              fontWeight = WXStyle.getFontWeight(style);
          }
          if (style.containsKey(Constants.Name.FONT_STYLE)) {
              fontStyle = WXStyle.getFontStyle(style);
          }
          if (style.containsKey(Constants.Name.FONT_FAMILY)) {
              fontFamily = WXStyle.getFontFamily(style);
          }
          if (fontWeight != UNSET
              || fontStyle != UNSET
              || fontFamily != null) {
              return new WXCustomStyleSpan(fontStyle, fontWeight, fontFamily);
          } else {
              return null;
          }
      }
      public RichTextNode findRichNode(String ref){
          RichTextNode theNode;
          if(mRef != null && TextUtils.equals(mRef,ref)){
              return this;
          }
          if(children != null && !children.isEmpty()){
              for (RichTextNode child:children) {
                  if((theNode = child.findRichNode(ref)) != null)
                      return theNode;
              }
          }
          return null;
      }
  }
  