/*
 * 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.passes;

import com.google.common.css.compiler.ast.CssConditionalBlockNode;
import com.google.common.css.compiler.ast.CssConditionalRuleNode;
import com.google.common.css.compiler.ast.CssDeclarationBlockNode;
import com.google.common.css.compiler.ast.CssMediaRuleNode;
import com.google.common.css.compiler.ast.CssNode;
import com.google.common.css.compiler.ast.CssRulesetNode;
import com.google.common.css.compiler.ast.GssParserException;
import com.google.common.css.compiler.passes.testing.PassesTestBase;

/**
 * Unit tests for {@link MarkNonFlippableNodes}.
 *
 * @author oana@google.com (Oana Florescu)
 * @author fbenz@google.com (Florian Benz)
 */
public class MarkNonFlippableNodesTest extends PassesTestBase {

  public void testMisplacedAnnotation() throws GssParserException {
    parseAndRun(linesToString(
        ".CLASSX,",
        "/* @noflip */ .CLASSY {",
        "  left:10px;",
        "}"),
        MarkNonFlippableNodes.INVALID_NOFLIP_ERROR_MESSAGE);
  }

  public void testMarkNonFlippableSingelDeclaration() {
    parseAndBuildTree(linesToString(
        ".CLASSX {",
        "  /* @noflip */ left: 10px;",
        "  margin: 10px 9px 8px 7px;",
        "}"));
    runPass();

    CssRulesetNode ruleset =
        (CssRulesetNode) tree.getRoot().getBody().getChildren().get(0);
    // The ruleset itself has to be marked as flippable.
    assertTrue(ruleset.getShouldBeFlipped());

    CssDeclarationBlockNode block = ruleset.getDeclarations();
    assertEquals(2, block.getChildren().size());
    // The first declaration has to be marked as not flippable.
    assertFalse(block.getChildren().get(0).getShouldBeFlipped());
    // The second declaration has to be marked as flippable.
    assertTrue(block.getChildren().get(1).getShouldBeFlipped());
  }

  public void testMarkNonFlippableRuleset() {
    parseAndBuildTree(linesToString(
        "/* @noflip */ .CLASSX {",
        "  left: 10px;",
        "  margin: 10px 9px 8px 7px;",
        "}"));
    runPass();

    CssRulesetNode ruleset =
        (CssRulesetNode) tree.getRoot().getBody().getChildren().get(0);
    // The ruleset itself has to be marked as not flippable.
    assertFalse(ruleset.getShouldBeFlipped());

    // All declaration in the ruleset have to be marked as flippable.
    CssDeclarationBlockNode block = ruleset.getDeclarations();
    assertEquals(2, block.getChildren().size());
    assertFalse(block.getChildren().get(0).getShouldBeFlipped());
    assertFalse(block.getChildren().get(1).getShouldBeFlipped());
  }

  public void testMarkNonFlippableConditional() {
    parseAndBuildTree(linesToString(
        "/* @noflip */ @if COND {",
        "  foo { top : expression('cond') }",
        "  bar { bottom : expression('cond') }",
        "} @else {",
        "  bar { top : expression('cond') }",
        "}"));
    new CreateConditionalNodes(tree.getMutatingVisitController(),
        errorManager).runPass();
    runPass();

    CssConditionalBlockNode block =
        (CssConditionalBlockNode) tree.getRoot().getBody().getChildren().get(0);
    assertFalse(block.getShouldBeFlipped());

    // Check each of the conditional rules.
    for (CssConditionalRuleNode rule : block.getChildren()) {
      assertFalse(rule.getShouldBeFlipped());
      // Check each of the rules inside the conditional rule.
      for (CssNode node : rule.getBlock().getChildren()) {
        assertTrue(node instanceof CssRulesetNode);
        assertFalse(node.getShouldBeFlipped());
        // Check each of the declarations inside the style rule.
        for (CssNode decl
            : ((CssRulesetNode) node).getDeclarations().getChildren()) {
          assertFalse(decl.getShouldBeFlipped());
        }
      }
    }
  }

