/*
 * Copyright 2009 Google Inc.
 *
 * 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.google.common.css.compiler.ast;

import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;

import com.google.common.collect.Lists;
import com.google.common.css.SourceCode;
import com.google.common.css.compiler.ast.CssAttributeSelectorNode.MatchType;
import com.google.common.css.compiler.ast.DefaultVisitController.RootVisitAfterChildrenState;
import com.google.common.css.compiler.ast.DefaultVisitController.RootVisitBeforeChildrenState;
import com.google.common.css.compiler.ast.DefaultVisitController.RootVisitBodyState;
import com.google.common.css.compiler.ast.DefaultVisitController.RootVisitCharsetState;
import com.google.common.css.compiler.ast.DefaultVisitController.RootVisitImportBlockState;
import com.google.common.css.compiler.ast.DefaultVisitController.VisitBlockChildrenState;
import com.google.common.css.compiler.ast.DefaultVisitController.VisitDefinitionState;
import com.google.common.css.compiler.ast.DefaultVisitController.VisitImportBlockChildrenState;
import com.google.common.css.compiler.ast.DefaultVisitController.VisitImportRuleState;
import com.google.common.css.compiler.ast.DefaultVisitController.VisitReplaceChildrenState;

import junit.framework.TestCase;

import org.easymock.EasyMock;
import org.easymock.IMocksControl;

import java.util.List;

/**
 * Unit tests for {@link DefaultVisitController}.
 *
 * TODO(oana): Add more unit tests.
 *
 * @author oana@google.com (Oana Florescu)
 */
public class DefaultVisitControllerTest extends TestCase {

  public void testConstructor() {
    DefaultVisitController visitController = new DefaultVisitController(
        new CssTree((SourceCode) null), false);

    assertTrue(visitController.getStateStack().isEmpty());
  }

  public void testVisitBlock() {
    CssLiteralNode literal = new CssLiteralNode("");
    CssDefinitionNode def = new CssDefinitionNode(literal);
    CssBlockNode block = new CssBlockNode(false);
    block.addChildToBack(def);
    CssRootNode root = new CssRootNode(block);
    CssTree tree = new CssTree(null, root);

    IMocksControl mockControl = EasyMock.createStrictControl();
    DefaultTreeVisitor testVisitor =
        mockControl.createMock(DefaultTreeVisitor.class);

    // Enter Tree gets the root - there is no enterRoot.
    expect(testVisitor.enterTree(eq(root))).andReturn(true);

    // There are blocks that get created even if you don't add them.
    expect(testVisitor.enterImportBlock((CssImportBlockNode)anyObject())).andReturn(true);
    testVisitor.leaveImportBlock((CssImportBlockNode)anyObject());

    // Then we enter the block.
    expect(testVisitor.enterBlock(eq(block))).andReturn(true);

    // Then another node that we created.
    expect(testVisitor.enterDefinition(eq(def))).andReturn(true);

    // Exit the nodes.
    testVisitor.leaveDefinition(eq(def));
    testVisitor.leaveBlock(eq(block));
    testVisitor.leaveTree(eq(root));

    mockControl.replay();

    DefaultVisitController controller = new DefaultVisitController(tree, true);
    controller.startVisit(testVisitor);

    mockControl.verify();
  }

