package io.burt.jmespath.function;

import java.util.Iterator;
import java.util.List;

import io.burt.jmespath.Adapter;
import io.burt.jmespath.JmesPathType;
import io.burt.jmespath.Expression;

/**
 * Helper base class for higher order comparison functions like max_by and min_by.
 */
public abstract class CompareByFunction extends BaseFunction {
  public CompareByFunction() {
    super(
      ArgumentConstraints.arrayOf(ArgumentConstraints.typeOf(JmesPathType.OBJECT)),
      ArgumentConstraints.expression()
    );
  }

  /**
   * Subclasses override this method to decide whether the greatest or least
   * element sorts first.
   */
  protected abstract boolean sortsBefore(int compareResult);

  @Override
  protected <T> T callFunction(Adapter<T> runtime, List<FunctionArgument<T>> arguments) {
    Iterator<T> elements = runtime.toList(arguments.get(0).value()).iterator();
    Expression<T> expression = arguments.get(1).expression();
    if (elements.hasNext()) {
      T result = elements.next();
      T resultValue = expression.search(result);
      boolean expectNumbers = true;
      if (runtime.typeOf(resultValue) == JmesPathType.STRING) {
        expectNumbers = false;
      } else if (runtime.typeOf(resultValue) != JmesPathType.NUMBER) {
        throw new ArgumentTypeException(name(), "number or string", runtime.typeOf(resultValue).toString());
      }
      while (elements.hasNext()) {
        T candidate = elements.next();
        T candidateValue = expression.search(candidate);
        JmesPathType candidateType = runtime.typeOf(candidateValue);
        if (expectNumbers && candidateType != JmesPathType.NUMBER) {
          throw new ArgumentTypeException(name(), "number", candidateType.toString());
        } else if (!expectNumbers && candidateType != JmesPathType.STRING) {
          throw new ArgumentTypeException(name(), "string", candidateType.toString());
        }
        if (sortsBefore(runtime.compare(candidateValue, resultValue))) {
          result = candidate;
          resultValue = candidateValue;
        }
      }
      return result;
    } else {
      return runtime.createNull();
    }
  }
}

@@ -1087,16 +1087,549 @@
 s = 
-tr
+expectNumbers(runtime, resultVal
 ue
+)
 ;%0A  
+    while (elements.hasNext()) %7B%0A        T candidate = elements.next();%0A        T candidateValue = expression.search(candidate);%0A        checkType(runtime, candidateValue, expectNumbers);%0A        if (sortsBefore(runtime.compare(candidateValue, resultValue))) %7B%0A          result = candidate;%0A          resultValue = candidateValue;%0A        %7D%0A      %7D%0A      return result;%0A    %7D else %7B%0A      return runtime.createNull();%0A    %7D%0A  %7D%0A%0A  private %3CT%3E boolean expectNumbers(Adapter%3CT%3E runtime, T resultValue) %7B%0A
     
@@ -1696,33 +1696,22 @@
     
-  expectNumbers =
+return
  false;%0A
     
@@ -1706,18 +1706,16 @@
  false;%0A
-  
     %7D el
@@ -1771,26 +1771,24 @@
 e.NUMBER) %7B%0A
-  
       throw 
@@ -1890,147 +1890,124 @@
     
-  %7D%0A      while (elements.hasNext()) %7B%0A        T candidate = elements.next();%0A        T candidateValue = expression.search(candidate);%0A    
+%7D%0A    return true;%0A  %7D%0A%0A  private %3CT%3E void checkType(Adapter%3CT%3E runtime, T candidateValue, boolean expectNumbers) %7B%0A
     
@@ -2063,28 +2063,24 @@
 dateValue);%0A
-    
     if (expe
@@ -2130,36 +2130,32 @@
 NUMBER) %7B%0A      
-    
 throw new Argume
@@ -2219,20 +2219,16 @@
 ));%0A    
-   
- 
 %7D else i
@@ -2284,28 +2284,24 @@
 e.STRING) %7B%0A
-    
       throw 
@@ -2375,248 +2375,8 @@
 ));%0A
-        %7D%0A        if (sortsBefore(runtime.compare(candidateValue, resultValue))) %7B%0A          result = candidate;%0A          resultValue = candidateValue;%0A        %7D%0A      %7D%0A      return result;%0A    %7D else %7B%0A      return runtime.createNull();%0A
     

Decrease complexity in CompareByFunction