/*
 * 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.web.validators;

import cz.csu.sdmx.validation.model.LogicCondition;
import cz.csu.sdmx.validation.model.Dimension;
import cz.csu.sdmx.validation.model.Param;
import cz.csu.sdmx.validation.model.dsds.Dsd;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

/**
 *
 * @author themi
 */
public class LogicConditionValidator implements Validator{

    private Dsd dsd;

    public LogicConditionValidator(Dsd dsd) {
        this.dsd = dsd;
    }
    
    public LogicConditionValidator() {
        this.dsd = null;
    }
    
    @Override
    public boolean supports(Class<?> type) {
        return LogicCondition.class.isAssignableFrom(type);
    }

    @Override
    public void validate(Object o, Errors errors) {
        LogicCondition lc = (LogicCondition) o;
        
        String expression = lc.getExpression();
        
        List<String> errorsList = validateCondition(expression);
        for(String error : errorsList)
            errors.rejectValue("expression", "default", error);
        
        if(lc.getObsValAttrName() == null || lc.getObsValAttrName().isEmpty()){
            errors.rejectValue("expression", "default", "Obs attribute value have to be chosen.");
        }
      
    }
    
    public List<String> validateCondition(String expression){
        List<String> errors = new ArrayList<>();
        
        if(expression == null || expression.isEmpty())
            errors.add("Expression cannot be empty.");
        
        if(!errors.isEmpty())
            return errors;
        
        count("(", ")", expression, errors);
        count("[", "]", expression, errors);
        if(!errors.isEmpty())
            return errors;
        
        Matcher matcher = Pattern.compile("\\[(.*?)\\]").matcher(expression);
        while(matcher.find()){
            if(!matcher.group(1).matches("^.+=.+$"))
                errors.add("'"+matcher.group()+"' does not contain dimension's name or value.");
        }
        if(!errors.isEmpty())
            return errors;
        
        String copy = expression.replaceAll("\\[(.*?)\\]", "0");
        
        matcher = Pattern.compile("[0-9]+(=|>|<|>=|<=|!=)[0-9]+").matcher(copy.replaceAll("\\s", ""));
        if(!matcher.find()){     
            errors.add("Condiditon has no valid comparative character. [=,<,>,>=,<=,!=]");
        }
            
        matcher = Pattern.compile("[^0-9+/\\-*=<>\\s]{1,}|[+\\-/*]{2,}|^[*/]|[/+\\-*]$|[0-9]\\s[0-9]").matcher(copy);
        while(matcher.find()){
            int start = matcher.start()+1;
            int end = matcher.end();
            String position = String.valueOf((start == end) ? start : start + " - " + end);
            errors.add("'"+matcher.group()+"' are not allowed chars in condidion at position ["+position+"].");
        }
        if(dsd == null)
            return errors;
        
        LogicCondition condition = new LogicCondition();
        condition.setExpression(expression);

        List<Param> params = condition.getParams();
        List<Dimension> dimensions = dsd.getSeriesDimensions();
        for(Param param : params){
            boolean found = false;
            Iterator<Dimension> iterator = dimensions.iterator();
            while(iterator.hasNext() && !found){
                Dimension dimension = iterator.next();
                if(dimension.getName().equals(param.getName())){
                    found = true;
                    if(!dimension.isValidValue(param.getValue()))
                        errors.add("Value["+param.getValue()+"] is not valid for dimension["+param.getName()+"]");
                }
            }
            if (!found) {
               errors.add("Dimension["+param.getName()+"] is not valid for this DSD."); 
            }
        }
        
        return errors;
    }
    
    private void count(String toCount, String toCountOpposite, String condition, List<String> errors){
        int count = 0;
        
        for(int i = 0; i < condition.length(); i++){
            String letter = String.valueOf(condition.charAt(i));
            if (letter.equals(toCount)) {
                count++;
            }
            if (letter.equals(toCountOpposite)) {
                count--;
            }
        }
        
        if(count > 0)
            errors.add("There is not enough of '"+toCountOpposite+"'");
        if(count < 0)
            errors.add("There is not enough of '"+toCount+"'");
    }
}
