/******************************************************************************
 * Copyright (c) 2002 - 2006 IBM Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *****************************************************************************/
package com.ibm.wala.cast.test;

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

import junit.framework.Assert;

import org.junit.Test;

import com.ibm.wala.cast.tree.CAstNode;
import com.ibm.wala.cast.tree.impl.CAstImpl;
import com.ibm.wala.cast.util.CAstPattern;
import com.ibm.wala.cast.util.CAstPattern.Segments;
import com.ibm.wala.cast.util.CAstPrinter;
import com.ibm.wala.core.tests.util.WalaTestCase;

public class TestCAstPattern extends WalaTestCase {

  private static final int NAME_ASSERTION_SINGLE = 501;

  private static final int NAME_ASSERTION_MULTI = 502;

  private static class TestingCAstImpl extends CAstImpl {
    private final Map testNameMap = new HashMap();

    @SuppressWarnings("unchecked")
    public CAstNode makeNode(int kind, CAstNode children[]) {
      if (kind == NAME_ASSERTION_SINGLE || kind == NAME_ASSERTION_MULTI) {
        assert children.length == 2;
        assert children[0].getValue() instanceof String;
        @SuppressWarnings("unused")
        CAstNode result = children[1];
        if (kind == NAME_ASSERTION_SINGLE) {
          testNameMap.put(children[0].getValue(), children[1]);
        } else {
          if (!testNameMap.containsKey(children[0].getValue())) {
            testNameMap.put(children[0].getValue(), new ArrayList());
          }

          ((List) testNameMap.get(children[0].getValue())).add(children[1]);
        }
        return children[1];
      } else {
        return super.makeNode(kind, children);
      }
    }

  }

  private void test(CAstPattern p, CAstNode n, Map names) {
    System.err.println(("testing pattern " + p));
    System.err.println(("testing with input " + CAstPrinter.print(n)));

    if (names == null) {
      Assert.assertFalse(p.match(n, null));
    } else {
      Segments s = CAstPattern.match(p, n);
      Assert.assertTrue(s != null);
      for (Iterator ns = names.keySet().iterator(); ns.hasNext();) {
        String nm = (String) ns.next();
        Object o = names.get(nm);
        if (o instanceof CAstNode) {
          System.err.println(("found " + CAstPrinter.print(s.getSingle(nm)) + " for " + nm));
          Assert.assertTrue("for name " + nm + ": expected " + names.get(nm) + " but got " + s.getSingle(nm), names.get(nm).equals(
              s.getSingle(nm)));
        } else {
          for (Iterator cs = s.getMultiple(nm).iterator(); cs.hasNext();) {
            System.err.println(("found " + CAstPrinter.print((CAstNode) cs.next()) + " for " + nm));
          }
          Assert.assertTrue("for name " + nm + ": expected " + names.get(nm) + " but got " + s.getMultiple(nm), names.get(nm)
              .equals(s.getMultiple(nm)));
        }
      }
    }
  }

  private final CAstPattern simpleNamePattern = CAstPattern.parse("<top>BINARY_EXPR(\"+\",<left>\"prefix\",\"suffix\")");

  private final CAstNode simpleNameAst;

  private final Map simpleNameMap;

  {
    TestingCAstImpl Ast = new TestingCAstImpl();

    simpleNameAst = Ast.makeNode(NAME_ASSERTION_SINGLE, Ast.makeConstant("top"), Ast.makeNode(CAstNode.BINARY_EXPR, Ast
        .makeConstant("+"), Ast.makeNode(NAME_ASSERTION_SINGLE, Ast.makeConstant("left"), Ast.makeConstant("prefix")), Ast
        .makeConstant("suffix")));

    simpleNameMap = Ast.testNameMap;
  }

  @Test public void testSimpleName() {
    test(simpleNamePattern, simpleNameAst, simpleNameMap);
  }

