/*
 * Copyright 2010-2012 VMware and contributors
 *
 * 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 org.springsource.loaded.test;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.springsource.loaded.test.ReloadingJVM.JVMOutput;

/**
 * These tests use a harness that forks a JVM with the agent attached, closely simulating a real environment. The
 * forked process is running a special class that can be sent commands.
 * 
 * @author Andy Clement
 */
public class SpringLoadedTestsInSeparateJVM extends SpringLoadedTests {

	private static ReloadingJVM jvm;

	@BeforeClass
	public static void startJVM() throws Exception {
//		jvm = ReloadingJVM.launch("verbose;explain");
		jvm = ReloadingJVM.launch("");
	}

	@AfterClass
	public static void stopJVM() {
		jvm.shutdown();
	}
	
	@After
	public void teardown() throws Exception {
		super.teardown();
		jvm.clearTestdataDirectory();
	}

	@Test
	public void testEcho() throws Exception {
		JVMOutput result = jvm.echo("hello");
		assertStdout("hello", result);
	}

	@Test
	public void testRunClass() throws Exception {
		JVMOutput output = jvm.run("jvmtwo.Runner");
		assertStdout("jvmtwo.Runner.run() running", output);
	}
	
	@Test
	public void githubIssue34() throws Exception {
		jvm.copyToTestdataDirectory("issue34.Interface1"); 
		jvm.copyToTestdataDirectory("issue34.Interface2"); 
		jvm.copyToTestdataDirectory("issue34.Implementation1"); 
		jvm.copyToTestdataDirectory("issue34.Implementation2"); 
		jvm.copyToTestdataDirectory("issue34.Implementation3"); 
		JVMOutput output = jvm.run("issue34.Implementation3");
		assertStdout("Hello World!\n", output);		
	}
	
	@Test
	public void serialization() throws Exception {
		jvm.copyToTestdataDirectory("remote.Serialize");
		jvm.copyToTestdataDirectory("remote.Person");
		JVMOutput output = null;

		// When the Serialize class is run directly, we see: byteinfo:len=98:crc=c1047cf6
		// When run via the non separate JVM test, we see: byteinfo:len=98:crc=7e07276a
		// When run here, we see: byteinfo:len=98:crc=c1047cf6
	
		output = jvm.run("remote.Serialize");
		assertStdoutContains("check ok\n", output);

		// Load new Person
		jvm.updateClass("remote.Person", retrieveRename("remote.Person","remote.Person2"));
		pause(2);
		
		output = jvm.run("remote.Serialize");
		assertStdoutContains("check ok\n", output);

		// Load original Person
		jvm.updateClass("remote.Person", loadBytesForClass("remote.Person"));
		pause(2);

		output = jvm.run("remote.Serialize");
		assertStdoutContains("check ok\n", output);
	}
	
	// Deserializing something serialized earlier
	@Test
	public void serialization2() throws Exception {
		jvm.copyToTestdataDirectory("remote.Serialize");
		jvm.copyToTestdataDirectory("remote.Person");
		JVMOutput output = null;
		
		output = jvm.run("remote.Serialize");
		assertStdoutContains("check ok\n", output);

		jvm.newInstance("a", "remote.Serialize");
		JVMOutput jo = jvm.call("a","checkPredeserializedData");
		assertStdoutContains("Pre-serialized form checked ok\n", jo);
	}
	
	// Deserialize a groovy closure
	@Test
	public void serializationGroovy() throws Exception {
//		debug();
		jvm.copyToTestdataDirectory("remote.SerializeG");
		jvm.copyToTestdataDirectory("remote.FakeClosure");	
		
		// Notes on serialization
		// When SerializeG run standalone, reports byteinfo:len=283:crc=245529d9
		// When run in agented JVM, reports byteinfo:len=283:crc=245529d9
		
		jvm.newInstance("a", "remote.SerializeG");
		JVMOutput jo = jvm.call("a","checkPredeserializedData");
		assertStdoutContains("Pre-serialized groovy form checked ok\n", jo);
	}
	
