/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.facebook.presto.sql.planner;

import com.facebook.presto.metadata.Metadata;
import com.facebook.presto.metadata.MetadataManager;
import com.facebook.presto.operator.project.InterpretedPageFilter;
import com.facebook.presto.operator.project.SelectedPositions;
import com.facebook.presto.spi.Page;
import com.facebook.presto.sql.parser.SqlParser;
import com.facebook.presto.sql.tree.ComparisonExpressionType;
import com.google.common.collect.ImmutableMap;
import org.testng.annotations.Test;

import static com.facebook.presto.SessionTestUtils.TEST_SESSION;
import static com.facebook.presto.operator.scalar.FunctionAssertions.createExpression;
import static java.lang.String.format;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;

public class TestInterpretedPageFilterFunction
{
    private static final SqlParser SQL_PARSER = new SqlParser();
    private static final Metadata METADATA = MetadataManager.createTestMetadataManager();

    @Test
    public void testNullLiteral()
    {
        assertFilter("null", false);
    }

    @Test
    public void testBooleanLiteral()
    {
        assertFilter("true", true);
        assertFilter("false", false);
    }

    @Test
    public void testNotExpression()
    {
        assertFilter("not true", false);
        assertFilter("not false", true);
        assertFilter("not null", false);
    }

    @Test
    public void testAndExpression()
    {
        assertFilter("true and true", true);
        assertFilter("true and false", false);
        assertFilter("true and null", false);

        assertFilter("false and true", false);
        assertFilter("false and false", false);
        assertFilter("false and null", false);

        assertFilter("null and true", false);
        assertFilter("null and false", false);
        assertFilter("null and null", false);
    }

    @Test
    public void testORExpression()
    {
        assertFilter("true or true", true);
        assertFilter("true or false", true);
        assertFilter("true or null", true);

        assertFilter("false or true", true);
        assertFilter("false or false", false);
        assertFilter("false or null", false);

        assertFilter("null or true", true);
        assertFilter("null or false", false);
        assertFilter("null or null", false);
    }

    @Test
    public void testIsNullExpression()
    {
        assertFilter("null is null", true);
        assertFilter("42 is null", false);
    }

    @Test
    public void testIsNotNullExpression()
    {
        assertFilter("42 is not null", true);
        assertFilter("null is not null", false);
    }

    @Test
    public void testComparisonExpression()
    {
        assertFilter("42 = 42", true);
        assertFilter("42 = 42.0", true);
        assertFilter("42.42 = 42.42", true);
        assertFilter("'foo' = 'foo'", true);

        assertFilter("42 = 87", false);
        assertFilter("42 = 22.2", false);
        assertFilter("42.42 = 22.2", false);
        assertFilter("'foo' = 'bar'", false);

        assertFilter("42 != 87", true);
        assertFilter("42 != 22.2", true);
        assertFilter("42.42 != 22.22", true);
        assertFilter("'foo' != 'bar'", true);

        assertFilter("42 != 42", false);
        assertFilter("42 != 42.0", false);
        assertFilter("42.42 != 42.42", false);
        assertFilter("'foo' != 'foo'", false);

        assertFilter("42 < 88", true);
        assertFilter("42 < 88.8", true);
        assertFilter("42.42 < 88.8", true);
        assertFilter("'bar' < 'foo'", true);

        assertFilter("88 < 42", false);
        assertFilter("88 < 42.42", false);
        assertFilter("88.8 < 42.42", false);
        assertFilter("'foo' < 'bar'", false);

        assertFilter("42 <= 88", true);
        assertFilter("42 <= 88.8", true);
        assertFilter("42.42 <= 88.8", true);
        assertFilter("'bar' <= 'foo'", true);

        assertFilter("42 <= 42", true);
        assertFilter("42 <= 42.0", true);
        assertFilter("42.42 <= 42.42", true);
        assertFilter("'foo' <= 'foo'", true);

        assertFilter("88 <= 42", false);
        assertFilter("88 <= 42.42", false);
        assertFilter("88.8 <= 42.42", false);
        assertFilter("'foo' <= 'bar'", false);

        assertFilter("88 >= 42", true);
        assertFilter("88.8 >= 42.0", true);
        assertFilter("88.8 >= 42.42", true);
        assertFilter("'foo' >= 'bar'", true);

        assertFilter("42 >= 88", false);
        assertFilter("42.42 >= 88.0", false);
        assertFilter("42.42 >= 88.88", false);
        assertFilter("'bar' >= 'foo'", false);

        assertFilter("88 >= 42", true);
        assertFilter("88.8 >= 42.0", true);
        assertFilter("88.8 >= 42.42", true);
        assertFilter("'foo' >= 'bar'", true);
        assertFilter("42 >= 42", true);
        assertFilter("42 >= 42.0", true);
        assertFilter("42.42 >= 42.42", true);
        assertFilter("'foo' >= 'foo'", true);

        assertFilter("42 >= 88", false);
        assertFilter("42.42 >= 88.0", false);
        assertFilter("42.42 >= 88.88", false);
        assertFilter("'bar' >= 'foo'", false);
    }

    @Test
    public void testComparisonExpressionWithNulls()
    {
        for (ComparisonExpressionType type : ComparisonExpressionType.values()) {
            if (type == ComparisonExpressionType.IS_DISTINCT_FROM) {
                // IS DISTINCT FROM has different NULL semantics
                continue;
            }

            assertFilter(format("NULL %s NULL", type.getValue()), false);

            assertFilter(format("42 %s NULL", type.getValue()), false);
            assertFilter(format("NULL %s 42", type.getValue()), false);

            assertFilter(format("11.1 %s NULL", type.getValue()), false);
            assertFilter(format("NULL %s 11.1", type.getValue()), false);
        }
    }

    private static void assertFilter(String expression, boolean expectedValue)
    {
        InterpretedPageFilter filterFunction = new InterpretedPageFilter(
                createExpression(expression, METADATA, ImmutableMap.of()),
                ImmutableMap.of(),
                ImmutableMap.of(),
                METADATA,
                SQL_PARSER,
                TEST_SESSION);

        SelectedPositions selectedPositions = filterFunction.filter(TEST_SESSION.toConnectorSession(), new Page(1));
        assertEquals(selectedPositions.size(), expectedValue ? 1 : 0);
        if (expectedValue) {
            if (selectedPositions.isList()) {
                assertEquals(selectedPositions.getPositions()[selectedPositions.getOffset()], 0);
            }
            else {
                assertEquals(selectedPositions.getOffset(), 0);
            }
        }
        else {
            assertTrue(selectedPositions.isEmpty());
        }
    }
}