  private final CAstPattern simpleStarNamePattern = CAstPattern.parse("<top>BINARY_EXPR(\"+\",*,<right>\"suffix\")");

  private final CAstNode simpleStarNameAst;

  private final Map simpleStarNameMap;

  {
    TestingCAstImpl Ast = new TestingCAstImpl();

    simpleStarNameAst = Ast.makeNode(NAME_ASSERTION_SINGLE, Ast.makeConstant("top"), Ast.makeNode(CAstNode.BINARY_EXPR, Ast
        .makeConstant("+"), Ast.makeConstant("prefix"), Ast.makeNode(NAME_ASSERTION_SINGLE, Ast.makeConstant("right"), Ast
        .makeConstant("suffix"))));

    simpleStarNameMap = Ast.testNameMap;
  }

  @Test public void testSimpleStarName() {
    test(simpleStarNamePattern, simpleStarNameAst, simpleStarNameMap);
  }

  private final CAstPattern simpleRepeatedPattern = CAstPattern.parse("<top>BINARY_EXPR(\"+\",<children>@(VAR(*))@)");

  private final CAstNode simpleRepeatedAstOne;

  private final Map simpleRepeatedMapOne;

  {
    TestingCAstImpl Ast = new TestingCAstImpl();

    simpleRepeatedAstOne = Ast.makeNode(NAME_ASSERTION_SINGLE, Ast.makeConstant("top"), Ast.makeNode(CAstNode.BINARY_EXPR, Ast
        .makeConstant("+"), Ast.makeNode(NAME_ASSERTION_SINGLE, Ast.makeConstant("children"), Ast.makeNode(CAstNode.VAR, Ast
        .makeConstant("suffix")))));

    simpleRepeatedMapOne = Ast.testNameMap;
  }

  @Test public void testSimpleRepeatedOne() {
    test(simpleRepeatedPattern, simpleRepeatedAstOne, simpleRepeatedMapOne);
  }

  private final CAstNode simpleRepeatedAstTwo;

  private final Map simpleRepeatedMapTwo;

  {
    TestingCAstImpl Ast = new TestingCAstImpl();

    simpleRepeatedAstTwo = Ast.makeNode(NAME_ASSERTION_SINGLE, Ast.makeConstant("top"), Ast.makeNode(CAstNode.BINARY_EXPR, Ast
        .makeConstant("+"), Ast.makeNode(NAME_ASSERTION_MULTI, Ast.makeConstant("children"), Ast.makeNode(CAstNode.VAR, Ast
        .makeConstant("prefix"))), Ast.makeNode(NAME_ASSERTION_MULTI, Ast.makeConstant("children"), Ast.makeNode(CAstNode.VAR, Ast
        .makeConstant("suffix")))));

    simpleRepeatedMapTwo = Ast.testNameMap;
  }

  @Test public void testSimpleRepeatedTwo() {
    test(simpleRepeatedPattern, simpleRepeatedAstTwo, simpleRepeatedMapTwo);
  }

  private final CAstNode simpleRepeatedAstThree;

  private final Map simpleRepeatedMapThree;

  {
    TestingCAstImpl Ast = new TestingCAstImpl();

    simpleRepeatedAstThree = Ast.makeNode(NAME_ASSERTION_SINGLE, Ast.makeConstant("top"), Ast.makeNode(CAstNode.BINARY_EXPR, Ast
        .makeConstant("+"), Ast.makeNode(NAME_ASSERTION_MULTI, Ast.makeConstant("children"), Ast.makeNode(CAstNode.VAR, Ast
        .makeConstant("prefix"))), Ast.makeNode(NAME_ASSERTION_MULTI, Ast.makeConstant("children"), Ast.makeNode(CAstNode.VAR, Ast
        .makeConstant("middle"))), Ast.makeNode(NAME_ASSERTION_MULTI, Ast.makeConstant("children"), Ast.makeNode(CAstNode.VAR, Ast
        .makeConstant("suffix")))));

    simpleRepeatedMapThree = Ast.testNameMap;
  }

