/*
 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */


package org.graalvm.compiler.hotspot.test;

import static org.junit.Assert.assertNotEquals;

import org.graalvm.compiler.api.directives.GraalDirectives;
import org.graalvm.compiler.graph.Node;
import org.graalvm.compiler.hotspot.nodes.KlassBeingInitializedCheckNode;
import org.graalvm.compiler.nodes.DeoptimizeNode;
import org.graalvm.compiler.nodes.StructuredGraph;
import org.graalvm.compiler.nodes.java.LoadFieldNode;
import org.graalvm.compiler.nodes.java.NewInstanceNode;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;

import jdk.vm.ci.code.InstalledCode;
import jdk.vm.ci.code.InvalidInstalledCodeException;
import jdk.vm.ci.meta.DeoptimizationReason;
import jdk.vm.ci.meta.ResolvedJavaMethod;

public class HotSpotClassInitializationTest extends HotSpotGraalCompilerTest {

    static HotSpotClassInitializationTest instance;

    static class InvokeStatic {
        static {
            instance.test(InvokeStatic.class, "m");
        }

        static boolean m() {
            double value = 123;
            doOtherWork(value);
            return GraalDirectives.inCompiledCode();
        }

        static double doOtherWork(double value) {
            return value;
        }

        static int field;
    }

    static class GetStatic {
        static class Inner {
            static int N = 5000;

            static {
                instance.test(GetStatic.class, "m", LoadFieldNode.class);
            }
        }

        static {
            @SuppressWarnings("unused")
            int n = Inner.N;
        }

        @SuppressWarnings("unused")
        static boolean m() {
            double value = 123 * Inner.N;
            return GraalDirectives.inCompiledCode();
        }

        static double field;
    }

    static class NewInstance {
        static {
            instance.test(NewInstance.class, "m", NewInstanceNode.class);
        }

        @SuppressWarnings("unused")
        static boolean m() {
            Object o = new NewInstance();
            return GraalDirectives.inCompiledCode();
        }

        static double field;
    }

    @Before
    public void checkAssumptions() {
        // cannot be BeforeClass because we need a runtime and BeforeClass must be static
        Assume.assumeTrue("init_thread field must be visible", runtime().getVMConfig().instanceKlassInitThreadOffset != -1);
    }

    @SafeVarargs
    final void test(Class<?> testClass, String methodName, Class<? extends Node>... nodeTypes) {
        ResolvedJavaMethod method = getResolvedJavaMethod(testClass, methodName);
        StructuredGraph graph = parseProfiled(method, StructuredGraph.AllowAssumptions.NO);
        for (DeoptimizeNode d : graph.getNodes().filter(DeoptimizeNode.class)) {
            assertNotEquals("No unresolved deopts expected", d.getReason(), DeoptimizationReason.Unresolved);
        }
        assertTrue("A dynamic check should have been emitted", graph.getNodes().filter(KlassBeingInitializedCheckNode.class).count() == 1);

        // Ensure that all expected nodes exist.
        for (Class<? extends Node> nodeType : nodeTypes) {
            assertTrue("expected node of type " + nodeType, graph.getNodes().filter(nodeType).count() == 1);
        }

        // Ensure that execution remains in the compiled code to the return point
        InstalledCode code = instance.getCode(method, graph);
        try {
            boolean result = (boolean) code.executeVarargs();
            Assert.assertEquals("should have completed in compiled code", result, true);
        } catch (InvalidInstalledCodeException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testInvokeStatic() {
        GraalDirectives.inCompiledCode();
        instance = this;
        InvokeStatic.field = 0;
        instance = null;
    }

    @Test
    public void testGetStatic() {
        GraalDirectives.inCompiledCode();
        instance = this;
        GetStatic.field = 0;
        instance = null;
    }

    @Test
    public void testNewInstance() {
        GraalDirectives.inCompiledCode();
        instance = this;
        NewInstance.field = 0;
        instance = null;
    }

}
