/*
 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */


package org.graalvm.compiler.replacements.test;

import java.util.HashMap;

import org.junit.Assert;
import org.junit.Test;

import org.graalvm.compiler.core.test.GraalCompilerTest;

/**
 * Tests the implementation of {@code NEW}.
 */
public class NewInstanceTest extends GraalCompilerTest {

    @Override
    protected void assertDeepEquals(Object expected, Object actual) {
        Assert.assertTrue(expected != null);
        Assert.assertTrue(actual != null);
        super.assertDeepEquals(expected.getClass(), actual.getClass());

        if (expected instanceof Object[]) {
            Assert.assertTrue(actual instanceof Object[]);
            Object[] eArr = (Object[]) expected;
            Object[] aArr = (Object[]) actual;
            Assert.assertTrue(eArr.length == aArr.length);
            for (int i = 0; i < eArr.length; i++) {
                assertDeepEquals(eArr[i], aArr[i]);
            }
        } else if (expected.getClass() != Object.class) {
            try {
                expected.getClass().getDeclaredMethod("equals", Object.class);
                super.assertDeepEquals(expected, actual);
            } catch (Exception e) {
            }
        }
    }

    @Test
    public void test1() {
        test("newObject");
    }

    @Test
    public void test2() {
        test("newObjectTwice");
    }

    public static Object newObject() {
        return new Object();
    }

    @Test
    public void test3() {
        test("newObjectLoop", 100);
    }

    @Test
    public void test4() {
        test("newBigObject");
    }

    @Test
    public void test5() {
        test("newSomeObject");
    }

    @Test
    public void test6() {
        test("newEmptyString");
    }

    @Test
    public void test7() {
        test("newString", "value");
    }

    @Test
    public void test8() {
        test("newHashMap", 31);
    }

    @Test
    public void test9() {
        test("newRegression", true);
    }

    public static Object[] newObjectTwice() {
        Object[] res = {new Object(), new Object()};
        return res;
    }

    public static Object[] newObjectLoop(int n) {
        Object[] res = new Object[n];
        for (int i = 0; i < n; i++) {
            res[i] = new Object();
        }
        return res;
    }

    public static BigObject newBigObject() {
        return new BigObject();
    }

    public static SomeObject newSomeObject() {
        return new SomeObject();
    }

    public static String newEmptyString() {
        return new String();
    }

    public static String newString(String value) {
        return new String(value);
    }

    public static HashMap<?, ?> newHashMap(int initialCapacity) {
        return new HashMap<>(initialCapacity);
    }

    static class SomeObject {

        String name = "o1";
        HashMap<String, Object> map = new HashMap<>();

        SomeObject() {
            map.put(name, this.getClass());
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof SomeObject) {
                SomeObject so = (SomeObject) obj;
                return so.name.equals(name) && so.map.equals(map);
            }
            return false;
        }

        @Override
        public int hashCode() {
            return name.hashCode();
        }
    }

    static class BigObject {

        Object f01;
        Object f02;
        Object f03;
        Object f04;
        Object f05;
        Object f06;
        Object f07;
        Object f08;
        Object f09;
        Object f10;
        Object f12;
        Object f13;
        Object f14;
        Object f15;
        Object f16;
        Object f17;
        Object f18;
        Object f19;
        Object f20;
        Object f21;
        Object f22;
        Object f23;
        Object f24;
        Object f25;
        Object f26;
        Object f27;
        Object f28;
        Object f29;
        Object f30;
        Object f31;
        Object f32;
        Object f33;
        Object f34;
        Object f35;
        Object f36;
        Object f37;
        Object f38;
        Object f39;
        Object f40;
        Object f41;
        Object f42;
        Object f43;
        Object f44;
        Object f45;
    }

    /**
     * Tests that an earlier bug does not occur. The issue was that the loading of the TLAB 'top'
     * and 'end' values was being GVN'ed from each branch of the 'if' statement. This meant that the
     * allocated B object in the true branch overwrote the allocated array. The cause is that
     * RegisterNode was a floating node and the reads from it were UnsafeLoads which are also
     * floating. The fix was to make RegisterNode a fixed node (which it should have been in the
     * first place).
     */
    public static Object newRegression(boolean condition) {
        Object result;
        if (condition) {
            Object[] arr = {0, 1, 2, 3, 4, 5};
            result = new B();
            for (int i = 0; i < arr.length; ++i) {
                // If the bug exists, the values of arr will now be deadbeef values
                // and the virtual dispatch will cause a segfault. This can result in
                // either a VM crash or a spurious NullPointerException.
                if (arr[i].equals(Integer.valueOf(i))) {
                    return false;
                }
            }
        } else {
            result = new B();
        }
        return result;
    }

    static class B {

        long f1 = 0xdeadbeefdeadbe01L;
        long f2 = 0xdeadbeefdeadbe02L;
        long f3 = 0xdeadbeefdeadbe03L;
        long f4 = 0xdeadbeefdeadbe04L;
        long f5 = 0xdeadbeefdeadbe05L;
    }
}
