package gumtree.spoon;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.util.List;
import java.util.Optional;

import org.apache.commons.io.FileUtils;
import org.junit.Test;

import com.github.gumtreediff.io.TreeIoUtils;
import com.github.gumtreediff.io.TreeIoUtils.TreeSerializer;
import com.github.gumtreediff.tree.ITree;
import com.github.gumtreediff.tree.TreeContext;
import com.google.gson.JsonObject;

import gumtree.spoon.builder.CtWrapper;
import gumtree.spoon.builder.Json4SpoonGenerator;
import gumtree.spoon.builder.Json4SpoonGenerator.JSON_PROPERTIES;
import gumtree.spoon.builder.SpoonGumTreeBuilder;
import gumtree.spoon.diff.Diff;
import gumtree.spoon.diff.DiffImpl;
import gumtree.spoon.diff.operations.InsertOperation;
import gumtree.spoon.diff.operations.Operation;
import gumtree.spoon.diff.operations.UpdateOperation;
import spoon.Launcher;
import spoon.compiler.SpoonResourceHelper;
import spoon.reflect.code.CtStatement;
import spoon.reflect.cu.SourcePosition;
import spoon.reflect.cu.position.NoSourcePosition;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtType;
import spoon.reflect.factory.Factory;
import spoon.reflect.path.CtPath;
import spoon.reflect.visitor.CtScanner;

public class TreeTest {

	@Test
	public void test_Path_1() throws Exception {
		AstComparator diff = new AstComparator();
		File fl = new File("src/test/resources/examples/roots/test8/left_QuickNotepad_1.13.java");
		File fr = new File("src/test/resources/examples/roots/test8/right_QuickNotepad_1.14.java");

		CtType<?> astLeft = diff.getCtType(fl);

		assertNotNull(astLeft);
		assertEquals("QuickNotepad", astLeft.getSimpleName());

		CtPath pathLeft = astLeft.getPath();
		assertNotNull(pathLeft);

		CtType<?> astRight = diff.getCtType(fr);

		assertNotNull(astRight);
		assertEquals("QuickNotepad", astRight.getSimpleName());
		// No package
		assertEquals("QuickNotepad", astRight.getQualifiedName());

		CtPath pathRight = astRight.getPath();
		assertNotNull(pathRight);

		pathOfStmt(astLeft);
		pathOfStmt(astRight);

	}

	@Test
	public void test_Path_of_affected_nodes() throws Exception {
		AstComparator comparator = new AstComparator();
		File fl = new File("src/test/resources/examples/roots/test8/left_QuickNotepad_1.13.java");
		File fr = new File("src/test/resources/examples/roots/test8/right_QuickNotepad_1.14.java");

		CtType<?> astLeft = comparator.getCtType(fl);

		assertNotNull(astLeft);

		CtType<?> astRight = comparator.getCtType(fr);
		assertNotNull(astRight);

		Diff diffResult = comparator.compare(astLeft, astRight);
		List<Operation> rootOperations = diffResult.getRootOperations();
		getPaths(rootOperations);
		List<Operation> allOperations = diffResult.getAllOperations();
		getPaths(allOperations);

	}

	@Test
	public void test_Path_Scanner_failing_tofix() throws Exception {
		AstComparator diff = new AstComparator();
		File fl = new File("src/test/resources/examples/roots/test8/left_QuickNotepad_1.13.java");
		File fr = new File("src/test/resources/examples/roots/test8/right_QuickNotepad_1.14.java");

		CtType<?> astLeft = diff.getCtType(fl);

		assertNotNull(astLeft);
		assertEquals("QuickNotepad", astLeft.getSimpleName());

		CtPath pathLeft = astLeft.getPath();
		assertNotNull(pathLeft);

		CtType<?> astRight = diff.getCtType(fr);

		assertNotNull(astRight);
		assertEquals("QuickNotepad", astRight.getSimpleName());
		// No package in that file
		assertEquals("QuickNotepad", astRight.getQualifiedName());

		CtPath pathRight = astRight.getPath();
		assertNotNull(pathRight);

		PathScanner pscanner = new PathScanner();
		astLeft.accept(pscanner);
		astRight.accept(pscanner);

	}