  public void testVisitProperties() {

    CssValueNode first = new CssLiteralNode("one", null);
    CssValueNode second = new CssLiteralNode("two", null);
    CssValueNode third = new CssLiteralNode("three", null);
    CssPropertyNode propName = new CssPropertyNode("prop");

    CssPropertyValueNode propValue = new CssPropertyValueNode();
    propValue.addChildToBack(first);
    propValue.addChildToBack(second);
    propValue.addChildToBack(third);

    CssDeclarationNode decl = new CssDeclarationNode(propName, propValue);
    CssDeclarationBlockNode dBlock = new CssDeclarationBlockNode();
    dBlock.addChildToBack(decl);

    CssClassSelectorNode classSelector = new CssClassSelectorNode("foo", null);
    CssIdSelectorNode idSelector = new CssIdSelectorNode("bar", null);
    CssPseudoClassNode pseudoClass = new CssPseudoClassNode("foo", null);
    CssPseudoElementNode pseudoElement = new CssPseudoElementNode("bar", null);
    CssAttributeSelectorNode attrSelector = new CssAttributeSelectorNode(
        MatchType.EXACT, "hreflang",
        new CssStringNode(
            CssStringNode.Type.DOUBLE_QUOTED_STRING, "en"), null);

    CssSelectorNode selector = new CssSelectorNode("name", null);
    selector.getRefiners().addChildToBack(classSelector);
    selector.getRefiners().addChildToBack(idSelector);
    selector.getRefiners().addChildToBack(pseudoClass);
    selector.getRefiners().addChildToBack(pseudoElement);
    selector.getRefiners().addChildToBack(attrSelector);

    CssRulesetNode ruleset = new CssRulesetNode(dBlock);
    ruleset.addSelector(selector);

    CssBlockNode block = new CssBlockNode(false);
    block.addChildToBack(ruleset);

    CssRootNode root = new CssRootNode(block);
    CssTree tree = new CssTree(null, root);

    IMocksControl mockControl = EasyMock.createStrictControl();
    DefaultTreeVisitor testVisitor =
        mockControl.createMock(DefaultTreeVisitor.class);

    // After all of that to get to a multi-valued property list, we
    // visit the tree and check what we see.
    expect(testVisitor.enterTree(eq(root))).andReturn(true);
    expect(testVisitor.enterImportBlock((CssImportBlockNode)anyObject())).andReturn(true);
    testVisitor.leaveImportBlock((CssImportBlockNode)anyObject());
    expect(testVisitor.enterBlock(eq(block))).andReturn(true);
    expect(testVisitor.enterRuleset(eq(ruleset))).andReturn(true);
    expect(testVisitor.enterSelectorBlock((CssSelectorListNode) anyObject())).andReturn(true);
    expect(testVisitor.enterSelector(eq(selector))).andReturn(true);
    expect(testVisitor.enterClassSelector(eq(classSelector))).andReturn(true);
    testVisitor.leaveClassSelector(eq(classSelector));
    expect(testVisitor.enterIdSelector(eq(idSelector))).andReturn(true);
    testVisitor.leaveIdSelector(eq(idSelector));
    expect(testVisitor.enterPseudoClass(eq(pseudoClass))).andReturn(true);
    testVisitor.leavePseudoClass(eq(pseudoClass));
    expect(testVisitor.enterPseudoElement(eq(pseudoElement))).andReturn(true);
    testVisitor.leavePseudoElement(eq(pseudoElement));
    expect(testVisitor.enterAttributeSelector(eq(attrSelector))).andReturn(true);
    testVisitor.leaveAttributeSelector(eq(attrSelector));
    testVisitor.leaveSelector(eq(selector));
    testVisitor.leaveSelectorBlock((CssSelectorListNode) anyObject());
    expect(testVisitor.enterDeclarationBlock(eq(dBlock))).andReturn(true);
    expect(testVisitor.enterDeclaration(eq(decl))).andReturn(true);
    expect(testVisitor.enterPropertyValue(eq(propValue))).andReturn(true);
    expect(testVisitor.enterValueNode(eq(first))).andReturn(true);
    testVisitor.leaveValueNode(eq(first));
    expect(testVisitor.enterValueNode(eq(second))).andReturn(true);
    testVisitor.leaveValueNode(eq(second));
    expect(testVisitor.enterValueNode(eq(third))).andReturn(true);
    testVisitor.leaveValueNode(eq(third));
    testVisitor.leavePropertyValue(eq(propValue));
    testVisitor.leaveDeclaration(eq(decl));
    testVisitor.leaveDeclarationBlock(eq(dBlock));
    testVisitor.leaveRuleset(eq(ruleset));
    testVisitor.leaveBlock(eq(block));
    testVisitor.leaveTree(eq(root));

    mockControl.replay();

    DefaultVisitController controller = new DefaultVisitController(tree, true);
    controller.startVisit(testVisitor);

    mockControl.verify();
  }

  public void testStateStack() {
    CssTree tree = new CssTree((SourceCode) null);
    DefaultVisitController visitController = new DefaultVisitController(
        tree, false);

    RootVisitBeforeChildrenState state
        = visitController.new RootVisitBeforeChildrenState(tree.getRoot());
    visitController.getStateStack().push(state);
    assertEquals(state, visitController.getStateStack().getTop());
    assertEquals(1, visitController.getStateStack().size());

    visitController.getStateStack().pop();
    assertTrue(visitController.getStateStack().isEmpty());
  }