  public void testMarkNonFlippableInConditional() {
    parseAndBuildTree(linesToString(
        "@if COND {",
        "  /* @noflip */foo { top : expression('cond') }",
        "  bar { bottom : expression('cond') }",
        "} @else {",
        "  bar { top : expression('cond') }",
        "}"));
    new CreateConditionalNodes(tree.getMutatingVisitController(),
        errorManager).runPass();
    runPass();

    CssConditionalBlockNode block =
        (CssConditionalBlockNode) tree.getRoot().getBody().getChildren().get(0);
    assertTrue(block.getShouldBeFlipped());

    // Check each of the conditional rules.

    // First conditional rule.
    CssConditionalRuleNode rule = block.getChildren().get(0);
    assertTrue(rule.getShouldBeFlipped());
    // Check each of the rules inside the conditional rule.
    int i = 0;
    for (CssNode node : rule.getBlock().getChildren()) {
      assertTrue(node instanceof CssRulesetNode);
      if (i == 0) {
        // Only the first rule is non flippable.
        assertFalse(node.getShouldBeFlipped());
        i++;
        // Check each of the declarations inside the style rule.
        for (CssNode decl
            : ((CssRulesetNode) node).getDeclarations().getChildren()) {
          assertFalse(decl.getShouldBeFlipped());
        }
      } else {
        assertTrue(node.getShouldBeFlipped());
        // Check each of the declarations inside the style rule.
        for (CssNode decl
            : ((CssRulesetNode) node).getDeclarations().getChildren()) {
          assertTrue(decl.getShouldBeFlipped());
        }
      }
    }

    // Second conditional rule.
    rule = block.getChildren().get(1);
    assertTrue(rule.getShouldBeFlipped());
    // Check each of the rules inside the conditional rule.
    for (CssNode node : rule.getBlock().getChildren()) {
      assertTrue(node instanceof CssRulesetNode);
      assertTrue(node.getShouldBeFlipped());
      // Check each of the declarations inside the style rule.
      for (CssNode decl
          : ((CssRulesetNode) node).getDeclarations().getChildren()) {
        assertTrue(decl.getShouldBeFlipped());
      }
    }
  }

  public void testMarkNonFlippableMedia() {
    parseAndBuildTree(linesToString(
        "@media print /* @noflip */{",
        "  .CSS_RULE_1, .CSS_RULE_2:hover a {",
        "     border: thickBorder(red, 2px);",
        "  }",
        "}",
        "@media tv {",
        "  .CSS_RULE_1, .CSS_RULE_2:hover a /* @noflip */{",
        "     border: thickBorder(green, 2px);",
        "  }",
        "}",
        ".CSS_RULE_3 { /* @noflip */top : expression('cond') }"
        ));
    new CreateStandardAtRuleNodes(tree.getMutatingVisitController(),
        errorManager).runPass();
    runPass();

    CssNode rule = tree.getRoot().getBody().getChildren().get(0);
    assertTrue(rule instanceof CssMediaRuleNode);
    assertFalse(rule.getShouldBeFlipped());

    // Check the rules inside the media rule.
    for (CssNode ruleset : ((CssMediaRuleNode) rule).getBlock().getChildren()) {
      assertTrue(ruleset instanceof CssRulesetNode);
      assertFalse(ruleset.getShouldBeFlipped());
      // Check the declarations inside the ruleset.
      for (CssNode decl
          : ((CssRulesetNode) ruleset).getDeclarations().getChildren()) {
        assertFalse(decl.getShouldBeFlipped());
      }
    }

    // Check the second media rule.
    rule = tree.getRoot().getBody().getChildren().get(1);
    assertTrue(rule instanceof CssMediaRuleNode);
    assertTrue(rule.getShouldBeFlipped());

    for (CssNode ruleset : ((CssMediaRuleNode) rule).getBlock().getChildren()) {
      assertTrue(ruleset instanceof CssRulesetNode);
      assertFalse(ruleset.getShouldBeFlipped());
      // Check the declarations inside the ruleset.
      for (CssNode decl
          : ((CssRulesetNode) ruleset).getDeclarations().getChildren()) {
        assertFalse(decl.getShouldBeFlipped());
      }
    }

    // Check the ruleset following the media rule.
    rule = tree.getRoot().getBody().getChildren().get(2);
    assertTrue(rule instanceof CssRulesetNode);
    assertTrue(rule.getShouldBeFlipped());
    // Check the declarations inside the ruleset.
    for (CssNode decl
        : ((CssRulesetNode) rule).getDeclarations().getChildren()) {
      assertFalse(decl.getShouldBeFlipped());
    }
  }

  @Override
  protected void runPass() {
    MarkNonFlippableNodes pass = new MarkNonFlippableNodes(
        tree.getVisitController(), errorManager);
    pass.runPass();
  }
}