	@Test
	public void test_Path_ScannerWithSpoon_left() throws Exception {

		Launcher spoon = new Launcher();
		Factory factory = spoon.createFactory();
		spoon.createCompiler(factory,
				SpoonResourceHelper.resources("src/test/resources/examples/roots/test8/left_QuickNotepad_1.13.java"))
				.build();

		CtType<?> astLeft = factory.Type().get("QuickNotepad");
		assertNotNull(astLeft.getPath());
		assertNotNull(astLeft);
		PathScanner pscanner = new PathScanner();
		astLeft.accept(pscanner);

	}

	@Test
	public void test_Path_ScannerWithSpoon_Right() throws Exception {

		Launcher spoon = new Launcher();
		Factory factory = spoon.createFactory();
		spoon.createCompiler(factory,
				SpoonResourceHelper.resources("src/test/resources/examples/roots/test8/right_QuickNotepad_1.14.java"))
				.build();

		CtType<?> astLeft = factory.Type().get("QuickNotepad");
		assertNotNull(astLeft.getPath());
		assertNotNull(astLeft);
		PathScanner pscanner = new PathScanner();
		astLeft.accept(pscanner);

	}

	@Test
	public void test_JSON_from_GT() throws Exception {

		Launcher spoon = new Launcher();
		Factory factory = spoon.createFactory();
		spoon.createCompiler(factory,
				SpoonResourceHelper.resources("src/test/resources/examples/roots/test8/right_QuickNotepad_1.14.java"))
				.build();

		CtType<?> astLeft = factory.Type().get("QuickNotepad");
		SpoonGumTreeBuilder builder = new SpoonGumTreeBuilder();
		ITree generatedTree = builder.getTree(astLeft);

		TreeContext tcontext = new TreeContext();
		tcontext.setRoot(generatedTree);
		TreeSerializer ts = TreeIoUtils.toJson(tcontext);
		String out = ts.toString();
		assertNotNull(out);
	}

	@Test
	public void test_JSON_manual_generation_1() throws Exception {

		Launcher spoon = new Launcher();
		Factory factory = spoon.createFactory();
		spoon.createCompiler(factory,
				SpoonResourceHelper.resources("src/test/resources/examples/roots/test8/right_QuickNotepad_1.14.java"))
				.build();

		CtType<?> aType = factory.Type().get("QuickNotepad");
		SpoonGumTreeBuilder builder = new SpoonGumTreeBuilder();
		assertNotNull(new Json4SpoonGenerator().getJSONasJsonObject(aType));
	}

	@Test
	public void test_JSON_manual_generation_2() throws Exception {

		Launcher spoon = new Launcher();

		spoon.getFactory().getEnvironment().setCommentEnabled(false);
		Factory factory = spoon.createFactory();
		factory.getEnvironment().setCommentEnabled(false);
		spoon.createCompiler(factory,
				SpoonResourceHelper.resources("src/test/resources/examples/roots/test8/right_QuickNotepad_1.14.java"))
				.build();

		CtType<?> aType = factory.Type().get("QuickNotepad");
		String jsoNasString = new Json4SpoonGenerator().getJSONasString(aType).trim();
		assertEquals(FileUtils.readFileToString(new File("./src/test/resources/examples/spoon.json")).trim(),
				jsoNasString);
	}

	@Test
	public void test_Path_ScannerWithGTFailing_left() throws Exception {

		AstComparator diff = new AstComparator();
		File fl = new File("src/test/resources/examples/roots/test8/left_QuickNotepad_1.13.java");

		CtType<?> astLeft = diff.getCtType(fl);

		PathScanner pscanner = new PathScanner();

		assertNotNull(astLeft.getPath());
		assertNotNull(astLeft);
		astLeft.accept(pscanner);

	}

	@Test
	public void test_Path_ScannerWithGTFailing_right() throws Exception {

		AstComparator diff = new AstComparator();

		File fr = new File("src/test/resources/examples/roots/test8/right_QuickNotepad_1.14.java");

		CtType<?> astRight = diff.getCtType(fr);

		PathScanner pscanner = new PathScanner();

		assertNotNull(astRight.getPath());
		assertNotNull(astRight);
		astRight.accept(pscanner);

	}