  public void testRootVisitBeforeChildrenState() {
    CssTree tree = new CssTree((SourceCode) null);
    DefaultVisitController visitController = new DefaultVisitController(
        tree, false);
    RootVisitBeforeChildrenState state
        = visitController.new RootVisitBeforeChildrenState(tree.getRoot());

    visitController.getStateStack().push(state);
    assertEquals(state, visitController.getStateStack().getTop());

    state.transitionToNextState();
    assertEquals(1, visitController.getStateStack().size());
    assertTrue(visitController.getStateStack()
        .getTop() instanceof RootVisitCharsetState);
  }

  public void testRootVisitCharsetState() {
    CssTree tree = new CssTree((SourceCode) null);
    DefaultVisitController visitController = new DefaultVisitController(
        tree, true);
    RootVisitCharsetState state
        = visitController.new RootVisitCharsetState(tree.getRoot(),
            tree.getRoot().getCharsetRule());

    visitController.getStateStack().push(state);
    assertEquals(state, visitController.getStateStack().getTop());

    visitController.removeCurrentNode();
    assertNull(tree.getRoot().getCharsetRule());

    state.transitionToNextState();
    assertEquals(1, visitController.getStateStack().size());
    assertTrue(visitController.getStateStack()
        .getTop() instanceof RootVisitImportBlockState);
  }

  public void testRootVisitImportBlockState() {
    CssTree tree = new CssTree((SourceCode) null);
    DefaultVisitController visitController = new DefaultVisitController(
        tree, true);
    RootVisitImportBlockState state
        = visitController.new RootVisitImportBlockState(tree.getRoot(),
            tree.getRoot().getImportRules());

    visitController.getStateStack().push(state);
    assertEquals(state, visitController.getStateStack().getTop());

    state.transitionToNextState();
    assertEquals(2, visitController.getStateStack().size());
    assertTrue(visitController.getStateStack()
        .getTop() instanceof VisitImportBlockChildrenState);

    state.transitionToNextState();
    assertEquals(2, visitController.getStateStack().size());
    assertTrue(visitController.getStateStack()
        .getTop() instanceof RootVisitBodyState);
  }

  public void testVisitImportBlockChildrenState() {
    CssTree tree = new CssTree((SourceCode) null);
    DefaultVisitController visitController = new DefaultVisitController(
        tree, true);
    visitController.visitor = new DefaultTreeVisitor();
    CssImportBlockNode cssImportBlockNode = new CssImportBlockNode();
    cssImportBlockNode.setChildren(Lists.newArrayList(new CssImportRuleNode()));
    VisitImportBlockChildrenState state
        = visitController.new VisitImportBlockChildrenState(cssImportBlockNode);

    visitController.getStateStack().push(state);
    assertEquals(state, visitController.getStateStack().getTop());

    state.transitionToNextState();
    assertEquals(2, visitController.getStateStack().size());
    assertTrue(visitController.getStateStack()
        .getTop() instanceof VisitImportRuleState);

    visitController.getStateStack().getTop().transitionToNextState();
    assertEquals(1, visitController.getStateStack().size());
    assertTrue(visitController.getStateStack()
        .getTop() instanceof VisitImportBlockChildrenState);

    state.transitionToNextState();
    assertTrue(visitController.getStateStack().isEmpty());
  }

  public void testVisitImportRuleState() {
    CssTree tree = new CssTree((SourceCode) null);
    DefaultVisitController visitController = new DefaultVisitController(
        tree, true);
    visitController.visitor = new DefaultTreeVisitor();
    VisitImportRuleState state
        = visitController.new VisitImportRuleState(new CssImportRuleNode());

    visitController.getStateStack().push(state);
    assertEquals(state, visitController.getStateStack().getTop());

    state.transitionToNextState();
    assertTrue(visitController.getStateStack().isEmpty());
  }

  public void testRootVisitBodyState() {
    CssTree tree = new CssTree((SourceCode) null);
    DefaultVisitController visitController = new DefaultVisitController(
        tree, true);
    visitController.visitor = new DefaultTreeVisitor();
    RootVisitBodyState state
        = visitController.new RootVisitBodyState(tree.getRoot(),
            tree.getRoot().getBody());

    visitController.getStateStack().push(state);
    assertEquals(state, visitController.getStateStack().getTop());

    state.transitionToNextState();
    assertEquals(2, visitController.getStateStack().size());
    assertTrue(visitController.getStateStack()
        .getTop() instanceof VisitBlockChildrenState);

    visitController.getStateStack().getTop().transitionToNextState();
    assertEquals(1, visitController.getStateStack().size());
    assertTrue(visitController.getStateStack()
        .getTop() instanceof RootVisitBodyState);

    state.transitionToNextState();
    assertEquals(1, visitController.getStateStack().size());
    assertTrue(visitController.getStateStack()
        .getTop() instanceof RootVisitAfterChildrenState);
  }

