package org.cqframework.cql.cql2elm.model;

import org.cqframework.cql.elm.tracking.DataType;

import java.util.*;

public class OperatorMap {
    private Map<String, OperatorEntry> operators = new HashMap<>();

    public void addOperator(Operator operator) {
        OperatorEntry entry = getEntry(operator.getName());
        entry.addOperator(operator);
    }

    private OperatorEntry getEntry(String operatorName) {
        if (operatorName == null || operatorName.equals("")) {
            throw new IllegalArgumentException("operatorName is null or empty.");
        }

        OperatorEntry entry = operators.get(operatorName);
        if (entry == null) {
            entry = new OperatorEntry(operatorName);
            operators.put(operatorName, entry);
        }

        return entry;
    }

    public OperatorResolution resolveOperator(CallContext callContext, ConversionMap conversionMap) {
        OperatorEntry entry = getEntry(callContext.getOperatorName());
        List<OperatorResolution> results = entry.resolve(callContext, conversionMap);

        // Score each resolution and return the lowest score
        // Duplicate scores indicate ambiguous match
        OperatorResolution result = null;
        if (results != null) {
            int lowestScore = Integer.MAX_VALUE;
            for (OperatorResolution resolution : results) {
                // for each operand
                    // exact match = 0
                    // subtype = 1
                    // conversion = 2
                Iterator<DataType> operands = resolution.getOperator().getSignature().getOperandTypes().iterator();
                Iterator<DataType> callOperands = callContext.getSignature().getOperandTypes().iterator();
                Iterator<Conversion> conversions = resolution.hasConversions() ? resolution.getConversions().iterator() : null;
                int score = 0;
                while (operands.hasNext()) {
                    DataType operand = operands.next();
                    DataType callOperand = callOperands.next();
                    Conversion conversion = conversions != null ? conversions.next() : null;
                    if (operand.equals(callOperand)) {
                        score += 0;
                    }
                    else if (operand.isSuperTypeOf(callOperand)) {
                        score += 1;
                    }
                    else if (conversion != null) {
                        score += 2;
                    }
                }

                if (score < lowestScore) {
                    lowestScore = score;
                    result = resolution;
                }
                else if (score == lowestScore) {
                    throw new IllegalArgumentException(String.format("Call to operator %s with signature %s is ambiguous between %s and %s.",
                            callContext.getOperatorName(), callContext.getSignature(),
                            result.getOperator().getSignature(), resolution.getOperator().getSignature()));
                }
            }
        }

        return result;
    }
}

@@ -1297,16 +1297,95 @@
 _VALUE;%0A
+            List%3COperatorResolution%3E lowestScoringResults = new ArrayList%3C%3E();%0A
         
@@ -2694,25 +2694,91 @@
         
-result = 
+lowestScoringResults.clear();%0A                    lowestScoringResults.add(
 resoluti
@@ -2779,16 +2779,17 @@
 solution
+)
 ;%0A      
@@ -2873,251 +2873,478 @@
     
-throw new IllegalArgumentException(String.format(%22Call to operator %25s with signature %25s is ambiguous between %25s and %25s.%22,%0A                            callContext.getOperatorName(), callContext.getSignature(),%0A                            result
+lowestScoringResults.add(resolution);%0A                %7D%0A            %7D%0A%0A            if (lowestScoringResults.size() %3E 1) %7B%0A                StringBuilder message = new StringBuilder(%22Call to operator %22).append(callContext.getOperatorName())%0A                        .append(callContext.getSignature()).append(%22 is ambiguous with: %22);%0A                for (OperatorResolution resolution : lowestScoringResults) %7B%0A                    message.append(%22%5Cn  - %22).append(resolution
 .get
@@ -3361,21 +3361,23 @@
 .get
-Signature(), 
+Name()).append(
 reso
@@ -3412,17 +3412,16 @@
 ature())
-)
 ;%0A      
@@ -3423,32 +3423,191 @@
                %7D
+%0A                throw new IllegalArgumentException(message.toString());%0A            %7D%0A            else %7B%0A                result = lowestScoringResults.get(0);
 %0A            %7D%0A 

Fix bug in operator resolution failing before considering all possible resolutions