  @Test public void testSimpleRepeatedThree() {
    test(simpleRepeatedPattern, simpleRepeatedAstThree, simpleRepeatedMapThree);
  }

  private final CAstPattern simpleDoubleStarPattern = CAstPattern.parse("<top>BINARY_EXPR(\"+\",<children>**)");

  private final CAstNode simpleDoubleStarAst;

  private final Map simpleDoubleStarMap;

  {
    TestingCAstImpl Ast = new TestingCAstImpl();

    simpleDoubleStarAst = Ast.makeNode(NAME_ASSERTION_SINGLE, Ast.makeConstant("top"), Ast.makeNode(CAstNode.BINARY_EXPR, Ast
        .makeConstant("+"), Ast.makeNode(NAME_ASSERTION_MULTI, Ast.makeConstant("children"), Ast.makeNode(CAstNode.VAR, Ast
        .makeConstant("prefix"))), Ast.makeNode(NAME_ASSERTION_MULTI, Ast.makeConstant("children"), Ast.makeNode(CAstNode.VAR, Ast
        .makeConstant("suffix")))));

    simpleDoubleStarMap = Ast.testNameMap;
  }

  @Test public void testSimpleDoubleStar() {
    test(simpleDoubleStarPattern, simpleDoubleStarAst, simpleDoubleStarMap);
  }

  private final CAstPattern simpleAlternativePattern = CAstPattern
      .parse("<top>BINARY_EXPR(\"+\",<firstchild>|(VAR(\"suffix\")||VAR(\"prefix\"))|,*)");

  private final CAstNode simpleAlternativeAst;

  private final Map simpleAlternativeMap;

  {
    TestingCAstImpl Ast = new TestingCAstImpl();

    simpleAlternativeAst = Ast.makeNode(NAME_ASSERTION_SINGLE, Ast.makeConstant("top"), Ast.makeNode(CAstNode.BINARY_EXPR, Ast
        .makeConstant("+"), Ast.makeNode(NAME_ASSERTION_SINGLE, Ast.makeConstant("firstchild"), Ast.makeNode(CAstNode.VAR, Ast
        .makeConstant("prefix"))), Ast.makeNode(CAstNode.VAR, Ast.makeConstant("suffix"))));

    simpleAlternativeMap = Ast.testNameMap;
  }

  @Test public void testSimpleAlternative() {
    test(simpleAlternativePattern, simpleAlternativeAst, simpleAlternativeMap);
  }

  private final CAstPattern simpleOptionalPattern = CAstPattern
      .parse("<top>BINARY_EXPR(\"+\",?(VAR(\"prefix\"))?,<child>VAR(\"suffix\"))");

  private final CAstNode simpleOptionalAstWith;

  private final Map simpleOptionalMapWith;

  {
    TestingCAstImpl Ast = new TestingCAstImpl();

    simpleOptionalAstWith = Ast.makeNode(NAME_ASSERTION_SINGLE, Ast.makeConstant("top"), Ast.makeNode(CAstNode.BINARY_EXPR, Ast
        .makeConstant("+"), Ast.makeNode(CAstNode.VAR, Ast.makeConstant("prefix")), Ast.makeNode(NAME_ASSERTION_SINGLE, Ast
        .makeConstant("child"), Ast.makeNode(CAstNode.VAR, Ast.makeConstant("suffix")))));

    simpleOptionalMapWith = Ast.testNameMap;
  }

  @Test public void testSimpleOptionalWith() {
    test(simpleOptionalPattern, simpleOptionalAstWith, simpleOptionalMapWith);
  }

  private final CAstNode simpleOptionalAstNot;

  private final Map simpleOptionalMapNot;