  public void testVisitBlockChildrenState1() {
    CssDefinitionNode def = new CssDefinitionNode(new CssLiteralNode(""));
    CssBlockNode block = new CssBlockNode(false);
    block.addChildToBack(def);

    CssRootNode root = new CssRootNode(block);
    CssTree tree = new CssTree(null, root);
    DefaultVisitController visitController = new DefaultVisitController(
        tree, true);
    visitController.visitor = new DefaultTreeVisitor();
    VisitReplaceChildrenState<CssNode> state
        = visitController.new VisitBlockChildrenState(tree.getRoot().getBody());

    visitController.getStateStack().push(state);
    assertEquals(state, visitController.getStateStack().getTop());
    assertEquals(-1, state.currentIndex);

    state.transitionToNextState();
    assertEquals(2, visitController.getStateStack().size());
    assertTrue(visitController.getStateStack()
        .getTop() instanceof VisitDefinitionState);
    assertEquals(0, state.currentIndex);

    visitController.getStateStack().getTop().transitionToNextState();
    assertEquals(3, visitController.getStateStack().size());

    state.transitionToNextState();
    state.transitionToNextState();
    state.transitionToNextState();
    assertTrue(visitController.getStateStack().isEmpty());
  }

  public void testVisitBlockChildrenState2() {
    CssDefinitionNode def = new CssDefinitionNode(new CssLiteralNode(""));
    CssBlockNode block = new CssBlockNode(false);
    block.addChildToBack(def);
    def = new CssDefinitionNode(new CssLiteralNode(""));
    block.addChildToBack(def);

    CssRootNode root = new CssRootNode(block);
    CssTree tree = new CssTree(null, root);
    DefaultVisitController visitController = new DefaultVisitController(
        tree, true);
    visitController.visitor = new DefaultTreeVisitor();
    VisitReplaceChildrenState<CssNode> state
        = visitController.new VisitBlockChildrenState(tree.getRoot().getBody());

    visitController.getStateStack().push(state);
    assertEquals(state, visitController.getStateStack().getTop());
    assertEquals(-1, state.currentIndex);

    state.transitionToNextState();
    assertEquals(2, visitController.getStateStack().size());
    assertTrue(visitController.getStateStack()
        .getTop() instanceof VisitDefinitionState);
    assertEquals(0, state.currentIndex);

    state.removeCurrentChild();
    assertEquals(0, state.currentIndex);

    state.removeCurrentChild();
    assertEquals(0, state.currentIndex);

    visitController.getStateStack().getTop().transitionToNextState();
    assertEquals(3, visitController.getStateStack().size());

    state.transitionToNextState();
    state.transitionToNextState();
    state.transitionToNextState();
    assertTrue(visitController.getStateStack().isEmpty());
    assertEquals(0, state.currentIndex);
  }

  public void testVisitBlockChildrenState3() {
    CssDefinitionNode def = new CssDefinitionNode(new CssLiteralNode(""));
    CssBlockNode block = new CssBlockNode(false);
    block.addChildToBack(def);
    def = new CssDefinitionNode(new CssLiteralNode(""));
    block.addChildToBack(def);

    CssRootNode root = new CssRootNode(block);
    CssTree tree = new CssTree(null, root);
    DefaultVisitController visitController = new DefaultVisitController(
        tree, true);
    visitController.visitor = new DefaultTreeVisitor();
    VisitReplaceChildrenState<CssNode> state
        = visitController.new VisitBlockChildrenState(tree.getRoot().getBody());

    visitController.getStateStack().push(state);
    assertEquals(state, visitController.getStateStack().getTop());
    assertEquals(-1, state.currentIndex);

    state.transitionToNextState();
    assertEquals(2, visitController.getStateStack().size());
    assertTrue(visitController.getStateStack()
        .getTop() instanceof VisitDefinitionState);
    assertEquals(0, state.currentIndex);

    state.replaceCurrentBlockChildWith(
        Lists.<CssNode>newArrayList(
            new CssDefinitionNode(new CssLiteralNode("")),
            new CssDefinitionNode(new CssLiteralNode(""))),
        true);
    assertEquals(0, state.currentIndex);

    state.removeCurrentChild();
    assertEquals(0, state.currentIndex);

    visitController.getStateStack().getTop().transitionToNextState();
    assertEquals(3, visitController.getStateStack().size());
  }