	@Test
	public void test_Path_ScannerWithGTFailing_both() throws Exception {

		AstComparator diff = new AstComparator();
		File fl = new File("src/test/resources/examples/roots/test8/left_QuickNotepad_1.13.java");

		CtType<?> astLeft = diff.getCtType(fl);

		File fr = new File("src/test/resources/examples/roots/test8/right_QuickNotepad_1.14.java");

		CtType<?> astRight = diff.getCtType(fr);

		PathScanner pscanner = new PathScanner();

		assertNotNull(astLeft.getPath());
		assertNotNull(astLeft);
		astLeft.accept(pscanner);

		assertNotNull(astRight.getPath());
		assertNotNull(astRight);
		astRight.accept(pscanner);

	}

	@Test
	public void test_Path_ScannerWithSpoon_both() throws Exception {

		Launcher spoon = new Launcher();
		Factory factory = spoon.createFactory();
		spoon.createCompiler(factory,
				SpoonResourceHelper.resources("src/test/resources/examples/roots/test8/left_QuickNotepad_1.13.java"))
				.build();

		CtType<?> astLeft = factory.Type().get("QuickNotepad");

		// So, if we create a new factory, it works.
		// Reusing the same factory, the path fails.
		factory = spoon.createFactory();
		spoon.createCompiler(factory,
				SpoonResourceHelper.resources("src/test/resources/examples/roots/test8/left_QuickNotepad_1.13.java"))
				.build();

		CtType<?> astRight = factory.Type().get("QuickNotepad");

		PathScanner pscanner = new PathScanner();

		assertNotNull(astLeft.getPath());
		assertNotNull(astLeft);
		astLeft.accept(pscanner);

		assertNotNull(astRight.getPath());
		assertNotNull(astRight);
		astRight.accept(pscanner);

	}

	private void getPaths(List<Operation> rootops) {
		for (Operation<?> op : rootops) {

			CtElement left = op.getSrcNode();
			CtPath pleft = left.getPath();
			assertNotNull(pleft);

			CtElement right = op.getSrcNode();
			CtPath pright = right.getPath();
			assertNotNull(pleft);

		}
	}

	private void pathOfStmt(CtType<?> ast) {
		for (CtMethod method : ast.getAllMethods()) {

			CtPath path = method.getPath();
			assertNotNull(path);

			for (CtStatement stmt : method.getBody().getStatements()) {
				path = stmt.getPath();
				assertNotNull(path);
			}

		}
	}

	private final class PathScanner extends CtScanner {
		@Override
		public void scan(CtElement element) {
			if (element != null) {
				CtPath path = element.getPath();
				assertNotNull(path);

			}
			// to avoid visiting all childs
			super.scan(element);
		}
	}

	@SuppressWarnings("rawtypes")
	@Test
	public void test_packageName_202564() throws Exception {
		AstComparator diff = new AstComparator();
		File fl = new File("src/test/resources/examples/t_202564/left_PropPanelModelElement_1.9.java");

		CtType typel = diff.getCtType(fl);
		assertNotNull(typel);
		assertEquals("org.argouml.uml.ui.foundation.core.PropPanelModelElement", typel.getQualifiedName());
		assertEquals("org.argouml.uml.ui.foundation.core", typel.getPackage().getQualifiedName());

	}

	@Test
	public void test_JSON_COLORED() throws Exception {

		AstComparator diff = new AstComparator();
		File fl = new File("src/test/resources/examples/roots/test8/left_QuickNotepad_1.13.java");

		CtType<?> astLeft = diff.getCtType(fl);

		File fr = new File("src/test/resources/examples/roots/test8/right_QuickNotepad_1.14.java");

		CtType<?> astRight = diff.getCtType(fr);

		assertNotNull(astLeft);

		assertNotNull(astRight);

		DiffImpl diffC = (DiffImpl) diff.compare(astLeft, astRight);

		TreeContext context = diffC.getContext();

		assertTrue(diffC.getAllOperations().size() > 0);
		assertTrue(diffC.getAllOperations().get(0) instanceof InsertOperation);

		Json4SpoonGenerator jsongen = new Json4SpoonGenerator();

		// Modified
		ITree insertedNode = diffC.getRootOperations().get(0).getAction().getNode();

		JsonObject jsonOb = jsongen.getJSONwithOperations(context, insertedNode, diffC.getAllOperations());

		assertTrue(jsonOb.has(JSON_PROPERTIES.op.toString()));

		assertEquals("\"INS\"", jsonOb.get(JSON_PROPERTIES.op.toString()).toString());
	}