  {
    TestingCAstImpl Ast = new TestingCAstImpl();

    simpleOptionalAstNot = Ast.makeNode(NAME_ASSERTION_SINGLE, Ast.makeConstant("top"), Ast.makeNode(CAstNode.BINARY_EXPR, Ast
        .makeConstant("+"), Ast.makeNode(NAME_ASSERTION_SINGLE, Ast.makeConstant("child"), Ast.makeNode(CAstNode.VAR, Ast
        .makeConstant("suffix")))));

    simpleOptionalMapNot = Ast.testNameMap;
  }

  @Test public void testSimpleOptionalNot() {
    test(simpleOptionalPattern, simpleOptionalAstNot, simpleOptionalMapNot);
  }

  private final String recursiveTreeStr = "|({leaf}|(<const>CONSTANT()||VAR(<vars>*))|||{node}BINARY_EXPR(\"+\",`leaf`,|(`leaf`||`node`)|))|";

  private final CAstPattern recursiveTreePattern = CAstPattern.parse(recursiveTreeStr);

  private final CAstNode recursiveTreeOneAst;

  private final Map recursiveTreeOneMap;

  {
    TestingCAstImpl Ast = new TestingCAstImpl();

    recursiveTreeOneAst = Ast.makeNode(CAstNode.BINARY_EXPR, Ast.makeConstant("+"), Ast.makeNode(CAstNode.VAR, Ast.makeNode(
        NAME_ASSERTION_MULTI, Ast.makeConstant("vars"), Ast.makeConstant("x"))), Ast.makeNode(CAstNode.VAR, Ast.makeNode(
        NAME_ASSERTION_MULTI, Ast.makeConstant("vars"), Ast.makeConstant("y"))));

    recursiveTreeOneMap = Ast.testNameMap;
  }

  @Test public void testRecursiveTreeOne() {
    test(recursiveTreePattern, recursiveTreeOneAst, recursiveTreeOneMap);
  }

  private final CAstNode recursiveTreeTwoAst;

  private final Map recursiveTreeTwoMap;

  {
    TestingCAstImpl Ast = new TestingCAstImpl();

    recursiveTreeTwoAst = Ast.makeNode(CAstNode.BINARY_EXPR, Ast.makeConstant("+"), Ast.makeNode(CAstNode.VAR, Ast.makeNode(
        NAME_ASSERTION_MULTI, Ast.makeConstant("vars"), Ast.makeConstant("x"))), Ast.makeNode(CAstNode.BINARY_EXPR, Ast
        .makeConstant("+"), Ast.makeNode(CAstNode.VAR, Ast.makeNode(NAME_ASSERTION_MULTI, Ast.makeConstant("vars"), Ast
        .makeConstant("y"))), Ast.makeNode(CAstNode.VAR, Ast.makeNode(NAME_ASSERTION_MULTI, Ast.makeConstant("vars"), Ast
        .makeConstant("z")))));

    recursiveTreeTwoMap = Ast.testNameMap;
  }

  @Test public void testRecursiveTreeTwo() {
    test(recursiveTreePattern, recursiveTreeTwoAst, recursiveTreeTwoMap);
  }

  private final CAstNode recursiveTreeFiveAst;

  private final Map recursiveTreeFiveMap;