  public void testVisitSimpleUnknownAtRule() {
    CssLiteralNode defLit = new CssLiteralNode("def");
    CssUnknownAtRuleNode atDef = new CssUnknownAtRuleNode(defLit, false);
    CssLiteralNode xLit = new CssLiteralNode("x");
    CssLiteralNode yLit = new CssLiteralNode("y");
    List<CssValueNode> defParameters = Lists.newArrayList((CssValueNode) xLit, (CssValueNode) yLit);
    atDef.setParameters(defParameters);
    CssBlockNode block = new CssBlockNode(false);
    block.addChildToBack(atDef);
    CssRootNode root = new CssRootNode(block);
    CssTree tree = new CssTree(null, root);

    IMocksControl mockControl = EasyMock.createStrictControl();
    DefaultTreeVisitor testVisitor =
        mockControl.createMock(DefaultTreeVisitor.class);

    // Enter Tree gets the root - there is no enterRoot.
    expect(testVisitor.enterTree(eq(root))).andReturn(true);

    // There are blocks that get created even if you don't add them.
    expect(testVisitor.enterImportBlock((CssImportBlockNode)anyObject())).andReturn(true);
    testVisitor.leaveImportBlock((CssImportBlockNode)anyObject());

    // Then we enter the block.
    expect(testVisitor.enterBlock(eq(block))).andReturn(true);

    // Then we enter the unknown at-rule node that we created.
    expect(testVisitor.enterUnknownAtRule(eq(atDef))).andReturn(true);

    // Then the media type list params
    for (int i = 0; i < defParameters.size(); ++i) {
      expect(
          testVisitor.enterValueNode(eq(defParameters.get(i)))).andReturn(true);
      testVisitor.leaveValueNode(eq(defParameters.get(i)));
      if (i < defParameters.size() - 1) {
        expect(testVisitor.enterMediaTypeListDelimiter(eq(atDef)))
            .andReturn(true);
        testVisitor.leaveMediaTypeListDelimiter(eq(atDef));
      }
    }

    // We've got no block associated with this at rule.

    // Exit the nodes.
    testVisitor.leaveUnknownAtRule(eq(atDef));
    testVisitor.leaveBlock(eq(block));
    testVisitor.leaveTree(eq(root));

    mockControl.replay();

    DefaultVisitController controller = new DefaultVisitController(tree, true);
    controller.startVisit(testVisitor);

    mockControl.verify();
  }

