/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.csu.sdmx.validation;

import cz.csu.sdmx.validation.model.Expression;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 * @author themi
 */ 
public class ExpressionEvaluator {
    public static final String COMPARATORS_REGEX = "(!=|>=|<=|=|>|<)";
    
    public static boolean evaluate(Expression expression) {
        List<String> comparators = getExpressionComparators(expression.getExpression());
        String[] expressionParts = splitExpression(expression.getExpression());
        Double[] expressionPartsResults = evaluateParts(expressionParts);
        
        for(int i = 0; i < comparators.size(); i++){
            String comparator = comparators.get(i);
            Double ep1 = expressionPartsResults[i];
            Double ep2 = expressionPartsResults[i+1];
            if(compareExpressionParts(comparator,ep1, ep2))
                return false;
        }
        return true;
    }
    
    private static boolean compareExpressionParts(String comparator, Double ep1, Double ep2) {
        if(comparator.equals("=")){
            return Objects.equals(ep1, ep2);
        }
        if(comparator.equals("<")){
            return ep1 < ep2;
        }
        if(comparator.equals(">")){
            return ep1 > ep2;
        }
        if(comparator.equals("<=")){
            return ep1 <= ep2;
        }
        if(comparator.equals(">=")){
            return ep1 >= ep2;
        }
        if(comparator.equals("!=")){
            return !Objects.equals(ep1, ep2);
        }
        
        return false;
    }
    
    private static String[] splitExpression(String expression) {
        //splits expression by matching comparators
        return Pattern.compile(COMPARATORS_REGEX).split(expression);
    }

    private static List<String> getExpressionComparators(String expression) {
        //find all tokens matching comparator regex and return them
        List<String> delimiters = new ArrayList<>();
        Matcher m = Pattern.compile(COMPARATORS_REGEX).matcher(expression);
        while (m.find())
            delimiters.add(m.group());

        return delimiters;
    }

    private static Double[] evaluateParts(String[] expressionParts) {
        Double[] results = new Double[expressionParts.length];
        //convert and evaluate all parts of expression
        for (int i = 0; i < expressionParts.length; i++) {
            Stack<String> postfix = ExpressionConverter.infixToRPN(expressionParts[i]);
            results[i] = evaluatePostfix(postfix);
        }
        return results;
    }
    
    private static double evaluatePostfix(Stack<String> postfixExpression){
        Stack<Double> operandStack = new Stack<>();
        
        //for all expression chars/tokens
        for(String operand : postfixExpression){
            //if is number, add to stack
            if(operand.matches("-?\\d+(\\.\\d+)?")){
                operandStack.push(Double.parseDouble(operand));
            }else{
                //otherwise it is operator
                //evaluate and push on stack
                Double result = evaluateByOperator(operand, operandStack);
                operandStack.push(result);
            }
        }
        
        //return result which should be the only operand remaining in stack
        return operandStack.pop();
    }

    private static Double evaluateByOperator(String operator, Stack<Double> operandStack) {
        //get 2 last numbers from stack
        Double op1 = operandStack.pop();
        Double op2 = operandStack.pop();
        
        //evaluate
        if(operator.equals("+")){
            return op2 + op1;
        }
        
        if(operator.equals("-")){
            return op2 - op1;
        }
        
        if(operator.equals("*")){
            return op2 * op1;
        }
        
        if(operator.equals("/")){
            return op2 / op1;
        }
        
        if(operator.equals("^")){
            return Math.pow(op2, op1);
        }
        
        if(operator.equals("%")){
            return op2 % op1;
        }
        
        //if no valid operator, return null
        //should not occur because all conditions are validated
        return null;
    }
    
}
