content: package com.alphawallet.token.entity;
  
  import java.math.BigInteger;
  import java.util.Map;
  import java.util.regex.Matcher;
  
  import static com.alphawallet.token.entity.TSSelection.decodeParam;
  
  /**
   * Created by JB on 21/05/2020.
   */
  public class TSFilterNode
  {
      public TSFilterNode parent = null;
      public TSFilterNode first = null;
      public TSFilterNode second = null;
      public FilterType type;
      boolean negate;
      public BigInteger value = null;
      String strValue = null;
      LogicState logic = LogicState.NONE;
  
      public TSFilterNode(String val, TSFilterNode p, FilterType t)
      {
          if (t == FilterType.ATTRIBUTE)
          {
              val = extractAttribute(val);
          }
  
          type = t;
          try
          {
              value = new BigInteger(val);
              strValue = val;
          }
          catch (Exception e)
          {
              if (val.equalsIgnoreCase("true"))
              {
                  logic = LogicState.TRUE;
              }
              else if (val.equalsIgnoreCase("false"))
              {
                  logic = LogicState.FALSE;
              }
              else
              {
                  strValue = val;
              }
              value = null;
          }
          parent = p;
      }
  
      public TSFilterNode(FilterType type, TSFilterNode p)
      {
          this.type = type;
          parent = p;
      }
  
      public boolean isLeafLogic()
      {
          return isLeafLogic(type);
      }
  
      public static boolean isLeafLogic(FilterType type)
      {
          switch (type)
          {
              case AND:
              case OR:
              case NOT:
                  return false;
              case LESS_THAN:
              case EQUAL:
              case GREATER_THAN:
              case GREATER_THAN_OR_EQUAL:
              case LESS_THAN_OR_EQUAL_TO:
                  return true;
              default:
              case VALUE:
                  return false;
          }
      }
  
      public boolean isNodeLogic()
      {
          switch (type)
          {
              case AND:
              case OR:
                  return true;
              default:
                  return false;
          }
      }
  
      LogicState evaluate(Map<String, TokenScriptResult.Attribute> attrs)
      {
          String valueLeftStr = getValue(first, attrs);
          String valueRightStr = getValue(second, attrs);
          boolean isBoolComparison = detemineBooleanComparison(valueLeftStr, valueRightStr);
          String valueSupplementalLeftStr = valueLeftStr;
          String valueSupplementalRightStr = valueRightStr;
  
          if (isBoolComparison)
          {
              valueSupplementalLeftStr = valueLeftStr.equalsIgnoreCase("true") ? "1" : valueLeftStr;
              valueSupplementalRightStr = valueRightStr.equalsIgnoreCase("true") ? "1" : valueRightStr;
          }
  
          BigInteger valueLeft = getBIValue(first, attrs);
          BigInteger valueRight = getBIValue(second, attrs);
  
          boolean bothSidesValues = valueLeft != null && valueRight != null;
          if (valueLeftStr == null || valueRightStr == null) return LogicState.FALSE;
  
          //perform comparison
          switch (type)
          {
              case EQUAL:
                  //compare strings
                  return compareLogic(valueLeftStr.equalsIgnoreCase(valueRightStr)
                          || (isBoolComparison && valueSupplementalLeftStr.equalsIgnoreCase(valueSupplementalRightStr)));
              case GREATER_THAN:
                  //both sides must be values
                  if (!bothSidesValues) return LogicState.FALSE;
                  return compareLogic(valueLeft.compareTo(valueRight) > 0);
              case LESS_THAN:
                  //both sides must be values
                  if (!bothSidesValues) return LogicState.FALSE;
                  return compareLogic(valueLeft.compareTo(valueRight) < 0);
              case GREATER_THAN_OR_EQUAL:
                  //both sides must be values
                  if (!bothSidesValues) return LogicState.FALSE;
                  return compareLogic(valueLeft.compareTo(valueRight) >= 0);
              case LESS_THAN_OR_EQUAL_TO:
                  //both sides must be values
                  if (!bothSidesValues) return LogicState.FALSE;
                  return compareLogic(valueLeft.compareTo(valueRight) <= 0);
              default:
                  // should have caught this previously
                  return LogicState.FALSE;
          }
      }
  
      private boolean detemineBooleanComparison(String valueLeftStr, String valueRightStr)
      {
          return valueLeftStr.equalsIgnoreCase("true")
                  || valueLeftStr.equalsIgnoreCase("false")
                  || valueRightStr.equalsIgnoreCase("true")
                  || valueRightStr.equalsIgnoreCase("false");
      }
  
      private LogicState compareLogic(boolean comparison)
      {
          if (comparison)
          {
              return negate ? LogicState.FALSE : LogicState.TRUE;
          }
          else
          {
              return negate ? LogicState.TRUE : LogicState.FALSE;
          }
      }
  
      private BigInteger getBIValue(TSFilterNode node, Map<String, TokenScriptResult.Attribute> attrs)
      {
          BigInteger returnValue = null;
          if (node.strValue != null && node.strValue.length() > 0)
          {
              TokenScriptResult.Attribute attr = attrs.get(node.strValue);
              if (attr != null)
              {
                  //found an attribute
                  returnValue = attr.value;
              }
              else
              {
                  returnValue = node.value;
              }
          }
  
          return returnValue;
      }
  
      private String getValue(TSFilterNode node, Map<String, TokenScriptResult.Attribute> attrs)
      {
          String returnValue = null;
  
          if (node.logic != null && node.logic != LogicState.NONE)
          {
              returnValue = node.logic.toString();
          }
          else if (node.type == FilterType.ATTRIBUTE)
          {
              TokenScriptResult.Attribute attr = attrs.get(node.strValue);
              if (attr != null)
              {
                  //found an attribute
                  returnValue = attr.text;
              }
          }
          else if (node.strValue != null && node.strValue.length() > 0)
          {
              returnValue = node.strValue;
          }
  
          return returnValue;
      }
  
      boolean isEvaluated()
      {
          if ((isNodeLogic() || isLeafLogic()) && logic != TSFilterNode.LogicState.NONE)
          {
              return true;
          }
          else
          {
              return type == FilterType.VALUE;
          }
      }
  
      LogicState evaluate()
      {
          if (!isNodeLogic()) return LogicState.NONE;
  
          boolean eval = first.logic == LogicState.TRUE && second.logic == LogicState.TRUE;
          if (eval)
          {
              return negate ? LogicState.FALSE : LogicState.TRUE;
          }
          else
          {
              return negate ? LogicState.TRUE : LogicState.FALSE;
          }
      }
  
      private String extractAttribute(String val)
      {
          Matcher matcher = decodeParam.matcher(val);
          if (matcher.find() && !matcher.group(1).isEmpty())
          {
              //found an attribute param
              return matcher.group(1);
          }
          else
          {
              return val;
          }
      }
  
      enum LogicState
      {
          NONE, TRUE, FALSE
      }
  }
  