  public void testVisitComplexUnknownAtRule() {
    CssLiteralNode defLit = new CssLiteralNode("def");
    CssUnknownAtRuleNode atDef = new CssUnknownAtRuleNode(defLit, false);
    CssLiteralNode xLit = new CssLiteralNode("x");
    CssLiteralNode yLit = new CssLiteralNode("y");
    List<CssValueNode> defParameters = Lists.newArrayList((CssValueNode) xLit, (CssValueNode) yLit);
    atDef.setParameters(defParameters);
    CssBlockNode defBlock = new CssBlockNode(true);
    defBlock.addChildToBack(atDef);

    CssLiteralNode ifLit = new CssLiteralNode("if");
    CssUnknownAtRuleNode atIf = new CssUnknownAtRuleNode(ifLit, true);
    CssBooleanExpressionNode ifCondition = new CssBooleanExpressionNode(
        CssBooleanExpressionNode.Type.CONSTANT, "some condition", null, null);
    List<CssValueNode> ifParameters = Lists.newArrayList((CssValueNode) ifCondition);
    atIf.setParameters(ifParameters);
    atIf.setBlock(defBlock);

    CssBlockNode block = new CssBlockNode(false);
    block.addChildToBack(atIf);
    CssRootNode root = new CssRootNode(block);
    CssTree tree = new CssTree(null, root);

    IMocksControl mockControl = EasyMock.createStrictControl();
    DefaultTreeVisitor testVisitor =
        mockControl.createMock(DefaultTreeVisitor.class);

    // Enter Tree gets the root - there is no enterRoot.
    expect(testVisitor.enterTree(eq(root))).andReturn(true);

    // There are blocks that get created even if you don't add them.
    expect(testVisitor.enterImportBlock((CssImportBlockNode)anyObject())).andReturn(true);
    testVisitor.leaveImportBlock((CssImportBlockNode)anyObject());

    // Then we enter the block.
    expect(testVisitor.enterBlock(eq(block))).andReturn(true);

    // Then we enter the unknown 'if' at-rule node that we created.
    expect(testVisitor.enterUnknownAtRule(eq(atIf))).andReturn(true);

    // Then the media type list params for 'if'
    for (int i = 0; i < ifParameters.size(); ++i) {
      expect(
          testVisitor.enterValueNode(eq(ifParameters.get(i)))).andReturn(true);
      testVisitor.leaveValueNode(eq(ifParameters.get(i)));
      if (i < ifParameters.size() - 1) {
        expect(testVisitor.enterMediaTypeListDelimiter(eq(atIf)))
            .andReturn(true);
        testVisitor.leaveMediaTypeListDelimiter(eq(atIf));
      }
    }

    // Then we enter the defBlock.
    expect(testVisitor.enterBlock(eq(defBlock))).andReturn(true);

    // Then we enter the unknown 'def' at-rule node within the 'if'.
    expect(testVisitor.enterUnknownAtRule(eq(atDef))).andReturn(true);

    // Then the media type list params for 'def'
    for (int i = 0; i < defParameters.size(); ++i) {
      expect(
          testVisitor.enterValueNode(eq(defParameters.get(i)))).andReturn(true);
      testVisitor.leaveValueNode(eq(defParameters.get(i)));
      if (i < defParameters.size() - 1) {
        expect(testVisitor.enterMediaTypeListDelimiter(eq(atDef)))
            .andReturn(true);
        testVisitor.leaveMediaTypeListDelimiter(eq(atDef));
      }
    }

    // Exit the nodes.
    testVisitor.leaveUnknownAtRule(eq(atDef));
    testVisitor.leaveBlock(eq(defBlock));
    testVisitor.leaveUnknownAtRule(eq(atIf));
    testVisitor.leaveBlock(eq(block));
    testVisitor.leaveTree(eq(root));

    mockControl.replay();

    DefaultVisitController controller = new DefaultVisitController(tree, true);
    controller.startVisit(testVisitor);

    mockControl.verify();
  }

  public void testVisitComponent() {
    CssLiteralNode x = new CssLiteralNode("FOO");
    CssDefinitionNode def = new CssDefinitionNode(x);

    CssBlockNode compBlock = new CssBlockNode(true);
    compBlock.addChildToBack(def);

    CssLiteralNode compLit = new CssLiteralNode("CSS_BAR");
    CssComponentNode comp = new CssComponentNode(compLit, null, false,
        CssComponentNode.PrefixStyle.LITERAL, compBlock);

    CssBlockNode block = new CssBlockNode(false);
    block.addChildToBack(comp);
    CssRootNode root = new CssRootNode(block);
    CssTree tree = new CssTree(null, root);

    IMocksControl mockControl = EasyMock.createStrictControl();
    DefaultTreeVisitor testVisitor =
        mockControl.createMock(DefaultTreeVisitor.class);

    // Enter Tree gets the root - there is no enterRoot.
    expect(testVisitor.enterTree(eq(root))).andReturn(true);

    // There are blocks that get created even if you don't add them.
    expect(testVisitor.enterImportBlock((CssImportBlockNode)anyObject())).andReturn(true);
    testVisitor.leaveImportBlock((CssImportBlockNode)anyObject());

    // Then we enter the block.
    expect(testVisitor.enterBlock(eq(block))).andReturn(true);

    // Then we enter the component that we created.
    expect(testVisitor.enterComponent(eq(comp))).andReturn(true);

    // Then we enter the definition within the component.
    expect(testVisitor.enterDefinition(eq(def))).andReturn(true);

    // Exit the nodes.
    testVisitor.leaveDefinition(eq(def));
    testVisitor.leaveComponent(eq(comp));
    testVisitor.leaveBlock(eq(block));
    testVisitor.leaveTree(eq(root));

    mockControl.replay();

    DefaultVisitController controller = new DefaultVisitController(tree, true);
    controller.startVisit(testVisitor);

    mockControl.verify();
  }

