/*
 * Copyright 2014 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.optimized;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.junit.DoNotRunWith;
import com.google.gwt.junit.Platform;

import java.util.Random;
import jsinterop.annotations.JsPackage;
import jsinterop.annotations.JsType;

/**
 * Tests cast checks are optimized out when cast checking is disabled.
 */
@DoNotRunWith(Platform.Devel)
public class CastOptimizationTest extends OptimizationTestBase {

  private static class TestObject { }

  @JsType(isNative = true)
  private interface JsTypeTestInterface { }

  private interface DualJsoTestInterface { }

  private static class JsoTestObject extends JavaScriptObject implements DualJsoTestInterface {
    protected JsoTestObject() { }
  }

  @JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Object")
  static class NativeObject {
  }

  @JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Function")
  static class NativeFunction {
  }

  @JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Array")
  static class NativeArray {
  }

  @JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Number")
  static class NativeNumber {
  }

  @JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "String")
  static class NativeString {
  }

  private static Object field;
  private static double doubleField = new Random().nextDouble();

  private static int randomNumber = new Random().nextInt(42);

  @Override
  protected void gwtSetUp() throws Exception {
    field = createField();
  }

  private static Object createField() {
    // Makes sure that field type is not upgradable even the compiler becomes really smart and also
    // no types are pruned otherwise casts can be statically evaluated.
    switch (randomNumber) {
      case 0:
        return new TestObject();
      case 1:
        return JavaScriptObject.createObject();
      case 2:
        return new DualJsoTestInterface() { };
      case 3:
        return "Some string";
      default:
        return null;
    }
  }

  public static TestObject castOp() {
    return ((TestObject) field);
  }

  public static JavaScriptObject castOpJso() {
    return ((JavaScriptObject) field);
  }

  public static DualJsoTestInterface castOpDualJso() {
    return ((DualJsoTestInterface) field);
  }

  public static JavaScriptObject castOpJsType() {
    return ((JavaScriptObject) field);
  }

  public static String castOpString() {
    return ((String) field);
  }

  public static NativeFunction castNativeFunction() {
    return ((NativeFunction) field);
  }

  public static NativeObject castNativeObject() {
    return ((NativeObject) field);
  }

  public static NativeArray castNativeArray() {
    return ((NativeArray) field);
  }

  public static NativeNumber castNativeNumber() {
    return ((NativeNumber) field);
  }

  public static void castInt() {
    int num = ((int) doubleField);
  }

  private static native String getGeneratedCastFunctionDefinition() /*-{
    return function() {
      @CastOptimizationTest::castOp()();
      @CastOptimizationTest::castOpJso()();
      @CastOptimizationTest::castOpDualJso()();
      @CastOptimizationTest::castOpJsType()();
      @CastOptimizationTest::castOpString()();
      @CastOptimizationTest::castNativeFunction()();
      @CastOptimizationTest::castNativeNumber()();
      @CastOptimizationTest::castNativeArray()();
      @CastOptimizationTest::castNativeObject()();
      @CastOptimizationTest::castInt()();
    }.toString();
  }-*/;

  public void testCastsAreRemoved() throws Exception {
    String functionDef = getGeneratedCastFunctionDefinition();
    assertFunctionMatches(functionDef, "");
  }

  private static native String getGeneratedNullnessPropagationFunctionDefinition() /*-{
    return function() {
      @CastOptimizationTest::castNonNullnessCheck()();
    }.toString();
  }-*/;

  public static void castNonNullnessCheck() {
    if ((String) (randomNumber == 1 ?  "uniqueCastString" :  new Object()) == null) {
      throw new RuntimeException();
    }
  }

  public void testCastsPropagatesNullness() throws Exception {
    String functionDef = getGeneratedNullnessPropagationFunctionDefinition();
    // Means that the function was inlined but the throw was optimized away.
    assertFunctionMatches(functionDef, "<obf>==1?'uniqueCastString':new<obf>()");
  }
}
