/*
 * 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.gwt.dev.cfg;

import com.google.gwt.core.ext.Linker;
import com.google.gwt.core.ext.LinkerContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.core.ext.linker.LinkerOrder;
import com.google.gwt.core.ext.linker.LinkerOrder.Order;
import com.google.gwt.core.ext.linker.Shardable;

import junit.framework.TestCase;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.SortedSet;

/**
 * Runs tests directly on ModuleDef.
 */
public class ModuleDefTest extends TestCase {

  @Shardable
  static class FakeLinker extends Linker {
    @Override
    public String getDescription() {
      return null;
    }

    @Override
    public ArtifactSet link(TreeLogger logger, LinkerContext context,
        ArtifactSet artifacts, boolean onePermutation) {
      return null;
    }

    @Override
    public ArtifactSet relink(TreeLogger logger, LinkerContext context,
        ArtifactSet newArtifacts) {
      return null;
    }
  }

  @LinkerOrder(Order.POST)
  @Shardable
  static class FakeLinkerPost extends FakeLinker {
  }

  @LinkerOrder(Order.POST)
  @Shardable
  static class FakeLinkerPost2 extends FakeLinker {
  }

  @LinkerOrder(Order.PRE)
  @Shardable
  static class FakeLinkerPre extends FakeLinker {
  }

  @LinkerOrder(Order.PRE)
  @Shardable
  static class FakeLinkerPre2 extends FakeLinker {
  }

  @LinkerOrder(Order.PRIMARY)
  @Shardable
  static class FakeLinkerPrimary extends FakeLinker {
  }

  @LinkerOrder(Order.PRIMARY)
  @Shardable
  static class FakeLinkerPrimary2 extends FakeLinker {
  }

  public void testCollapsedProperties() {
    ModuleDef def = new ModuleDef("fake");

    Properties p = def.getProperties();
    BindingProperty b = p.createBinding("fake");
    b.addDefinedValue(b.getRootCondition(), "a");
    b.addDefinedValue(b.getRootCondition(), "b");
    b.addDefinedValue(b.getRootCondition(), "c");
    b.addDefinedValue(b.getRootCondition(), "d");
    b.addDefinedValue(b.getRootCondition(), "e");
    b.addDefinedValue(b.getRootCondition(), "f1");
    b.addDefinedValue(b.getRootCondition(), "f2");
    b.addDefinedValue(b.getRootCondition(), "f3");
    b.addDefinedValue(b.getRootCondition(), "g1a");
    b.addDefinedValue(b.getRootCondition(), "g2a");
    b.addDefinedValue(b.getRootCondition(), "g1b");
    b.addDefinedValue(b.getRootCondition(), "g2b");

    // Check de-duplication
    b.addCollapsedValues("a", "b");
    b.addCollapsedValues("b", "a");

    // Check transitivity
    b.addCollapsedValues("c", "d");
    b.addCollapsedValues("c", "e");

    // Check globs
    b.addCollapsedValues("f*");
    b.addCollapsedValues("g*a");

    b.normalizeCollapsedValues();

    List<SortedSet<String>> collapsedValues = b.getCollapsedValuesSets();
    assertEquals(4, collapsedValues.size());
    assertEquals(Arrays.asList("a", "b"), new ArrayList<String>(
        collapsedValues.get(0)));
    assertEquals(Arrays.asList("c", "d", "e"), new ArrayList<String>(
        collapsedValues.get(1)));
    assertEquals(Arrays.asList("f1", "f2", "f3"), new ArrayList<String>(
        collapsedValues.get(2)));
    assertEquals(Arrays.asList("g1a", "g2a"), new ArrayList<String>(
        collapsedValues.get(3)));

    // Collapse everything
    b.addCollapsedValues("*");
    b.normalizeCollapsedValues();

    collapsedValues = b.getCollapsedValuesSets();
    assertEquals(1, collapsedValues.size());
    assertEquals(Arrays.asList(b.getDefinedValues()), new ArrayList<String>(
        collapsedValues.get(0)));
  }

  public void testLinkerOrder() throws UnableToCompleteException {
    ModuleDef def = new ModuleDef("fake");

    def.defineLinker(TreeLogger.NULL, "pre", FakeLinkerPre.class);
    def.defineLinker(TreeLogger.NULL, "pre2", FakeLinkerPre2.class);
    def.defineLinker(TreeLogger.NULL, "post", FakeLinkerPost.class);
    def.defineLinker(TreeLogger.NULL, "post2", FakeLinkerPost2.class);
    def.defineLinker(TreeLogger.NULL, "primary", FakeLinkerPrimary.class);

    def.addLinker("pre2");
    def.addLinker("pre");
    def.addLinker("post");
    def.addLinker("post2");
    def.addLinker("primary");

    Class<?>[] expectedClasses = {
        FakeLinkerPre2.class, FakeLinkerPre.class, FakeLinkerPost.class,
        FakeLinkerPost2.class, FakeLinkerPrimary.class};
    assertEquals(FakeLinkerPrimary.class, def.getActivePrimaryLinker());
    // Test iteration order
    assertEquals(Arrays.asList(expectedClasses),
        new ArrayList<Class<? extends Linker>>(def.getActiveLinkers()));
  }