	@Test
	public void githubIssue34_2() throws Exception {
		jvm.copyToTestdataDirectory("issue34.InnerEnum$sorters"); 
		jvm.copyToTestdataDirectory("issue34.InnerEnum$MyComparator"); 
		jvm.copyToTestdataDirectory("issue34.InnerEnum$sorters$1"); 
		JVMOutput output = jvm.run("issue34.InnerEnum");
		assertStdout("Hello World!\n", output);		
	}

	@Test
	public void testCreatingAndInvokingMethodsOnInstance() throws Exception {
		assertStderrContains("creating new instance 'a' of type 'jvmtwo.Runner'", jvm.newInstance("a", "jvmtwo.Runner"));
		assertStdout("jvmtwo.Runner.run1() running", jvm.call("a", "run1"));
	}

	/* careful with this test - when the forked JVM takes a while the output can interfere with later tests, needs fixing up
	 *
	 */
	@Ignore
	@Test
	public void reloadedPerformance() throws Exception {
//		debug();
		jvm.newInstance("a","remote.Perf1");
		JVMOutput jo = jvm.call("a","time"); // 75ms
		jo = jvm.call("a","time"); // 75ms
		pause(5);
		jvm.updateClass("remote.Perf1",retrieveRename("remote.Perf1","remote.Perf2"));
		pause(2);
		// In Perf2 the static method is gone, why does it give us a NSME?
		jo = jvm.call("a","time"); // 150ms
		pause(5);
	}
	
	@SuppressWarnings("unused")
	private final static void debug() {
		jvm.shutdown();
		jvm = ReloadingJVM.launch("",true);
	}
	
	@SuppressWarnings("unused")
	private final static void debug(String options) {
		jvm = ReloadingJVM.launch(options,true);
	}
	
	@Test
	public void testReloadingInOtherVM() throws Exception {
		jvm.newInstance("b", "remote.One");
		assertStdout("first", jvm.call("b", "run"));
		pause(1);
		jvm.updateClass("remote.One",retrieveRename("remote.One","remote.One2"));
		pause(2);
		assertStdoutContains("second", jvm.call("b", "run"));
	}
	// TODO tidyup test data area after each test?
	// TODO flush/replace classloader in forked VM to clear it out after each test?
	
	// GRAILS-10411
	/**
	 * GRAILS-10411. The supertype is not reloadable, the subtype is reloadable and makes super calls
	 * to overridden methods.
	 */
	@Test
	public void testClassMakingSuperCalls() throws Exception {
		String supertype="grails.Top";
		String subtype="foo.Controller";
		jvm.copyToTestdataDirectory(supertype); 
		jvm.copyToTestdataDirectory(subtype);
		JVMOutput jo = jvm.newInstance("bb",subtype);
		System.out.println(jo);
		pause(1);
		assertStdout("Top.foo() running\nController.foo() running\n", jvm.call("bb", "foo"));
		pause(1);
		jvm.updateClass(subtype,retrieveRename(subtype,subtype+"2"));
		waitForReloadToOccur();
		jo = jvm.call("bb", "foo");
		assertStdoutContains("Top.foo() running\nController.foo() running again!\n", jo);
	}
	
	/**
	 * GRAILS-10411. The supertype is not reloadable, the subtype is reloadable and makes super calls
	 * to overridden methods. This time the supertype method is protected.
	 */
	@Test
	public void testClassMakingSuperCalls2() throws Exception {
		String supertype="grails.TopB";
		String subtype="foo.ControllerB";
		jvm.copyToTestdataDirectory(supertype); 
		jvm.copyToTestdataDirectory(subtype);
		jvm.newInstance("a",subtype);
		assertStdout("TopB.foo() running\nControllerB.foo() running\n", jvm.call("a", "foo"));
		jvm.updateClass(subtype,retrieveRename(subtype,subtype+"2"));
		waitForReloadToOccur();
		assertStdoutContains("TopB.foo() running\nControllerB.foo() running again!\n", jvm.call("a", "foo"));
	}

	// ---

	private void waitForReloadToOccur() {
		try { Thread.sleep(2000); } catch (Exception e) {}
	}

	

}