  public void testVisitValueNodes() {
    List<CssValueNode> simpleValues = Lists.newLinkedList();
    for (String v : new String [] {"a", "b", "c"}) {
      simpleValues.add(new CssLiteralNode(v, null));
    }
    CssCompositeValueNode parent =
        new CssCompositeValueNode(
            simpleValues, CssCompositeValueNode.Operator.COMMA, null);

    CssPropertyValueNode propValue = new CssPropertyValueNode();
    propValue.addChildToBack(parent);
    CssDeclarationNode decl =
        new CssDeclarationNode(
            new CssPropertyNode("prop"),
            propValue);
    CssDeclarationBlockNode db = new CssDeclarationBlockNode();
    db.addChildToBack(decl);
    CssRulesetNode ruleset = new CssRulesetNode(db);
    ruleset.addSelector(new CssSelectorNode("name", null));
    CssBlockNode b = new CssBlockNode(false);
    b.addChildToBack(ruleset);
    CssTree t = new CssTree(null, new CssRootNode(b));

    final List<CssValueNode> cNodes = Lists.newLinkedList();
    final List<CssValueNode> evnNodes = Lists.newLinkedList();
    DefaultTreeVisitor testVisitor = new DefaultTreeVisitor() {
        @Override
        public boolean enterCompositeValueNode(CssCompositeValueNode c) {
          cNodes.add(c);
          return true;
        }
        @Override
        public boolean enterValueNode(CssValueNode n) {
          evnNodes.add(n);
          return true;
        }
      };
    DefaultVisitController controller = new DefaultVisitController(t, true);
    controller.startVisit(testVisitor);

    assertEquals(simpleValues.size(), evnNodes.size());
    for (CssValueNode i : simpleValues) {
      assertTrue(evnNodes.contains(i));
    }
    assertEquals(1, cNodes.size());
    assertTrue(cNodes.contains(parent));
  }

  public void testVisitCompositeValueNodeWithFunction() {
    List<CssValueNode> simpleValues = Lists.newLinkedList();
    simpleValues.add(
        new CssFunctionNode(CssFunctionNode.Function.byName("url"), null));
    simpleValues.add(
        new CssFunctionNode(CssFunctionNode.Function.byName("url"), null));

    CssCompositeValueNode parent =
        new CssCompositeValueNode(
            simpleValues, CssCompositeValueNode.Operator.COMMA, null);

    CssPropertyValueNode propValue = new CssPropertyValueNode();
    propValue.addChildToBack(parent);
    CssDeclarationNode decl =
        new CssDeclarationNode(
            new CssPropertyNode("prop"),
            propValue);
    CssDeclarationBlockNode db = new CssDeclarationBlockNode();
    db.addChildToBack(decl);
    CssRulesetNode ruleset = new CssRulesetNode(db);
    ruleset.addSelector(new CssSelectorNode("name", null));
    CssBlockNode b = new CssBlockNode(false);
    b.addChildToBack(ruleset);
    CssTree t = new CssTree(null, new CssRootNode(b));

    final List<CssValueNode> compositeNode = Lists.newLinkedList();
    final List<CssValueNode> functionNodes = Lists.newLinkedList();
    DefaultTreeVisitor testVisitor = new DefaultTreeVisitor() {
        @Override
        public boolean enterCompositeValueNode(CssCompositeValueNode c) {
          compositeNode.add(c);
          return true;
        }
        @Override
        public boolean enterFunctionNode(CssFunctionNode n) {
          functionNodes.add(n);
          return true;
        }
      };
    DefaultVisitController controller = new DefaultVisitController(t, true);
    controller.startVisit(testVisitor);

    assertEquals(2, functionNodes.size());
    assertEquals(1, compositeNode.size());
    assertTrue(compositeNode.contains(parent));
  }

  private static class ValueDetector extends DefaultTreeVisitor {
    private final String quarry;
    private final boolean[] foundValue = {false};
    @Override public boolean enterValueNode(CssValueNode node) {
      if (quarry.equals(node.getValue())) {
        foundValue[0] = true;
      }
      return true;
    }
    private ValueDetector(String quarry) { this.quarry = quarry; }
    public static boolean detect(CssTree t, String quarry) {
      ValueDetector detector = new ValueDetector(quarry);
      new DefaultVisitController(
          t, false /* allowMutating */).startVisit(detector);
      return detector.foundValue[0];
    }
  }

