package jadx.tests.functional;

import jadx.core.clsp.ClspGraph;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.utils.exceptions.DecodeException;

import java.io.IOException;

import org.junit.Before;
import org.junit.Test;

import static jadx.core.dex.instructions.args.ArgType.BOOLEAN;
import static jadx.core.dex.instructions.args.ArgType.BYTE;
import static jadx.core.dex.instructions.args.ArgType.CHAR;
import static jadx.core.dex.instructions.args.ArgType.INT;
import static jadx.core.dex.instructions.args.ArgType.LONG;
import static jadx.core.dex.instructions.args.ArgType.NARROW;
import static jadx.core.dex.instructions.args.ArgType.OBJECT;
import static jadx.core.dex.instructions.args.ArgType.STRING;
import static jadx.core.dex.instructions.args.ArgType.UNKNOWN;
import static jadx.core.dex.instructions.args.ArgType.UNKNOWN_OBJECT;
import static jadx.core.dex.instructions.args.ArgType.array;
import static jadx.core.dex.instructions.args.ArgType.genericType;
import static jadx.core.dex.instructions.args.ArgType.object;
import static jadx.core.dex.instructions.args.ArgType.unknown;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

public class TypeMergeTest {

	@Before
	public void initClsp() throws IOException, DecodeException {
		ClspGraph clsp = new ClspGraph();
		clsp.load();
		ArgType.setClsp(clsp);
	}

	@Test
	public void testMerge() throws IOException, DecodeException {
		first(INT, INT);
		first(BOOLEAN, INT);
		reject(INT, LONG);
		first(INT, UNKNOWN);
		reject(INT, UNKNOWN_OBJECT);

		first(INT, NARROW);
		first(CHAR, INT);

		check(unknown(PrimitiveType.INT, PrimitiveType.BOOLEAN, PrimitiveType.FLOAT),
				unknown(PrimitiveType.INT, PrimitiveType.BOOLEAN),
				unknown(PrimitiveType.INT, PrimitiveType.BOOLEAN));

		check(unknown(PrimitiveType.INT, PrimitiveType.FLOAT),
				unknown(PrimitiveType.INT, PrimitiveType.BOOLEAN),
				INT);

		check(unknown(PrimitiveType.INT, PrimitiveType.OBJECT),
				unknown(PrimitiveType.OBJECT, PrimitiveType.ARRAY),
				unknown(PrimitiveType.OBJECT));

		ArgType objExc = object("java.lang.Exception");
		ArgType objThr = object("java.lang.Throwable");
		ArgType objIO = object("java.io.IOException");
		ArgType objArr = object("java.lang.ArrayIndexOutOfBoundsException");
		ArgType objList = object("java.util.List");

		first(objExc, objExc);
		check(objExc, objList, OBJECT);
		first(objExc, OBJECT);

		check(objExc, objThr, objThr);
		check(objIO, objArr, objExc);

		ArgType generic = genericType("T");
		first(generic, objExc);
	}

	@Test
	public void testArrayMerge() {
		check(array(INT), array(BYTE), ArgType.OBJECT);
		first(array(INT), array(INT));
		first(array(STRING), array(STRING));

		first(OBJECT, array(INT));
		first(OBJECT, array(STRING));

		first(array(unknown(PrimitiveType.INT, PrimitiveType.FLOAT)), unknown(PrimitiveType.ARRAY));
	}

	private void first(ArgType t1, ArgType t2) {
		check(t1, t2, t1);
	}

	private void reject(ArgType t1, ArgType t2) {
		check(t1, t2, null);
	}

	private void check(ArgType t1, ArgType t2, ArgType exp) {
		merge(t1, t2, exp);
		merge(t2, t1, exp);
	}

	private void merge(ArgType t1, ArgType t2, ArgType exp) {
		ArgType res = ArgType.merge(t1, t2);
		String msg = format(t1, t2, exp, res);
		if (exp == null) {
			assertNull("Incorrect accept: " + msg, res);
		} else {
			assertNotNull("Incorrect reject: " + msg, res);
			assertTrue("Incorrect result: " + msg, exp.equals(res));
		}
	}

	private String format(ArgType t1, ArgType t2, ArgType exp, ArgType res) {
		return t1 + " <+> " + t2 + " = '" + res + "', expected: " + exp;
	}
}
