/*
 * Copyright 2013 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.jjs.impl.codesplitter;

import com.google.gwt.dev.jjs.Correlation;
import com.google.gwt.dev.jjs.Correlation.Axis;
import com.google.gwt.dev.jjs.CorrelationFactory;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.SourceOrigin;
import com.google.gwt.dev.jjs.ast.AccessModifier;
import com.google.gwt.dev.jjs.ast.JClassType;
import com.google.gwt.dev.jjs.ast.JConstructor;
import com.google.gwt.dev.jjs.ast.JDeclaredType;
import com.google.gwt.dev.jjs.ast.JField;
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.impl.JJSTestBase;
import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap;
import com.google.gwt.dev.js.ast.JsExprStmt;
import com.google.gwt.dev.js.ast.JsInvocation;
import com.google.gwt.dev.js.ast.JsName;
import com.google.gwt.dev.js.ast.JsNameRef;
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.js.ast.JsStatement;

import java.util.List;

/**
 * Unit tests for {@link com.google.gwt.dev.jjs.impl.codesplitter.FragmentExtractor}.
 */
public class FragmentExtractorTest extends JJSTestBase {

  private static final JsName DEFINE_CLASS_FUNCTION_NAME = new JsName(null, "defineClass", "defineClass");

  private static class MockSourceInfo implements SourceInfo {

    @Override
    public void addCorrelation(Correlation c) { }

    @Override
    public Correlation getCorrelation(Axis axis) {
      return null;
    }

    @Override
    public Correlation[] getCorrelations() {
      return null;
    }

    @Override
    public CorrelationFactory getCorrelator() {
      return null;
    }

    @Override
    public int getEndPos() {
      return 0;
    }

    @Override
    public String getFileName() {
      return null;
    }

    @Override
    public SourceOrigin getOrigin() {
      return null;
    }

    @Override
    public int getStartLine() {
      return 0;
    }

    @Override
    public int getStartPos() {
      return 0;
    }

    @Override
    public SourceInfo makeChild() {
      return null;
    }

    @Override
    public SourceInfo makeChild(SourceOrigin origin) {
      return null;
    }
  }

  private static class MockJavaToJavaScriptMap implements JavaToJavaScriptMap {

    @Override
    public JsName nameForField(JField field) {
      return null;
    }

    @Override
    public JsName nameForMethod(JMethod method) {
      return null;
    }

    @Override
    public JsName nameForType(JDeclaredType type) {
      return null;
    }

    @Override
    public JField nameToField(JsName name) {
      return null;
    }

    @Override
    public JMethod nameToMethod(JsName name) {
      return null;
    }

    @Override
    public JClassType nameToType(JsName name) {
      return null;
    }

    @Override
    public JClassType typeForStatement(JsStatement stat) {
      return null;
    }

    @Override
    public JMethod methodForStatement(JsStatement stat) {
      return null;
    }
  }

  private static class MockLivenessPredicate implements LivenessPredicate {

    @Override
    public boolean isLive(JDeclaredType type) {
      return false;
    }

    @Override
    public boolean isLive(JField field) {
      return false;
    }

    @Override
    public boolean isLive(JMethod method) {
      return false;
    }

    @Override
    public boolean miscellaneousStatementsAreLive() {
      return false;
    }
  }

  /**
   * Invokes FragmentExtractor with a fragment description claiming that Bar was not made live by
   * the current fragment, but that it has a constructor which *was* made live. Verifies that the
   * defineClass invocation from the global JS block *is* included in the extracted statement output.
   */
  public void testDefineClass_DeadTypeLiveConstructor() {
    FragmentExtractor fragmentExtractor;
    LivenessPredicate constructorLivePredicate;

    // Environment setup.
    {
      SourceInfo nullSourceInfo = new MockSourceInfo();
      final JClassType barType = new JClassType(nullSourceInfo, "Bar", false, false);
      final JsName barConstructorName = new JsName(null, "Bar", "Bar");
      final JConstructor barConstructor =
          new JConstructor(nullSourceInfo, barType, AccessModifier.PUBLIC);

      final JsExprStmt defineClassStatement = createDefineClassStatement(barConstructorName);

      JsProgram jsProgram = new JsProgram();
      // Defines the entirety of the JS program being split, to be the one defineClass statement.
      jsProgram.getGlobalBlock().getStatements().add(defineClassStatement);

      JavaToJavaScriptMap map = new MockJavaToJavaScriptMap() {
        @Override
        public JMethod nameToMethod(JsName name) {
          if (name == barConstructorName) {
            // Finds the Bar constructor by name.
            return barConstructor;
          }
          return null;
        }

        @Override
        public JClassType typeForStatement(JsStatement statement) {
          if (statement == defineClassStatement) {
            // Indicates that Bar is the type associated with the defineClass statement.
            return barType;
          }
          return null;
        }
      };
      fragmentExtractor = new FragmentExtractor(jsProgram, map, null, DEFINE_CLASS_FUNCTION_NAME);
      constructorLivePredicate = new MockLivenessPredicate() {
          @Override
        public boolean isLive(JDeclaredType type) {
          // Claims that Bar is not made live by the current fragment.
          return false;
        }

          @Override
        public boolean isLive(JMethod method) {
          // Claims that the bar Constructor *is* made live by the current fragment.
          return method == barConstructor;
        }
      };
    }

    List<JsStatement> extractedStatements =
        fragmentExtractor.extractStatements(constructorLivePredicate, new NothingAlivePredicate());

    // Asserts that the single defineClass statement was included in the extraction output.
    assertEquals(1, extractedStatements.size());
    JsStatement defineClassStatement = extractedStatements.get(0);
    assertTrue(defineClassStatement.toString().contains("defineClass"));
  }

  private JsExprStmt createDefineClassStatement(final JsName barConstructorName) {
    SourceInfo nullSourceInfo = new MockSourceInfo();
    JsInvocation defineClassInvocation =
        new JsInvocation(nullSourceInfo, new JsNameRef(nullSourceInfo, DEFINE_CLASS_FUNCTION_NAME),
        new JsNameRef(nullSourceInfo, barConstructorName));
    return defineClassInvocation.makeStmt();
  }
}
