package org.mybatis.qbe.mybatis3.render;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;

import java.sql.JDBCType;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;

import org.junit.Test;
import org.mybatis.qbe.Criterion;
import org.mybatis.qbe.condition.IsEqualToCondition;
import org.mybatis.qbe.condition.IsLikeCondition;
import org.mybatis.qbe.field.Field;
import org.mybatis.qbe.mybatis3.WhereClauseAndParameters;

public class MyBatis3CriterionRendererTest {

    @Test
    public void testAliasWithIgnore() {
        Field<Integer> field = Field.of("id", JDBCType.INTEGER).withAlias("a");
        IsEqualToCondition<Integer> condition = IsEqualToCondition.of(3);
        Criterion<Integer> criterion = Criterion.of(field, condition);
        AtomicInteger sequence = new AtomicInteger(1);
        MyBatis3CriterionRenderer renderer = MyBatis3CriterionRenderer.of(criterion, sequence);
        
        WhereClauseAndParameters rc = renderer.render(true);
        assertThat(rc.getWhereClause(), is(" id = #{parameters.p1,jdbcType=INTEGER}"));
        assertThat(rc.getParameters().size(), is(1));
    }

    @Test
    public void testAliasWithoutIgnore() {
        Field<Integer> field = Field.of("id", JDBCType.INTEGER).withAlias("a");
        IsEqualToCondition<Integer> condition = IsEqualToCondition.of(3);
        Criterion<Integer> criterion = Criterion.of(field, condition);
        AtomicInteger sequence = new AtomicInteger(1);
        MyBatis3CriterionRenderer renderer = MyBatis3CriterionRenderer.of(criterion, sequence);
        
        WhereClauseAndParameters rc = renderer.render(false);
        assertThat(rc.getWhereClause(), is(" a.id = #{parameters.p1,jdbcType=INTEGER}"));
        assertThat(rc.getParameters().size(), is(1));
    }

    @Test
    public void testNoAliasWithIgnore() {
        Field<Integer> field = Field.of("id", JDBCType.INTEGER);
        IsEqualToCondition<Integer> condition = IsEqualToCondition.of(3);
        Criterion<Integer> criterion = Criterion.of(field, condition);
        AtomicInteger sequence = new AtomicInteger(1);
        MyBatis3CriterionRenderer renderer = MyBatis3CriterionRenderer.of(criterion, sequence);
        
        WhereClauseAndParameters rc = renderer.render(true);
        assertThat(rc.getWhereClause(), is(" id = #{parameters.p1,jdbcType=INTEGER}"));
        assertThat(rc.getParameters().size(), is(1));
    }

    @Test
    public void testNoAliasWithoutIgnore() {
        Field<Integer> field = Field.of("id", JDBCType.INTEGER);
        IsEqualToCondition<Integer> condition = IsEqualToCondition.of(3);
        Criterion<Integer> criterion = Criterion.of(field, condition);
        AtomicInteger sequence = new AtomicInteger(1);
        MyBatis3CriterionRenderer renderer = MyBatis3CriterionRenderer.of(criterion, sequence);
        
        WhereClauseAndParameters rc = renderer.render(false);
        assertThat(rc.getWhereClause(), is(" id = #{parameters.p1,jdbcType=INTEGER}"));
        assertThat(rc.getParameters().size(), is(1));
    }

    @Test
    public void testTypeHandler() {
        Field<Date> field = Field.of("id", JDBCType.DATE).withTypeHandler("foo.Bar");
        IsEqualToCondition<Date> condition = IsEqualToCondition.of(new Date());
        Criterion<Date> criterion = Criterion.of(field, condition);
        AtomicInteger sequence = new AtomicInteger(1);
        MyBatis3CriterionRenderer renderer = MyBatis3CriterionRenderer.of(criterion, sequence);
        
        WhereClauseAndParameters rc = renderer.render(false);
        assertThat(rc.getWhereClause(), is(" id = #{parameters.p1,jdbcType=DATE,typeHandler=foo.Bar}"));
        assertThat(rc.getParameters().size(), is(1));
    }