  {
    TestingCAstImpl Ast = new TestingCAstImpl();

    recursiveTreeFiveAst = Ast.makeNode(CAstNode.BINARY_EXPR, Ast.makeConstant("+"), Ast.makeNode(CAstNode.VAR, Ast.makeNode(
        NAME_ASSERTION_MULTI, Ast.makeConstant("vars"), Ast.makeConstant("u"))), Ast.makeNode(CAstNode.BINARY_EXPR, Ast
        .makeConstant("+"), Ast.makeNode(CAstNode.VAR, Ast.makeNode(NAME_ASSERTION_MULTI, Ast.makeConstant("vars"), Ast
        .makeConstant("v"))), Ast.makeNode(CAstNode.BINARY_EXPR, Ast.makeConstant("+"), Ast.makeNode(CAstNode.VAR, Ast.makeNode(
        NAME_ASSERTION_MULTI, Ast.makeConstant("vars"), Ast.makeConstant("w"))), Ast.makeNode(CAstNode.BINARY_EXPR, Ast
        .makeConstant("+"), Ast.makeNode(CAstNode.VAR, Ast.makeNode(NAME_ASSERTION_MULTI, Ast.makeConstant("vars"), Ast
        .makeConstant("x"))), Ast.makeNode(CAstNode.BINARY_EXPR, Ast.makeConstant("+"), Ast.makeNode(CAstNode.VAR, Ast.makeNode(
        NAME_ASSERTION_MULTI, Ast.makeConstant("vars"), Ast.makeConstant("y"))), Ast.makeNode(CAstNode.VAR, Ast.makeNode(
        NAME_ASSERTION_MULTI, Ast.makeConstant("vars"), Ast.makeConstant("z"))))))));

    recursiveTreeFiveMap = Ast.testNameMap;
  }

  @Test public void testRecursiveTreeFive() {
    test(recursiveTreePattern, recursiveTreeFiveAst, recursiveTreeFiveMap);
  }

  private final CAstPattern buggyRecursiveTreePattern = CAstPattern
      .parse("|({leaf}|(<const>CONSTANT()||VAR(<vars>*))|||{node}BINARY_EXPR(\"+\",`leaf`,`node`))|");

  @Test public void testBuggyRecursiveTreeOne() {
    test(buggyRecursiveTreePattern, recursiveTreeOneAst, null);
  }

  @Test public void testBuggyRecursiveTreeTwo() {
    test(buggyRecursiveTreePattern, recursiveTreeTwoAst, null);
  }

  @Test public void testBuggyRecursiveTreeFive() {
    test(buggyRecursiveTreePattern, recursiveTreeFiveAst, null);
  }

  private static final String extraTestsStr = "BINARY_EXPR(|(\"==\"||\"\\==\")|,|(CONSTANT()||VAR(CONSTANT()))|,|(CONSTANT()||VAR(CONSTANT()))|)";

  private final CAstPattern testedTreePattern = CAstPattern.parse("{top}|(" + recursiveTreeStr + "||BINARY_EXPR(\",\","
      + extraTestsStr + ",`top`))|");

  @Test public void testTestedTreeOne() {
    test(testedTreePattern, recursiveTreeOneAst, recursiveTreeOneMap);
  }

  @Test public void testTestedTreeTwo() {
    test(testedTreePattern, recursiveTreeTwoAst, recursiveTreeTwoMap);
  }

  @Test public void testTestedTreeFive() {
    test(testedTreePattern, recursiveTreeFiveAst, recursiveTreeFiveMap);
  }

  private final CAstNode testedTreeOneAst;

  private final Map testedTreeOneMap;

  {
    TestingCAstImpl Ast = new TestingCAstImpl();

    testedTreeOneAst = Ast.makeNode(CAstNode.BINARY_EXPR, Ast.makeConstant(","), Ast.makeNode(CAstNode.BINARY_EXPR, Ast
        .makeConstant("=="), Ast.makeNode(CAstNode.VAR, Ast.makeConstant("x")), Ast.makeConstant(7)), Ast.makeNode(
        CAstNode.BINARY_EXPR, Ast.makeConstant("+"), Ast.makeNode(CAstNode.VAR, Ast.makeNode(NAME_ASSERTION_MULTI, Ast
            .makeConstant("vars"), Ast.makeConstant("x"))), Ast.makeNode(CAstNode.VAR, Ast.makeNode(NAME_ASSERTION_MULTI, Ast
            .makeConstant("vars"), Ast.makeConstant("y")))));

    testedTreeOneMap = Ast.testNameMap;
  }

  @Test public void testTestedTreeOneWithTest() {
    test(testedTreePattern, testedTreeOneAst, testedTreeOneMap);
  }

}