	@Test
	public void test_bugissue84_original_failling() throws Exception {
		File fl = new File("src/test/resources/examples/919148/ReplicationRun/919148_ReplicationRun_0_s.java");
		File fr = new File("src/test/resources/examples/919148/ReplicationRun/919148_ReplicationRun_0_t.java");

		AstComparator diff = new AstComparator();

		DiffImpl diffC = (DiffImpl) diff.compare(fl, fr);

		List<Operation> ops = diffC.getAllOperations();

		for (Operation operation : ops) {
			assertNotNull(operation.getSrcNode());
		}

	}

	@Test
	public void test_bugissue84_1_insert() throws Exception {
		File fl = new File("src/test/resources/examples/issue84_1/file_s.java");
		File fr = new File("src/test/resources/examples/issue84_1/file_t.java");

		AstComparator diff = new AstComparator();

		DiffImpl diffC = (DiffImpl) diff.compare(fl, fr);

		List<Operation> ops = diffC.getAllOperations();

		// to change a method to static means to change the type accesses that invoke
		// that method
		assertTrue(ops.size() > 0);

		for (Operation operation : ops) {
			assertNotNull(operation.getSrcNode());
		}

		Optional<Operation> findInsert = ops.stream().filter(e -> e instanceof InsertOperation).findFirst();

		assertTrue(findInsert.isPresent());

		Operation op1 = findInsert.get();
		assertTrue(op1 instanceof InsertOperation);

		assertTrue(op1.getSrcNode() instanceof CtWrapper);

		assertEquals("static", ((CtWrapper) op1.getSrcNode()).getValue().toString());

	}

	@Test
	public void test_bugissue84_2_update() throws Exception {
		File fl = new File("src/test/resources/examples/issue84_2/file_s.java");
		File fr = new File("src/test/resources/examples/issue84_2/file_t.java");

		AstComparator diff = new AstComparator();

		DiffImpl diffC = (DiffImpl) diff.compare(fl, fr);

		List<Operation> ops = diffC.getAllOperations();
		assertEquals(1, ops.size());
		System.out.println(ops);

		for (Operation operation : ops) {
			assertNotNull(operation.getSrcNode());
		}

		Operation op1 = ops.get(0);
		assertTrue(op1 instanceof UpdateOperation);

		assertTrue(op1.getSrcNode() instanceof CtWrapper);

		assertEquals("public", ((CtWrapper) op1.getSrcNode()).getValue().toString());

		assertEquals("protected", ((CtWrapper) op1.getDstNode()).getValue().toString());

	}

	@Test
	public void test_bug_Possition() throws Exception {
		AstComparator comparator = new AstComparator();
		File fl = new File("src/test/resources/examples/roots/test8/left_QuickNotepad_1.13.java");
		File fr = new File("src/test/resources/examples/roots/test8/right_QuickNotepad_1.14.java");

		CtType<?> astLeft = comparator.getCtType(fl);

		assertNotNull(astLeft);

		CtType<?> astRight = comparator.getCtType(fr);
		assertNotNull(astRight);

		Diff diffResult = comparator.compare(astLeft, astRight);
		List<Operation> rootOperations = diffResult.getRootOperations();
		getPaths(rootOperations);
		List<Operation> allOperations = diffResult.getAllOperations();
		getPaths(allOperations);

		assertEquals(1, rootOperations.size());

		SourcePosition position = rootOperations.get(0).getSrcNode().getPosition();
		assertTrue(position.getLine() > 0);
		assertEquals(113, position.getLine());

		assertTrue(!(position instanceof NoSourcePosition));

	}

	@Test
	public void test_bug_Possition_from_String() {
		String c1 = "" + "class X {\n" + "public void foo() {\n" + " int x = 0;\n" + "}" + "};";

		String c2 = "" + "class X {\n" + "public void foo() {\n" + " int x = 1;\n" + "}" + "};";

		AstComparator diff = new AstComparator();
		Diff editScript = diff.compare(c1, c2);
		assertTrue(editScript.getRootOperations().size() == 1);

		List<Operation> rootOperations = editScript.getRootOperations();

		assertEquals(1, rootOperations.size());

		SourcePosition position = rootOperations.get(0).getSrcNode().getPosition();
		assertTrue(!(position instanceof NoSourcePosition));

		assertTrue(position.getLine() > 0);
		assertEquals(3, position.getLine());

	}
}