    @Test
    public void testTypeHandlerAndAlias() {
        Field<Integer> field = Field.of("id", JDBCType.INTEGER).withTypeHandler("foo.Bar").withAlias("a");
        IsEqualToCondition<Integer> condition = IsEqualToCondition.of(3);
        Criterion<Integer> criterion = Criterion.of(field, condition);
        AtomicInteger sequence = new AtomicInteger(1);
        MyBatis3CriterionRenderer renderer = MyBatis3CriterionRenderer.of(criterion, sequence);
        
        WhereClauseAndParameters rc = renderer.render(false);
        assertThat(rc.getWhereClause(), is(" a.id = #{parameters.p1,jdbcType=INTEGER,typeHandler=foo.Bar}"));
        assertThat(rc.getParameters().size(), is(1));
    }
    
    @Test
    public void testCustomCallbacks() {
        Field<String> field = Field.of("description", JDBCType.VARCHAR)
                .withTypeHandler("foo.Bar")
                .withAlias("a")
                .withCustomRenderer(this::handleName);
        
        IsLikeCondition condition = IsLikeCaseInsensitiveCondition.of("fred");
        Criterion<String> criterion = Criterion.of(field, condition);
        AtomicInteger sequence = new AtomicInteger(1);
        MyBatis3CriterionRenderer renderer = MyBatis3CriterionRenderer.of(criterion, sequence);
        
        WhereClauseAndParameters rc = renderer.render(false);
        assertThat(rc.getWhereClause(), is(" upper(a.description) like #{parameters.p1,jdbcType=VARCHAR,typeHandler=foo.Bar}"));
        assertThat(rc.getParameters().size(), is(1));
        assertThat(rc.getParameters().get("p1"), is("FRED"));
    }
    
    private String handleName(Field<?> field, boolean ignoreAlias) {
        StringBuilder sb = new StringBuilder();
        sb.append("upper(");
        if (!ignoreAlias) {
            field.alias().ifPresent(a -> {
                sb.append(a);
                sb.append('.');
            });
        }
        sb.append(field.name());
        sb.append(')');
        return sb.toString();
    }
    
    public static class IsLikeCaseInsensitiveCondition extends IsLikeCondition {
        private IsLikeCaseInsensitiveCondition(String value) {
            super(value);
        }
        
        public static IsLikeCaseInsensitiveCondition of(String value) {
            return new IsLikeCaseInsensitiveCondition(value);
        }

        @Override
        public String value() {
            return super.value().toUpperCase();
        }
    }
}

@@ -4655,64 +4655,10 @@
 %22a%22)
-%0A                .withCustomRenderer(this::handleName)
 ;%0A
+
     
@@ -4789,32 +4789,69 @@
 iterion.of(field
+.withCustomRenderer(this::handleName)
 , condition);%0A  
@@ -5249,24 +5249,24 @@
 (), is(1));%0A
-
         asse
@@ -5311,24 +5311,615 @@
 s(%22FRED%22));%0A
+        %0A        // make sure that we didn't destroy the field definition with our custom renderer above%0A        condition = IsLikeCondition.of(%22fred%22);%0A        criterion = Criterion.of(field, condition);%0A        sequence = new AtomicInteger(1);%0A        renderer = MyBatis3CriterionRenderer.of(criterion, sequence);%0A        rc = renderer.render(false);%0A        assertThat(rc.getWhereClause(), is(%22 a.description like #%7Bparameters.p1,jdbcType=VARCHAR,typeHandler=foo.Bar%7D%22));%0A        assertThat(rc.getParameters().size(), is(1));%0A        assertThat(rc.getParameters().get(%22p1%22), is(%22fred%22));%0A
     %7D%0A    %0A 

Test to make sure the field definition is immutable