/*****************************************************************
 *   Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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
 *
 *    https://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 org.apache.cayenne.exp.property;

import java.util.Arrays;
import java.util.List;

import org.apache.cayenne.exp.Expression;
import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.exp.parser.ASTLike;
import org.apache.cayenne.exp.parser.ASTObjPath;
import org.apache.cayenne.reflect.TstJavaBean;
import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.*;

/**
 * @since 4.2
 */
public class BasePropertyTest {

    private BaseProperty<Integer> property;

    @Before
    public void createProperty() {
        property = new BaseProperty<>("path", null, Integer.class);
    }


    @Test
    public void testPathExpConstructor() {
        assertEquals(new ASTObjPath("path"), property.getExpression());
    }

    @Test
    public void testCustomExpConstructor() {
        property = new BaseProperty<>("path", new ASTLike(), Integer.class);
        assertEquals(new ASTLike(), property.getExpression());
    }

    @Test
    public void testEq() {
        Expression exp = property.eq(1);
        assertEquals(ExpressionFactory.exp("path = 1"), exp);
    }

    @Test
    public void testNe() {
        Expression exp = property.ne(1);
        assertEquals(ExpressionFactory.exp("path != 1"), exp);
    }

    @Test
    public void testIn() {
        Expression exp = property.in(1, 2, 3);
        assertEquals(ExpressionFactory.exp("path in (1, 2, 3)"), exp);
    }

    @Test
    public void testInCollection() {
        Expression exp = property.in(Arrays.asList(1,2,3));
        assertEquals(ExpressionFactory.exp("path in (1, 2, 3)"), exp);
    }

    @Test
    public void testNin() {
        Expression exp = property.nin(1, 2, 3);
        assertEquals(ExpressionFactory.exp("path not in (1, 2, 3)"), exp);
    }

    @Test
    public void testNinCollection() {
        Expression exp = property.nin(Arrays.asList(1,2,3));
        assertEquals(ExpressionFactory.exp("path not in (1, 2, 3)"), exp);
    }

    @Test
    public void testIsNull() {
        BaseProperty<Integer> property = new BaseProperty<>("path", null, Integer.class);
        Expression exp = property.isNull();
        assertEquals(ExpressionFactory.exp("path = null"), exp);
    }

    @Test
    public void testNotIsNull() {
        Expression exp = property.isNotNull();
        assertEquals(ExpressionFactory.exp("path != null"), exp);
    }

    @Test
    public void testIsTrue() {
        Expression exp = property.isTrue();
        assertEquals(ExpressionFactory.exp("path = true"), exp);
    }

    @Test
    public void testIsFalse() {
        Expression exp = property.isFalse();
        assertEquals(ExpressionFactory.exp("path = false"), exp);
    }

    @Test
    public void testAlias() {
        assertEquals("path", property.getName());
        assertNull(property.getAlias());

        property = property.alias("test");

        assertEquals("test", property.getName());
        assertEquals("test", property.getAlias());
        assertEquals(new ASTObjPath("path"), property.getExpression());
    }

    @Test
    public void testGetFrom() {
        TstJavaBean bean = new TstJavaBean();
        bean.setIntField(7);
        BaseProperty<Integer> INT_FIELD = new BaseProperty<>("intField", null, Integer.class);
        assertEquals(Integer.valueOf(7), INT_FIELD.getFrom(bean));
    }

    @Test
    public void testGetFromNestedProperty() {
        TstJavaBean bean = new TstJavaBean();
        TstJavaBean nestedBean = new TstJavaBean();
        nestedBean.setIntField(7);
        bean.setObjectField(nestedBean);
        BaseProperty<Integer> OBJECT_FIELD_INT_FIELD = new BaseProperty<>("objectField.intField", null, Integer.class);
        assertEquals(Integer.valueOf(7), OBJECT_FIELD_INT_FIELD.getFrom(bean));
    }

    @Test
    public void testGetFromNestedNull() {
        TstJavaBean bean = new TstJavaBean();
        bean.setObjectField(null);
        BaseProperty<Integer> OBJECT_FIELD_INT_FIELD = new BaseProperty<>("objectField.intField", null, Integer.class);
        assertNull(OBJECT_FIELD_INT_FIELD.getFrom(bean));
    }

    @Test
    public void testGetFromAll() {
        TstJavaBean bean = new TstJavaBean();
        bean.setIntField(7);

        TstJavaBean bean2 = new TstJavaBean();
        bean2.setIntField(8);

        List<TstJavaBean> beans = Arrays.asList(bean, bean2);

        BaseProperty<Integer> INT_FIELD = new BaseProperty<>("intField", null, Integer.class);
        assertEquals(Arrays.asList(7, 8), INT_FIELD.getFromAll(beans));
    }

    @Test
    public void testSetIn() {
        TstJavaBean bean = new TstJavaBean();
        BaseProperty<Integer> INT_FIELD = new BaseProperty<>("intField", null, Integer.class);
        INT_FIELD.setIn(bean, 7);
        assertEquals(7, bean.getIntField());
    }

    @Test
    public void testSetInNestedProperty() {
        TstJavaBean bean = new TstJavaBean();
        bean.setObjectField(new TstJavaBean());

        BaseProperty<Integer> OBJECT_FIELD_INT_FIELD = new BaseProperty<>("objectField.intField", null, Integer.class);

        OBJECT_FIELD_INT_FIELD.setIn(bean, 7);
        assertEquals(7, ((TstJavaBean) bean.getObjectField()).getIntField());
    }