  private static class FunctionDetector extends DefaultTreeVisitor {
    private final boolean[] foundValue = {false};
    @Override public boolean enterFunctionNode(CssFunctionNode node) {
      foundValue[0] = true;
      return true;
    }
    public static boolean detect(CssTree t) {
      FunctionDetector detector = new FunctionDetector();
      new DefaultVisitController(
          t, false /* allowMutating */).startVisit(detector);
      return detector.foundValue[0];
    }
  }

  public void verifyRemoveablePropertyValueElement(String backgroundValue) {
    try {
      CssTree t = new GssParser(
          new com.google.common.css.SourceCode(null,
              String.format("p { background: %s; }", backgroundValue)))
          .parse();
      assertTrue(
          "This test assumes we start with a stylesheet containing detectable "
          + "function nodes.",
          FunctionDetector.detect(t));
      final DefaultVisitController vc =
          new DefaultVisitController(t, true /* allowMutating */);
      CssTreeVisitor functionRemover = new DefaultTreeVisitor() {
          @Override public boolean enterFunctionNode(CssFunctionNode node) {
            System.err.println(node.getParent().getClass().getName());
            vc.removeCurrentNode();
            return true;
          }
        };
      vc.startVisit(functionRemover);
      assertFalse(
          "We should be able to remove function nodes that occur as property "
          + "values.",
          FunctionDetector.detect(t));
      assertTrue(
          "Removing one composite element within a property value should not "
          + "affect its siblings.",
          ValueDetector.detect(t, "red"));
      assertTrue(
          "Removing one composite element within a property value should not "
          + "affect its siblings.",
          ValueDetector.detect(t, "fixed"));
    } catch (GssParserException e) {
      throw new RuntimeException(e);
    }
  }

  public void testRemoveCompositePropertyValueElement() {
    verifyRemoveablePropertyValueElement(
        "url(http://www.google.com/logo), red fixed");
  }

  public void testRemoveCompositePropertyValueElementMiddle() {
    verifyRemoveablePropertyValueElement(
        "red, url(http://www.google.com/logo), fixed");
  }

  public void testRemoveCompositePropertyValueElementEnd() {
    verifyRemoveablePropertyValueElement(
        "red fixed, url(http://www.google.com/logo)");
  }

  public void testVisitForLoop() {
    CssLiteralNode x = new CssLiteralNode("FOO");
    CssDefinitionNode def = new CssDefinitionNode(x);

    CssBlockNode loopBlock = new CssBlockNode(true);
    loopBlock.addChildToBack(def);

    CssValueNode from = new CssNumericNode("1", CssNumericNode.NO_UNITS);
    CssValueNode to = new CssNumericNode("5", CssNumericNode.NO_UNITS);
    CssValueNode step = new CssNumericNode("2", CssNumericNode.NO_UNITS);

    CssLiteralNode variableNode = new CssLiteralNode("for");
    CssForLoopRuleNode loop = new CssForLoopRuleNode(
        variableNode, loopBlock, null, from, to, step, "i", 0, null);

    CssBlockNode block = new CssBlockNode(false);
    block.addChildToBack(loop);
    CssRootNode root = new CssRootNode(block);
    CssTree tree = new CssTree(null, root);

    IMocksControl mockControl = EasyMock.createStrictControl();
    DefaultTreeVisitor testVisitor =
        mockControl.createMock(DefaultTreeVisitor.class);

    // Enter Tree gets the root - there is no enterRoot.
    expect(testVisitor.enterTree(eq(root))).andReturn(true);

    // There are blocks that get created even if you don't add them.
    expect(testVisitor.enterImportBlock((CssImportBlockNode) anyObject())).andReturn(true);
    testVisitor.leaveImportBlock((CssImportBlockNode) anyObject());

    // Then we enter the block.
    expect(testVisitor.enterBlock(eq(block))).andReturn(true);

    // Then we enter the for loop node.
    expect(testVisitor.enterForLoop(eq(loop))).andReturn(true);

    // Then we enter the definition within the for loop.
    expect(testVisitor.enterDefinition(eq(def))).andReturn(true);

    // Exit the nodes.
    testVisitor.leaveDefinition(eq(def));
    testVisitor.leaveForLoop(eq(loop));
    testVisitor.leaveBlock(eq(block));
    testVisitor.leaveTree(eq(root));

    mockControl.replay();

    DefaultVisitController controller = new DefaultVisitController(tree, true);
    controller.startVisit(testVisitor);

    mockControl.verify();
  }
}
