// 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.introspector;

import jodd.util.ClassUtil;
import org.junit.Test;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;

public class IntrospectorGenericsTest {

	public static class MethodParameterType<A> {
		List<A> f;
		List f2;
		Map<String, A>f3;
		List<Long> f4;
		<T extends List<T>> void m(A a, String p1, T p2, List<?> p3, List<T> p4) { }
		<T extends List<T>> List<T> m2(A a, String p1, T p2, List<?> p3, List<T> p4) { return null; }
		<T extends List<T>> List<A> m3(A a, String p1, T p2, List<?> p3, List<T> p4) { return null; }
		List<Byte> m4(List<Long> list) {return null;}
		List<A> m5(List<A> list) {return null;}
	}

	public static class Foo extends MethodParameterType<Integer> {}

	@Test
	public void testFields() throws NoSuchFieldException {
		ClassDescriptor cd = ClassIntrospector.lookup(MethodParameterType.class);

		assertEquals(MethodParameterType.class, cd.getType());
		assertEquals(4, cd.getAllFieldDescriptors().length);

		FieldDescriptor[] fs = cd.getAllFieldDescriptors();
		int p = 0;
		for (FieldDescriptor f : fs) {
			if (f.isPublic()) {
				p++;
			}
		}
		assertEquals(0, p);

		FieldDescriptor fd = cd.getFieldDescriptor("f", true);
		FieldDescriptor fd2 = cd.getFieldDescriptor("f2", true);
		FieldDescriptor fd3 = cd.getFieldDescriptor("f3", true);
		FieldDescriptor fd4 = cd.getFieldDescriptor("f4", true);

		assertEquals(List.class, fd.getRawType());
		assertEquals(Object.class, fd.getRawComponentType());

		assertEquals(List.class, fd2.getRawType());
		assertNull(fd2.getRawComponentType());

		assertEquals(Map.class, fd3.getRawType());
		assertEquals(Object.class, fd3.getRawComponentType());

		assertEquals(List.class, fd4.getRawType());
		assertEquals(Long.class, fd4.getRawComponentType());

		// impl
		cd = ClassIntrospector.lookup(Foo.class);

		fd = cd.getFieldDescriptor("f", true);
		fd2 = cd.getFieldDescriptor("f2", true);
		fd3 = cd.getFieldDescriptor("f3", true);

		assertEquals(List.class, fd.getRawType());
		assertEquals(Integer.class, fd.getRawComponentType());

		assertEquals(List.class, fd2.getRawType());
		assertNull(fd2.getRawComponentType());

		assertEquals(Map.class, fd3.getRawType());
		assertEquals(Integer.class, fd3.getRawComponentType());
		assertEquals(String.class, ClassUtil.getComponentTypes(fd3.getField().getGenericType(), cd.getType())[0]);
	}

	@Test
	public void testMethods() throws NoSuchMethodException {
		ClassDescriptor cd = ClassIntrospector.lookup(MethodParameterType.class);

		assertEquals(MethodParameterType.class, cd.getType());
		assertEquals(5, cd.getAllMethodDescriptors().length);

		MethodDescriptor[] mds = cd.getAllMethodDescriptors();
		int mc = 0;
		for (MethodDescriptor md : mds) {
			if (md.isPublic()) mc++;
		}
		assertEquals(0, mc);


		Class[] params = new Class[] {Object.class, String.class, List.class, List.class, List.class};

		Method m = MethodParameterType.class.getDeclaredMethod("m", params);
		assertNotNull(m);

		Method m2 = cd.getMethodDescriptor("m", params, true).getMethod();
		assertNotNull(m2);
		assertEquals(m, m2);

		MethodDescriptor md1 = cd.getMethodDescriptor("m", params, true);
		assertNotNull(md1);
		assertEquals(m, md1.getMethod());
		assertArrayEquals(params, md1.getRawParameterTypes());
		assertEquals(void.class, md1.getRawReturnType());
		assertNull(md1.getRawReturnComponentType());

		MethodDescriptor md2 = cd.getMethodDescriptor("m2", params, true);
		assertNotNull(md2);
		assertArrayEquals(params, md2.getRawParameterTypes());
		assertEquals(List.class, md2.getRawReturnType());
		assertEquals(List.class, md2.getRawReturnComponentType());

		MethodDescriptor md3 = cd.getMethodDescriptor("m3", params, true);
		assertNotNull(md3);
		assertArrayEquals(params, md3.getRawParameterTypes());
		assertEquals(List.class, md3.getRawReturnType());
		assertEquals(Object.class, md3.getRawReturnComponentType());

		MethodDescriptor md4 = cd.getMethodDescriptor("m4", new Class[] {List.class}, true);
		assertNotNull(md4);
		assertArrayEquals(new Class[] {List.class}, md4.getRawParameterTypes());
		assertEquals(List.class, md4.getRawReturnType());
		assertEquals(Byte.class, md4.getRawReturnComponentType());
		assertEquals(List.class, md4.getSetterRawType());
		assertEquals(Long.class, md4.getSetterRawComponentType());

		MethodDescriptor md5 = cd.getMethodDescriptor("m5", new Class[] {List.class}, true);
		assertNotNull(md5);
		assertArrayEquals(new Class[] {List.class}, md5.getRawParameterTypes());
		assertEquals(List.class, md5.getRawReturnType());
		assertEquals(Object.class, md5.getRawReturnComponentType());
		assertEquals(List.class, md5.getSetterRawType());
		assertEquals(Object.class, md5.getSetterRawComponentType());


		// impl

		Class[] params2 = new Class[] {Integer.class, String.class, List.class, List.class, List.class};

		ClassDescriptor cd1 = ClassIntrospector.lookup(Foo.class);

		assertEquals(0, Foo.class.getDeclaredMethods().length);

		MethodDescriptor[] allm = cd1.getAllMethodDescriptors();

		assertEquals(5, allm.length);

		md3 = cd1.getMethodDescriptor("m", params, true);
		assertNotNull(md3);

		assertArrayEquals(params2, md3.getRawParameterTypes());

		md3 = cd1.getMethodDescriptor("m3", params, true);
		assertNotNull(md3);
		assertArrayEquals(params2, md3.getRawParameterTypes());
		assertEquals(List.class, md3.getRawReturnType());
		assertEquals(Integer.class, md3.getRawReturnComponentType());

		md5 = cd1.getMethodDescriptor("m5", new Class[] {List.class}, true);
		assertNotNull(md5);
		assertArrayEquals(new Class[] {List.class}, md5.getRawParameterTypes());
		assertEquals(List.class, md5.getRawReturnType());
		assertEquals(Integer.class, md5.getRawReturnComponentType());
		assertEquals(List.class, md5.getSetterRawType());
		assertEquals(Integer.class, md5.getSetterRawComponentType());
	}
}