    @Test
    public void testSetInNestedNull() {
        TstJavaBean bean = new TstJavaBean();
        bean.setObjectField(null);
        BaseProperty<Integer> OBJECT_FIELD_INT_FIELD = new BaseProperty<>("objectField.intField", null, Integer.class);
        OBJECT_FIELD_INT_FIELD.setIn(bean, 7);
    }

    @Test
    public void testSetInAll() {
        TstJavaBean bean = new TstJavaBean();
        TstJavaBean bean2 = new TstJavaBean();
        List<TstJavaBean> beans = Arrays.asList(bean, bean2);

        BaseProperty<Integer> INT_FIELD = new BaseProperty<>("intField", null, Integer.class);
        INT_FIELD.setInAll(beans, 7);
        assertEquals(7, bean.getIntField());
        assertEquals(7, bean2.getIntField());
    }

    @Test
    public void testEqualsWithName() {
        BaseProperty<Integer> INT_FIELD = new BaseProperty<>("intField", null, Integer.class);
        BaseProperty<Integer> INT_FIELD2 = new BaseProperty<>("intField", null, Integer.class);

        assertNotSame(INT_FIELD, INT_FIELD2);
        assertEquals(INT_FIELD, INT_FIELD2);
    }

    @Test
    public void testHashCodeWithName() {
        BaseProperty<Integer> INT_FIELD = new BaseProperty<>("intField", null, Integer.class);
        BaseProperty<Integer> INT_FIELD2 = new BaseProperty<>("intField", null, Integer.class);
        BaseProperty<Long> LONG_FIELD = new BaseProperty<>("longField", null, Long.class);

        assertEquals(INT_FIELD.hashCode(), INT_FIELD2.hashCode());
        assertNotSame(INT_FIELD.hashCode(), LONG_FIELD.hashCode());
    }

    @Test
    public void testEqualsWithNameAndType() {
        BaseProperty<Integer> INT_FIELD = new BaseProperty<>("intField", null, Integer.class);
        BaseProperty<Integer> INT_FIELD2 = new BaseProperty<>("intField", null, Integer.class);

        assertNotSame(INT_FIELD, INT_FIELD2);
        assertEquals(INT_FIELD, INT_FIELD2);
    }

    @Test
    public void testHashCodeWithNameAndType() {
        BaseProperty<Integer> INT_FIELD = new BaseProperty<>("intField", null, Integer.class);
        BaseProperty<Integer> INT_FIELD2 = new BaseProperty<>("intField", null, Integer.class);
        BaseProperty<Long> LONG_FIELD = new BaseProperty<>("longField", null, Long.class);

        assertEquals(INT_FIELD.hashCode(), INT_FIELD2.hashCode());
        assertNotEquals(INT_FIELD.hashCode(), LONG_FIELD.hashCode());
    }

    @Test
    public void testEqualsWithExpAndType() {
        BaseProperty<Integer> INT_FIELD = new BaseProperty<>(null, ExpressionFactory.exp("1"), Integer.class);
        BaseProperty<Integer> INT_FIELD2 = new BaseProperty<>(null, ExpressionFactory.exp("1"), Integer.class);

        assertNotSame(INT_FIELD, INT_FIELD2);
        assertEquals(INT_FIELD, INT_FIELD2);
    }

    @Test
    public void testHashCodeWithExpAndType() {
        BaseProperty<Integer> INT_FIELD = new BaseProperty<>(null, ExpressionFactory.exp("1"), Integer.class);
        BaseProperty<Integer> INT_FIELD2 = new BaseProperty<>(null, ExpressionFactory.exp("1"), Integer.class);
        BaseProperty<Integer> INT_FIELD3 = new BaseProperty<>(null, ExpressionFactory.exp("2"), Integer.class);

        assertEquals(INT_FIELD.hashCode(), INT_FIELD2.hashCode());
        assertNotEquals(INT_FIELD.hashCode(), INT_FIELD3.hashCode());
    }

    @Test
    public void testFunctionProperty() {
        BaseProperty<Integer> property = new BaseProperty<>("intField", null, Integer.class);
        BaseProperty<Integer> arg = new BaseProperty<>("intField2", null, Integer.class);

        BaseProperty<Integer> operator = property.function("%", Integer.class, arg);
        assertEquals(ExpressionFactory.exp("fn('%', intField, intField2)"), operator.getExpression());
    }

    @Test
    public void testFunctionScalar() {
        BaseProperty<Integer> property = new BaseProperty<>("intField", null, Integer.class);

        BaseProperty<Integer> operator = property.function("%", Integer.class, 10);
        assertEquals(ExpressionFactory.exp("fn('%', intField, 10)"), operator.getExpression());
    }

    @Test
    public void testOperatorProperty() {
        BaseProperty<Integer> property = new BaseProperty<>("intField", null, Integer.class);
        BaseProperty<Integer> arg = new BaseProperty<>("intField2", null, Integer.class);

        BaseProperty<Integer> operator = property.operator("%", Integer.class, arg);
        assertEquals(ExpressionFactory.exp("op('%', intField, intField2)"), operator.getExpression());
    }

    @Test
    public void testOperatorScalar() {
        BaseProperty<Integer> property = new BaseProperty<>("intField", null, Integer.class);

        BaseProperty<Integer> operator = property.operator("%", Integer.class, 10);
        assertEquals(ExpressionFactory.exp("op('%', intField, 10)"), operator.getExpression());
    }

}