/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 * Modified by
 * @author  Data Virtuality GmbH
 * @date    $Date$
 *  
 */

package org.teiid.query.processor.eval;

import static org.junit.jupiter.api.Assertions.*;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.junit.jupiter.api.Test;
import org.teiid.api.exception.query.ExpressionEvaluationException;
import org.teiid.common.buffer.BlockedException;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.query.eval.Evaluator;
import org.teiid.query.sql.lang.*;
import org.teiid.query.sql.symbol.Constant;
import org.teiid.query.sql.symbol.ElementSymbol;
import org.teiid.query.sql.util.ValueIterator;
import org.teiid.query.util.CommandContext;


public class TestCriteriaEvaluator {

	// ################################## TEST HELPERS ################################
	
    private void helpTestMatch(String value, String pattern, char escape, boolean negated, boolean expectedMatch) throws ExpressionEvaluationException, BlockedException, TeiidComponentException {
        MatchCriteria crit = new MatchCriteria(new Constant(value), new Constant(pattern), escape);
        crit.setNegated(negated);
        boolean actualMatch = Evaluator.evaluate(crit);
        // Compare actual and expected match
        assertEquals(expectedMatch, actualMatch, "Match criteria test failed for value=[" + value + "], pattern=[" + pattern + "], hasEscape=" + (escape != MatchCriteria.NULL_ESCAPE_CHAR) + ": "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
    }
    
    private void helpTestMatch(String value, String pattern, char escape, boolean expectedMatch) throws ExpressionEvaluationException, BlockedException, TeiidComponentException {
        helpTestMatch(value, pattern, escape, false, expectedMatch);
    }
	
    private void helpTestIsNull(String value, boolean negated, boolean expectedMatch) throws ExpressionEvaluationException, BlockedException, TeiidComponentException {
        IsNullCriteria criteria = new IsNullCriteria(new Constant(value));
        criteria.setNegated(negated);
        
        boolean result = Evaluator.evaluate(criteria);
        assertEquals(expectedMatch, result, "Result did not match expected value"); //$NON-NLS-1$
    }
    
    private void helpTestSetCriteria(int value, boolean negated, boolean expectedMatch) throws ExpressionEvaluationException, BlockedException, TeiidComponentException {
        helpTestSetCriteria(new Integer(value), negated, expectedMatch);
    }
    
    private void helpTestSetCriteria(Integer value, boolean negated, boolean expectedMatch) throws ExpressionEvaluationException, BlockedException, TeiidComponentException {
        Collection constants = new ArrayList(2);
        constants.add(new Constant(new Integer(1000)));
        constants.add(new Constant(new Integer(5000)));
        SetCriteria crit = new SetCriteria(new Constant(value), constants);
        crit.setNegated(negated);
        boolean result = Evaluator.evaluate(crit);
        assertEquals(expectedMatch, result, "Result did not match expected value"); //$NON-NLS-1$
    }
        
    private void helpTestCompareSubqueryCriteria(Criteria crit, Boolean expectedResult, final Collection<Object> values) throws ExpressionEvaluationException, BlockedException, TeiidComponentException{
        
        Map elementMap = new HashMap();
        ElementSymbol e1 = new ElementSymbol("e1"); //$NON-NLS-1$
        elementMap.put(e1, new Integer(0));
        
        List tuple = Arrays.asList(new String[]{"a"}); //$NON-NLS-1$
        CommandContext cc = new CommandContext();
        assertEquals(expectedResult, new Evaluator(elementMap, null, cc) {
        	@Override
        	protected ValueIterator evaluateSubquery(
        			SubqueryContainer container, List tuple)
        			throws TeiidProcessingException, BlockedException,
        			TeiidComponentException {
        		return new CollectionValueIterator(values);
        	}
        }.evaluateTVL(crit, tuple));
    }

    private SubqueryCompareCriteria helpGetCompareSubqueryCriteria(int operator, int predicateQuantifier){
        ElementSymbol e1 = new ElementSymbol("e1"); //$NON-NLS-1$
        SubqueryCompareCriteria crit = new SubqueryCompareCriteria(e1, new Query(), operator, predicateQuantifier);
        return crit;        
    }

	// ################################## ACTUAL TESTS ################################
	
    @Test public void testIsNull1() throws Exception {
        helpTestIsNull(null, false, true);
    }
    
    @Test public void testIsNull2() throws Exception {
        helpTestIsNull(null, true, false);
    }
    
    @Test public void testIsNull3() throws Exception {
        helpTestIsNull("x", false, false); //$NON-NLS-1$
    }
    
    @Test public void testIsNull4() throws Exception {
        helpTestIsNull("x", true, true); //$NON-NLS-1$
    }
    
	@Test public void testMatch1() throws Exception {
		helpTestMatch("", "", MatchCriteria.NULL_ESCAPE_CHAR, true);		 //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch2() throws Exception {
		helpTestMatch("x", "", MatchCriteria.NULL_ESCAPE_CHAR, false);		 //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch3() throws Exception {
		helpTestMatch("", "%", MatchCriteria.NULL_ESCAPE_CHAR, true);		 //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch4() throws Exception {
		helpTestMatch("x", "%", MatchCriteria.NULL_ESCAPE_CHAR, true);		 //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch5() throws Exception {
		helpTestMatch("xx", "%", MatchCriteria.NULL_ESCAPE_CHAR, true);		 //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch7() throws Exception {
		helpTestMatch("a", "a%", MatchCriteria.NULL_ESCAPE_CHAR, true);		 //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch8() throws Exception {
		helpTestMatch("ab", "a%", MatchCriteria.NULL_ESCAPE_CHAR, true);		 //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch9() throws Exception {
		helpTestMatch("a.", "a%", MatchCriteria.NULL_ESCAPE_CHAR, true);		 //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch11() throws Exception {
		helpTestMatch("ax.", "a%", MatchCriteria.NULL_ESCAPE_CHAR, true);		 //$NON-NLS-1$ //$NON-NLS-2$
	}
	
	@Test public void testMatch12() throws Exception {
		helpTestMatch("a..", "a%", MatchCriteria.NULL_ESCAPE_CHAR, true);		 //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch13() throws Exception {
//		helpTestMatch("x.y", "%.", MatchCriteria.NULL_ESCAPE_CHAR, false);		
		helpTestMatch("a.b", "a%.", MatchCriteria.NULL_ESCAPE_CHAR, false);		 //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch14() throws Exception {
		helpTestMatch("aaa", "%aaa", MatchCriteria.NULL_ESCAPE_CHAR, true);		 //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch15() throws Exception {
		helpTestMatch("baaa", "%aaa", MatchCriteria.NULL_ESCAPE_CHAR, true);		 //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch16() throws Exception {
		helpTestMatch("aaaa", "%aaa", MatchCriteria.NULL_ESCAPE_CHAR, true);		 //$NON-NLS-1$ //$NON-NLS-2$
	}
	
	@Test public void testMatch17() throws Exception {
		helpTestMatch("aaxaa", "%aaa", MatchCriteria.NULL_ESCAPE_CHAR, false);		 //$NON-NLS-1$ //$NON-NLS-2$
	}
	
	@Test public void testMatch18() throws Exception {
		helpTestMatch("", "a%b%", MatchCriteria.NULL_ESCAPE_CHAR, false);		 //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch19() throws Exception {
		helpTestMatch("a", "a%b%", MatchCriteria.NULL_ESCAPE_CHAR, false);		 //$NON-NLS-1$ //$NON-NLS-2$
	}
	
	@Test public void testMatch20() throws Exception {
		helpTestMatch("ab", "a%b%", MatchCriteria.NULL_ESCAPE_CHAR, true);		 //$NON-NLS-1$ //$NON-NLS-2$
	}
	
	@Test public void testMatch21() throws Exception {
		helpTestMatch("axb", "a%b%", MatchCriteria.NULL_ESCAPE_CHAR, true);		 //$NON-NLS-1$ //$NON-NLS-2$
	}
	
	@Test public void testMatch22() throws Exception {
		helpTestMatch("abx", "a%b%", MatchCriteria.NULL_ESCAPE_CHAR, true);		 //$NON-NLS-1$ //$NON-NLS-2$
	}
	
	@Test public void testMatch23() throws Exception {
		helpTestMatch("", "X%", 'X', false);		 //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch24() throws Exception {
		helpTestMatch("x", "X%", 'X', false);		 //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch25() throws Exception {
		helpTestMatch("xx", "X%", 'X', false);		 //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch26() throws Exception {
		helpTestMatch("a%", "aX%", 'X', true);		 //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch27() throws Exception {
		helpTestMatch("aX%", "aX%", 'X', false);		 //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch28() throws Exception {
		helpTestMatch("a%bb", "aX%b%", 'X', true);		 //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch29() throws Exception {
		helpTestMatch("aX%bb", "aX%b%", 'X', false);		 //$NON-NLS-1$ //$NON-NLS-2$
	}
	
	@Test public void testMatch30() throws Exception {
		helpTestMatch("", "_", MatchCriteria.NULL_ESCAPE_CHAR, false); //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch31() throws Exception {
		helpTestMatch("X", "_", MatchCriteria.NULL_ESCAPE_CHAR, true); //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch32() throws Exception {
		helpTestMatch("XX", "_", MatchCriteria.NULL_ESCAPE_CHAR, false); //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch33() throws Exception {
		helpTestMatch("", "__", MatchCriteria.NULL_ESCAPE_CHAR, false); //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch34() throws Exception {
		helpTestMatch("X", "__", MatchCriteria.NULL_ESCAPE_CHAR, false); //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch35() throws Exception {
		helpTestMatch("XX", "__", MatchCriteria.NULL_ESCAPE_CHAR, true); //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch36() throws Exception {
		helpTestMatch("XX", "_%_", MatchCriteria.NULL_ESCAPE_CHAR, true); //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch37() throws Exception {
		helpTestMatch("XaaY", "_%_", MatchCriteria.NULL_ESCAPE_CHAR, true); //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch38() throws Exception {
		helpTestMatch("a.b.c", "a.b.c", MatchCriteria.NULL_ESCAPE_CHAR, true); //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch39() throws Exception {
		helpTestMatch("a.b.c", "a%.c", MatchCriteria.NULL_ESCAPE_CHAR, true); //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Test public void testMatch40() throws Exception {
		helpTestMatch("a.b.", "a.b.", MatchCriteria.NULL_ESCAPE_CHAR, true); //$NON-NLS-1$ //$NON-NLS-2$
	}
    
    @Test public void testMatch41() throws Exception {
        helpTestMatch("asjdfajsdf (&). asdfasdf\nkjhkjh", "%&%", MatchCriteria.NULL_ESCAPE_CHAR, true);     //$NON-NLS-1$ //$NON-NLS-2$
    }
    
    @Test public void testMatch42() throws Exception {
        helpTestMatch("x", "", MatchCriteria.NULL_ESCAPE_CHAR, true, true); //$NON-NLS-1$ //$NON-NLS-2$
    }
    
    @Test public void testMatch43() throws Exception {
        helpTestMatch("a.b.", "a.b.", MatchCriteria.NULL_ESCAPE_CHAR, true, false); //$NON-NLS-1$ //$NON-NLS-2$
    }
    
    @Test public void testMatch44() throws Exception {
        helpTestMatch(null, "a.b.", MatchCriteria.NULL_ESCAPE_CHAR, false); //$NON-NLS-1$ 
    }
    
    @Test public void testMatch45() throws Exception {
        helpTestMatch("a.b.", null, MatchCriteria.NULL_ESCAPE_CHAR, false); //$NON-NLS-1$ 
    }
    
    @Test public void testMatch46() throws Exception {
        helpTestMatch("ab\r\n", "ab%", MatchCriteria.NULL_ESCAPE_CHAR, true); //$NON-NLS-1$ //$NON-NLS-2$
    }
    
    @Test public void testMatch47() throws Exception {
        helpTestMatch("", "", 'a', true); //$NON-NLS-1$ //$NON-NLS-2$
    }
    
    //should succeed - should be able to escape the escape char
    @Test public void testMatch48() throws Exception {
        helpTestMatch("abc", "aa%", 'a', true); //$NON-NLS-1$ //$NON-NLS-2$
    }
    
    //should fail - invalid match sequence
    @Test public void testMatch49() throws Exception {
        try {
            helpTestMatch("abc", "a", 'a', true); //$NON-NLS-1$ //$NON-NLS-2$
        } catch (ExpressionEvaluationException cee) {
            assertEquals("TEIID30449 Invalid escape sequence \"a\" with escape character \"a\"", cee.getMessage()); //$NON-NLS-1$
        }
    }
    
    //should fail - can't escape a non match char
    @Test public void testMatch50() throws Exception {
        try {
            helpTestMatch("abc", "ab", 'a', true); //$NON-NLS-1$ //$NON-NLS-2$
        } catch (ExpressionEvaluationException cee) {
            assertEquals("TEIID30449 Invalid escape sequence \"ab\" with escape character \"a\"", cee.getMessage()); //$NON-NLS-1$
        }
    }
    
    //should be able to use a regex reserved char as the escape char
    @Test public void testMatch51() throws Exception {
        helpTestMatch("$", "$$", '$', true); //$NON-NLS-1$ //$NON-NLS-2$
    }
    
    @Test public void testMatch52() throws Exception {
        helpTestMatch("abc\nde", "a%e", MatchCriteria.NULL_ESCAPE_CHAR, true); //$NON-NLS-1$ //$NON-NLS-2$
    }
    
    @Test public void testMatch53() throws Exception {
        helpTestMatch("\\", "\\%", MatchCriteria.NULL_ESCAPE_CHAR, true); //$NON-NLS-1$ //$NON-NLS-2$
    }
    
    @Test public void testSetCriteria1() throws Exception {
        helpTestSetCriteria(1000, false, true);
    }
    
    @Test public void testSetCriteria2() throws Exception {
        helpTestSetCriteria(1, false, false);
    }
    
    @Test public void testSetCriteria3() throws Exception {
        helpTestSetCriteria(1000, true, false);
    }
    
    @Test public void testSetCriteria4() throws Exception {
        helpTestSetCriteria(1, true, true);
    }
    
    @Test public void testSetCriteria5() throws Exception {
        helpTestSetCriteria(null, true, false);
    }
    
    @Test public void testSetCriteria6() throws Exception {
        helpTestSetCriteria(null, false, false);
    }
    
    @Test public void testExistsCriteria() throws Exception {
        ExistsCriteria crit = new ExistsCriteria(new Query());
        ArrayList values = new ArrayList();
        values.add("a"); //$NON-NLS-1$
        values.add("b"); //$NON-NLS-1$
        values.add("c"); //$NON-NLS-1$
        helpTestCompareSubqueryCriteria(crit, true, values);
    }

    @Test public void testExistsCriteria2() throws Exception {
        ExistsCriteria crit = new ExistsCriteria(new Query());
        helpTestCompareSubqueryCriteria(crit, false, Collections.emptyList());
    }

    /**
     * If rows are returned but they contain null, the result should
     * still be true.
     */
    @Test public void testExistsCriteria3() throws Exception {
        ExistsCriteria crit = new ExistsCriteria(new Query());
        ArrayList values = new ArrayList();
        values.add(null);
        values.add(null);
        values.add(null);
        helpTestCompareSubqueryCriteria(crit, true, values);
    }

    /**
     * Special case: if ALL is specified and the subquery returns no rows,
     * the result is true.
     */
    @Test public void testCompareSubqueryCriteriaNoRows() throws Exception {
        SubqueryCompareCriteria crit = helpGetCompareSubqueryCriteria(SubqueryCompareCriteria.EQ, SubqueryCompareCriteria.ALL);
        helpTestCompareSubqueryCriteria(crit, true, Collections.emptyList()); 
    }

    /**
     * Special case: if ANY/SOME is specified and the subquery returns no rows,
     * the result is false.
     */
    @Test public void testCompareSubqueryCriteriaNoRows2() throws Exception {
        SubqueryCompareCriteria crit = helpGetCompareSubqueryCriteria(SubqueryCompareCriteria.EQ, SubqueryCompareCriteria.SOME);
        helpTestCompareSubqueryCriteria(crit, false, Collections.emptyList()); 
    }

    @Test public void testCompareSubqueryCriteria2() throws Exception {
        SubqueryCompareCriteria crit = helpGetCompareSubqueryCriteria(SubqueryCompareCriteria.EQ, SubqueryCompareCriteria.ALL);
        ArrayList values = new ArrayList();
        values.add("a"); //$NON-NLS-1$
        values.add("b"); //$NON-NLS-1$
        values.add("c"); //$NON-NLS-1$
        helpTestCompareSubqueryCriteria(crit, false, values); 
    }

    @Test public void testCompareSubqueryCriteria3() throws Exception {
        SubqueryCompareCriteria crit = helpGetCompareSubqueryCriteria(SubqueryCompareCriteria.EQ, SubqueryCompareCriteria.SOME);
        ArrayList values = new ArrayList();
        values.add("a"); //$NON-NLS-1$
        values.add("b"); //$NON-NLS-1$
        values.add("c"); //$NON-NLS-1$
        helpTestCompareSubqueryCriteria(crit, true, values); 
    }
    
    @Test public void testCompareSubqueryCriteria4() throws Exception {
        SubqueryCompareCriteria crit = helpGetCompareSubqueryCriteria(SubqueryCompareCriteria.EQ, SubqueryCompareCriteria.SOME);
        ArrayList values = new ArrayList();
        values.add("b"); //$NON-NLS-1$
        values.add("c"); //$NON-NLS-1$
        helpTestCompareSubqueryCriteria(crit, false, values); 
    }

    @Test public void testCompareSubqueryCriteria5() throws Exception {
        SubqueryCompareCriteria crit = helpGetCompareSubqueryCriteria(SubqueryCompareCriteria.EQ, SubqueryCompareCriteria.SOME);
        ArrayList values = new ArrayList();
        values.add("a"); //$NON-NLS-1$
        values.add("b"); //$NON-NLS-1$
        values.add("c"); //$NON-NLS-1$
        helpTestCompareSubqueryCriteria(crit, true, values); 
    }

    @Test public void testCompareSubqueryCriteriaNulls3() throws Exception {
        SubqueryCompareCriteria crit = helpGetCompareSubqueryCriteria(SubqueryCompareCriteria.EQ, SubqueryCompareCriteria.ALL);
        ArrayList values = new ArrayList();
        values.add(null);
        values.add(null);
        helpTestCompareSubqueryCriteria(crit, null, values); 
    }

    @Test public void testCompareSubqueryCriteriaNulls4() throws Exception {
        SubqueryCompareCriteria crit = helpGetCompareSubqueryCriteria(SubqueryCompareCriteria.EQ, SubqueryCompareCriteria.SOME);
        ArrayList values = new ArrayList();
        values.add(null);
        values.add(null);
        helpTestCompareSubqueryCriteria(crit, null, values); 
    }

    @Test public void testCompareSubqueryCriteriaNulls5() throws Exception {
        SubqueryCompareCriteria crit = helpGetCompareSubqueryCriteria(SubqueryCompareCriteria.EQ, SubqueryCompareCriteria.SOME);
        ArrayList values = new ArrayList();
        values.add(null);
        values.add("a"); //$NON-NLS-1$
        values.add(null);
        helpTestCompareSubqueryCriteria(crit, true, values); 
    }

    @Test public void testCompareSubqueryCriteriaNulls6() throws Exception {
        SubqueryCompareCriteria crit = helpGetCompareSubqueryCriteria(SubqueryCompareCriteria.EQ, SubqueryCompareCriteria.SOME);
        ArrayList values = new ArrayList();
        values.add("a"); //$NON-NLS-1$
        values.add(null);
        values.add("a"); //$NON-NLS-1$
        helpTestCompareSubqueryCriteria(crit, true, values); 
    }

    /**
     * null is unknown
     */
    @Test public void testCompareSubqueryCriteriaNulls7() throws Exception{
        SubqueryCompareCriteria crit = helpGetCompareSubqueryCriteria(SubqueryCompareCriteria.LT, SubqueryCompareCriteria.ALL);
        ArrayList values = new ArrayList();
        values.add(null);
        values.add(null);
        helpTestCompareSubqueryCriteria(crit, null, values); 
    }

    /**
     * null is unknown
     */
    @Test public void testCompareSubqueryCriteriaNulls8() throws Exception {
        SubqueryCompareCriteria crit = helpGetCompareSubqueryCriteria(SubqueryCompareCriteria.GT, SubqueryCompareCriteria.ALL);
        ArrayList values = new ArrayList();
        values.add(null);
        values.add(null);
        helpTestCompareSubqueryCriteria(crit, null, values); 
    }    
    
    /**
     * Big decimal comparisons should ignore precision.
     */
    @Test public void testBigDecimalEquality() throws Exception {
    	CompareCriteria crit = new CompareCriteria(new Constant(new BigDecimal("3.10")), CompareCriteria.EQ, new Constant(new BigDecimal("3.1"))); //$NON-NLS-1$ //$NON-NLS-2$
    	assertTrue(Evaluator.evaluate(crit));
    }

}
