/*
 * Copyright IBM Corp. and others 2019
 *
 * This program and the accompanying materials are made available under
 * the terms of the Eclipse Public License 2.0 which accompanies this
 * distribution and is available at https://www.eclipse.org/legal/epl-2.0/
 * or the Apache License, Version 2.0 which accompanies this distribution and
 * is available at https://www.apache.org/licenses/LICENSE-2.0.
 *
 * This Source Code may also be made available under the following
 * Secondary Licenses when the conditions for such availability set
 * forth in the Eclipse Public License, v. 2.0 are satisfied: GNU
 * General Public License, version 2 with the GNU Classpath
 * Exception [1] and GNU General Public License, version 2 with the
 * OpenJDK Assembly Exception [2].
 *
 * [1] https://www.gnu.org/software/classpath/license.html
 * [2] https://openjdk.org/legal/assembly-exception.html
 *
 * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 OR GPL-2.0-only WITH OpenJDK-assembly-exception-1.0
 */
package org.openj9.test.java_lang_invoke;

import org.openj9.test.java_lang_invoke.helpers.Jep334MHHelperImpl;

import org.testng.Assert;
import org.testng.annotations.Test;
import org.testng.log4testng.Logger;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.invoke.VarHandle.VarHandleDesc;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * This test Java.lang.invoke.VarHandle API added in Java 12 and later version.
 *
 * new methods: 
 * - describeConstable 
 * - equals 
 * - hashCode 
 * - toString
 */
public class Test_VarHandle {
	public static Logger logger = Logger.getLogger(Test_VarHandle.class);

	/* test VarHandles */
	private static VarHandle instanceTest;
	private static VarHandle staticTest;
	private static VarHandle arrayTest;
	private static VarHandle arrayTest2;
	/* hash tests */
	private static VarHandle instanceTestCopy;
	private static VarHandle staticTestCopy;
	private static VarHandle arrayTestCopy;
	private static VarHandle arrayTest2Copy;
	static {
		try {
			instanceTest = Jep334MHHelperImpl.getInstanceTest();
			staticTest = Jep334MHHelperImpl.getStaticTest();
			arrayTest = Jep334MHHelperImpl.getArrayTest();
			arrayTest2 = Jep334MHHelperImpl.getArrayTest2();
			/* used for hash tests */
			instanceTestCopy = Jep334MHHelperImpl.getInstanceTest();
			staticTestCopy = Jep334MHHelperImpl.getStaticTest();
			arrayTestCopy = Jep334MHHelperImpl.getArrayTest();
			arrayTest2Copy = Jep334MHHelperImpl.getArrayTest2();
		} catch (Throwable e) {
			Assert.fail("Test variables could not be initialized for Test_VarHandle.");
		}
	};

	/*
	 * Test Java 12 API VarHandle.describeConstable() for instance field type
	 */
	@Test(groups = { "level.sanity" })
	public void testVarHandleDescribeConstableInstanceField() throws Throwable {
		describeConstableTestGeneral("testVarHandleDescribeConstableInstanceField", instanceTest);
	}

	/*
	 * Test Java 12 API VarHandle.describeConstable() for static field type
	 */
	@Test(groups = { "level.sanity" })
	public void testVarHandleDescribeConstableStaticField() throws Throwable {
		describeConstableTestGeneral("testVarHandleDescribeConstableStaticField", staticTest);
	}

	/*
	 * Test Java 12 API VarHandle.describeConstable() for array type
	 */
	@Test(groups = { "level.sanity" })
	public void testVarHandleDescribeConstableArray() throws Throwable {
		describeConstableTestGeneral("testVarHandleDescribeConstableArray (1)", arrayTest);
		describeConstableTestGeneral("testVarHandleDescribeConstableArray (2)", arrayTest2);
	}

	private void describeConstableTestGeneral(String testName, VarHandle handle) throws Throwable {
		VarHandleDesc desc = handle.describeConstable().orElseThrow();

		/*
		 * verify that descriptor can be resolved. Otherwise will throw
		 * ReflectiveOperationException
		 */
		VarHandle resolvedHandle = (VarHandle) desc.resolveConstantDesc(MethodHandles.lookup());

		logger.debug(testName + ": Descriptor of original class varType is: " + handle.varType().descriptorString());
		logger.debug(testName + ": Descriptor of VarHandleDesc varType is: " + resolvedHandle.varType().descriptorString());

		Assert.assertTrue(handle.varType().equals(resolvedHandle.varType()));
		Assert.assertTrue(handle.coordinateTypes().equals(resolvedHandle.coordinateTypes()));
	}

	/*
	 * Test Java 12 API VarHandle.toString()
	 */
	@Test(groups = { "level.sanity" })
	public void testVarHandleToString() {
		testHandleString("testVarHandleToString (instance)", instanceTest);
		testHandleString("testVarHandleToString (static)", staticTest);
		testHandleString("testVarHandleToString (array)", arrayTest);
		testHandleString("testVarHandleToString (array 2)", arrayTest2);
	}

	private void testHandleString(String testName, VarHandle handle) {
		String handleString = handle.toString();

		/* the general format is: VarHandle[varType=, coord=[]] */
		Assert.assertTrue(handleString.startsWith("VarHandle["));
		Assert.assertTrue(handleString.contains("varType=" + handle.varType().getName()));

		List<Class<?>> coordList = handle.coordinateTypes();
		String coordString = coordList.stream().map(coord -> String.valueOf(coord)).collect(Collectors.joining(", "));

		logger.debug(testName + ": var handle string is " + handleString + " string should include coords of: " + coordString);
		Assert.assertTrue(handleString.contains("coord=[" + coordString + "]"));
	}
}
