package io.burt.jmespath.function;

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

import io.burt.jmespath.Adapter;
import io.burt.jmespath.node.JmesPathNode;

public abstract class CompareByFunction extends JmesPathFunction {
  public CompareByFunction() {
    super(2, 2);
  }

  protected abstract boolean sortsBefore(int compareResult);

  @Override
  protected <T> T internalCall(Adapter<T> adapter, List<ExpressionOrValue<T>> arguments) {
    T array = arguments.get(0).value();
    JmesPathNode expression = arguments.get(1).expression();
    if (arguments.get(0).isExpression()) {
      throw new ArgumentTypeException(name(), "array of objects", "expression");
    } else if (!adapter.isArray(array)) {
      throw new ArgumentTypeException(name(), "array of objects", adapter.typeOf(array));
    }
    if (arguments.get(1).isValue()) {
      throw new ArgumentTypeException(name(), "expression", adapter.typeOf(arguments.get(1).value()));
    }
    Iterator<T> elements = adapter.toList(array).iterator();
    if (elements.hasNext()) {
      T result = elements.next();
      T resultValue = expression.evaluate(adapter, result);
      boolean expectNumbers = true;
      if (adapter.isString(resultValue)) {
        expectNumbers = false;
      } else if (!adapter.isNumber(resultValue)) {
        throw new ArgumentTypeException(name(), "number or string", adapter.typeOf(resultValue));
      }
      while (elements.hasNext()) {
        T candidate = elements.next();
        T candidateValue = expression.evaluate(adapter, candidate);
        if ((expectNumbers && !adapter.isNumber(candidateValue)) || (!expectNumbers && !adapter.isString(candidateValue))) {
          throw new ArgumentTypeException(name(), expectNumbers ? "number" : "string", adapter.typeOf(resultValue));
        }
        if (sortsBefore(adapter.compare(candidateValue, resultValue))) {
          result = candidate;
          resultValue = candidateValue;
        }
      }
      return result;
    } else {
      return adapter.createNull();
    }
  }
}

@@ -478,71 +478,102 @@
     
-T array = arguments.get(0).value();%0A    JmesPathNode expression
+ExpressionOrValue%3CT%3E firstArgument = arguments.get(0);%0A    ExpressionOrValue%3CT%3E secondArgument
  = a
@@ -591,21 +591,8 @@
 t(1)
-.expression()
 ;%0A  
@@ -597,32 +597,29 @@
     if (
-a
+firstA
 rgument
-s.get(0)
 .isExpre
@@ -719,40 +719,46 @@
    %7D
- else
+%0A   
  if (!
-adapter.isArray(array
+secondArgument.isExpression(
 )) %7B
@@ -805,32 +805,26 @@
 ame(), %22
-array of objects
+expression
 %22, adapt
@@ -839,54 +839,166 @@
 f(ar
-ray));%0A    %7D%0A    if (arguments.get(1).isValue(
+guments.get(1).value()));%0A    %7D%0A    T array = firstArgument.value();%0A    JmesPathNode expression = secondArgument.expression();%0A    if (!adapter.isArray(array
 )) %7B
@@ -1041,34 +1041,40 @@
 on(name(), %22
-expression
+array of objects
 %22, adapter.t
@@ -1081,38 +1081,19 @@
 ypeOf(ar
-guments.get(1).value()
+ray
 ));%0A    

Refactor the type checking in CompareByFunction