package org.mvel2.compiler;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.mvel2.MVEL;
import org.mvel2.ParserContext;
import org.mvel2.tests.BaseMvelTestCase;

/**
 * Asserts that the element at the end of the parse chain has its type parameter correctly inferred
 * IF the egress type is a parametric type (i.e. generic).
 *
 * @author Dhanji R. Prasanna (dhanji@gmail com)
 */
public class GenericsTypeInferenceTest extends BaseMvelTestCase {
  private static final List<String> STRINGS = Arrays.asList("hi", "there", "dude");

  public final void testInferLastTypeParametersFromProperty() {
    ParserContext context = new ParserContext();
    context.setStrongTyping(true);

    context.addInput("a", A.class);

    final CompiledExpression compiledExpression = new ExpressionCompiler("a.strings", context)
        .compile();

    final Object val = MVEL.executeExpression(compiledExpression, new AWrapper());

    assertTrue("Expression did not evaluate correctly: " + val, STRINGS.equals(val));
    assertTrue("No type parameters detected", null != context.getLastTypeParameters());
    assertTrue("Wrong parametric type inferred", String.class.equals(context.getLastTypeParameters()[0]));
  }

  public final void testInferLastTypeParametersFromMethod() {
    ParserContext context = new ParserContext();
    context.setStrongTyping(true);

    context.addInput("a", A.class);

    final CompiledExpression compiledExpression = new ExpressionCompiler("a.values()", context)
        .compile();

    final Object val = MVEL.executeExpression(compiledExpression, new AWrapper());

    assertTrue("Expression did not evaluate correctly: " + val, STRINGS.equals(val));
    assertTrue("No type parameters detected", null != context.getLastTypeParameters());
    assertTrue("Wrong parametric type inferred", String.class.equals(context.getLastTypeParameters()[0]));
  }

  public final void testInferLastTypeParametersFromPropertyMethod() {
    ParserContext context = new ParserContext();
    context.setStrongTyping(true);

    context.addInput("a", A.class);

    final CompiledExpression compiledExpression = new ExpressionCompiler("a.getFooMap()[\"key\"].someMethod()", context)
        .compile();

    final Object val = MVEL.executeExpression(compiledExpression, new AWrapper());

    assertEquals("Expression did not evaluate correctly: ", "bar", val);
    assertNotNull("No type parameters detected", context.getLastTypeParameters());
    assertEquals("Wrong parametric type inferred", String.class, context.getLastTypeParameters()[0]);
  }

  public final void testTypeByMethod() {
    ParserContext context = new ParserContext();
    context.setStrongTyping(true);

    context.addInput("a", A.class);

    CompiledExpression compiledExpression = new ExpressionCompiler("!a.show", context).compile();

    assertEquals(Boolean.class, compiledExpression.getKnownEgressType());
  }


//    public final void testInferLastTypeParametersFromPropertyMethod2() {
//        ParserContext context = new ParserContext();
//        context.setStrictTypeEnforcement( true );
//
//        context.addInput("a", A.class);
//
//        ExpressionCompiler compiler = new ExpressionCompiler("a.getBarMap()[\"key\"].someMethod();");
//        final CompiledExpression compiledExpression = compiler.compileShared(context);
//
//        Map<String,Object> vars = new HashMap<String,Object>();
//        vars.put( "a", new A() );
//        final Object val = MVEL.executeExpression(compiledExpression, vars);
//
//        assertEquals("Expression did not evaluate correctly: ", "bar", val);
//        assertNotNull("No type parameters detected", context.getLastTypeParameters());
//        assertEquals("Wrong parametric type inferred", String.class, context.getLastTypeParameters()[0]);
//    }

  public static class AWrapper {
    public A getA() {
      return new A();
    }
  }

  public static class A {
    private boolean show;

    public boolean isShow() {
      return show;
    }

    public void setShow(boolean show) {
      this.show = show;
    }

    public List<String> getStrings() {
      return STRINGS;
    }

    public List<String> values() {
      return STRINGS;
    }

    public Map<String, Foo> getFooMap() {
      Map<String, Foo> map = new HashMap<String, Foo>();
      map.put("key", new Foo() {
        public String someMethod() {
          return "bar";
        }
      });

      return map;
    }

    public Map<String, Foo> getBarMap() {
      Map<String, Foo> map = new HashMap<String, Foo>();
      map.put("key", new FooImpl());
      return map;
    }


  }

  public static interface Foo {

    public String someMethod();
  }

  public static class FooImpl implements Foo {

    public String someMethod() {
      return "bar";
    }
  }

  public static class Amazed1 {
    private List list = new ArrayList();

    public List<Integer> getList() {
      return this.list;
    }
  }

  public static class Amazed2 {
    private List list = new ArrayList();

    public List getList() {
      return this.list;
    }
  }

  public void testAmazed() {

    MVEL.COMPILER_OPT_ALLOW_NAKED_METH_CALL = true;
    try {
      ParserContext context = new ParserContext();
      context.setStrongTyping(true);
      context.addInput("this",
          Amazed1.class);
      ExecutableStatement stmt = (ExecutableStatement) MVEL.compileExpression("list.size", context);

      Amazed1 a1 = new Amazed1();
      assertEquals(new Integer(0), MVEL.executeExpression(stmt, a1));


      context = new ParserContext();
      context.setStrongTyping(true);
      context.addInput("this",
          Amazed2.class);
      stmt = (ExecutableStatement) MVEL.compileExpression("list.size", context);

      Amazed2 a2 = new Amazed2();

      assertEquals(new Integer(0), MVEL.executeExpression(stmt, a2));

    }
    finally {
      MVEL.COMPILER_OPT_ALLOW_NAKED_METH_CALL = false;
    }
  }
}