  public void testLinkerRedefinition() throws UnableToCompleteException {
    ModuleDef def = new ModuleDef("fake");

    def.defineLinker(TreeLogger.NULL, "pre", FakeLinkerPre.class);
    def.defineLinker(TreeLogger.NULL, "post", FakeLinkerPost.class);
    def.defineLinker(TreeLogger.NULL, "primary", FakeLinkerPrimary.class);
    def.addLinker("pre");
    def.addLinker("post");
    def.addLinker("primary");

    def.defineLinker(TreeLogger.NULL, "pre", FakeLinkerPre2.class);
    def.defineLinker(TreeLogger.NULL, "post", FakeLinkerPost2.class);
    def.defineLinker(TreeLogger.NULL, "primary", FakeLinkerPrimary2.class);
    // Intentional duplication
    def.addLinker("post");

    Class<?>[] expectedClasses = {
        FakeLinkerPre2.class, FakeLinkerPost2.class, FakeLinkerPrimary2.class};
    assertEquals(FakeLinkerPrimary2.class, def.getActivePrimaryLinker());
    // Test iteration order
    assertEquals(Arrays.asList(expectedClasses),
        new ArrayList<Class<? extends Linker>>(def.getActiveLinkers()));
  }

  public void testLinkerRedefinitionErrors() throws UnableToCompleteException {
    ModuleDef def = new ModuleDef("fake");

    def.defineLinker(TreeLogger.NULL, "pre", FakeLinkerPre.class);
    def.defineLinker(TreeLogger.NULL, "post", FakeLinkerPost.class);
    def.defineLinker(TreeLogger.NULL, "primary", FakeLinkerPrimary.class);
    def.addLinker("pre");
    def.addLinker("post");
    def.addLinker("primary");

    try {
      def.defineLinker(TreeLogger.NULL, "pre", FakeLinkerPrimary.class);
      fail();
    } catch (UnableToCompleteException e) {
      // OK
    }
    try {
      def.defineLinker(TreeLogger.NULL, "post", FakeLinkerPrimary.class);
      fail();
    } catch (UnableToCompleteException e) {
      // OK
    }
    try {
      def.defineLinker(TreeLogger.NULL, "primary", FakeLinkerPre.class);
      fail();
    } catch (UnableToCompleteException e) {
      // OK
    }
  }

  public void testTwoPrimaries() throws UnableToCompleteException {
    ModuleDef def = new ModuleDef("fake");

    def.defineLinker(TreeLogger.NULL, "pre", FakeLinkerPre.class);
    def.defineLinker(TreeLogger.NULL, "post", FakeLinkerPost.class);
    def.defineLinker(TreeLogger.NULL, "primary", FakeLinkerPrimary.class);
    def.defineLinker(TreeLogger.NULL, "primary2", FakeLinkerPrimary2.class);

    def.addLinker("pre");
    def.addLinker("post");
    def.addLinker("primary");
    def.addLinker("primary2");

    Class<?>[] expectedClasses = {
        FakeLinkerPre.class, FakeLinkerPost.class, FakeLinkerPrimary2.class};
    assertEquals(FakeLinkerPrimary2.class, def.getActivePrimaryLinker());
    // Test iteration order
    assertEquals(Arrays.asList(expectedClasses),
        new ArrayList<Class<? extends Linker>>(def.getActiveLinkers()));
  }

  public void testValidModuleName() {
    // Package names must contain valid Java identifiers.
    assertFalse(ModuleDef.isValidModuleName("com.foo.."));
    assertFalse(ModuleDef.isValidModuleName("com..Foo"));
    assertFalse(ModuleDef.isValidModuleName("com.7.Foo"));
    assertFalse(ModuleDef.isValidModuleName("com.7foo.Foo"));

    assertTrue(ModuleDef.isValidModuleName("com.foo.Foo"));
    assertTrue(ModuleDef.isValidModuleName("com.$foo.Foo"));
    assertTrue(ModuleDef.isValidModuleName("com._foo.Foo"));
    assertTrue(ModuleDef.isValidModuleName("com.foo7.Foo"));

    // For legacy reasons, allow the last part of the name is not
    // required to be a valid ident.  In the past, naming rules
    // were enforced for top level modules, but not nested modules.
    assertTrue(ModuleDef.isValidModuleName("com.foo.F-oo"));
    assertTrue(ModuleDef.isValidModuleName("com.foo.7Foo"));
    assertTrue(ModuleDef.isValidModuleName("com.foo.+Foo"));
  }
}
