// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.proxetta;

import jodd.io.FastByteArrayOutputStream;
import jodd.proxetta.impl.InvokeProxetta;
import jodd.proxetta.fixtures.inv.*;
import jodd.util.ClassLoaderUtil;
import org.junit.Test;

import java.io.IOException;
import java.io.PrintStream;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import static org.junit.Assert.*;

public class InvReplTest {

	@Test
	public void testReplacement() throws IllegalAccessException, InstantiationException, NoSuchMethodException, IOException {

		InvokeProxetta proxetta = initProxetta();

		String className = One.class.getCanonicalName();
		byte[] klazz = proxetta.builder(One.class).create();
		//FileUtil.writeBytes("/Users/igor/OneClone.class", klazz);

		FastByteArrayOutputStream fbaos = new FastByteArrayOutputStream();
//		PrintStream out = System.out;
		System.setOut(new PrintStream(fbaos));

		One one = (One) ClassLoaderUtil.defineClass((new StringBuilder()).append(className).append(JoddProxetta.invokeProxyClassNameSuffix).toString(), klazz).newInstance();
		assertEquals("one ctor!one ctor!", fbaos.toString());    // clone ctor calls super ctor,
		fbaos.reset();

		one.example1();
		assertEquals("REPLACED VIRTUAL! jodd.proxetta.fixtures.inv.Two * one!173>overriden sub", fbaos.toString());
		fbaos.reset();

		one.example2();
		assertEquals("REPLACED STATIC! one * jodd/proxetta/fixtures/inv/Two * example2 * void example2() * jodd.proxetta.fixtures.inv.One * jodd.proxetta.fixtures.inv.One$$Clonetou!15013static: 4", fbaos.toString());
		fbaos.reset();

		one.example3();
		assertEquals("state = REPLACED ctor!", fbaos.toString());
		fbaos.reset();

		assertEquals("jodd.proxetta.fixtures.inv.One$$Clonetou", one.getClass().getName());
		assertTrue(one instanceof Serializable);

		Annotation[] anns = one.getClass().getAnnotations();
		assertEquals(3, anns.length);

		Method ms = one.getClass().getMethod("example1");
		anns = ms.getAnnotations();
		assertEquals(1, anns.length);

	}

	@Test
	public void testSuper() {
		InvokeProxetta proxetta = initProxetta();
		try {
			proxetta.builder(OneWithSuper.class).define();
			fail();
		} catch (ProxettaException ignore) {

		}
	}

	@Test
	public void testInterface() {
		InvokeProxetta proxetta = initProxetta();
		try {
			proxetta.builder(Inter.class).newInstance();
			fail();
		} catch (ProxettaException ignore) {
		}
	}

	@Test
	public void testCurrentTimeMillis() {
		TimeClass timeClass = (TimeClass) InvokeProxetta.withAspects(new InvokeAspect() {
			@Override
			public boolean apply(MethodInfo methodInfo) {
				return methodInfo.isTopLevelMethod();
			}

			@Override
			public InvokeReplacer pointcut(InvokeInfo invokeInfo) {
				if (
						invokeInfo.getClassName().equals("java.lang.System") &&
						invokeInfo.getMethodName().equals("currentTimeMillis")
					) {
					return InvokeReplacer.with(MySystem.class, "currentTimeMillis");
				}
				return null;
			}
		}).builder(TimeClass.class).newInstance();

		long time = timeClass.time();

		assertEquals(10823, time);
	}

	@Test
	public void testWimp() {
		Wimp wimp = (Wimp) InvokeProxetta.withAspects(new InvokeAspect() {
			@Override
			public boolean apply(MethodInfo methodInfo) {
				return methodInfo.isTopLevelMethod();
			}

			@Override
			public InvokeReplacer pointcut(InvokeInfo invokeInfo) {
				return InvokeReplacer.NONE;
			}
		}).builder(Wimp.class).newInstance();

		int i = wimp.foo();
		assertEquals(0, i);

		String txt = wimp.aaa(3, null, null);
		assertEquals("int3WelcomeToJodd", txt);

		txt = wimp.ccc(3, "XXX", 1, null);
		assertEquals(">4:String:4long:4XXX:ccc:Wimp", txt);
	}


	protected InvokeProxetta initProxetta() {
		return InvokeProxetta.withAspects(
				new InvokeAspect() {
					@Override
					public InvokeReplacer pointcut(InvokeInfo invokeInfo) {
						if (invokeInfo.getMethodName().equals("invvirtual")) {
							return InvokeReplacer.with(Replacer.class, "rInvVirtual")
									.passOwnerName(false);
						} else {
							return null;
						}
					}
				}
				, new InvokeAspect() {
					@Override
					public InvokeReplacer pointcut(InvokeInfo invokeInfo) {
						if (invokeInfo.getMethodName().equals("invstatic")) {
							return InvokeReplacer.with(Replacer.class, "rInvStatic")
									.passOwnerName(true)
									.passMethodName(true)
									.passMethodSignature(true)
									.passThis(true)
									.passTargetClass(true);
						} else {
							return null;
						}
					}
				}
				, new InvokeAspect() {
					@Override
					public InvokeReplacer pointcut(InvokeInfo invokeInfo) {
						if (invokeInfo.getMethodName().equals("invinterface")) {
							return InvokeReplacer.with(Replacer.class, "rInvInterface");
						} else {
							return null;
						}
					}
				}
				, new InvokeAspect() {
					@Override
					public InvokeReplacer pointcut(InvokeInfo invokeInfo) {
						if (invokeInfo.getMethodName().equals("<init>") && invokeInfo.getClassName().equals(Two.class.getCanonicalName())) {
							return InvokeReplacer.with(Replacer.class, "rInvNew");
						} else {
							return null;
						}
					}
				}
		);
	}
}
