package se.mju.stackanalyzer.ui;

import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.util.Duration;
import se.mju.stackanalyzer.StackTracesStatistics;
import se.mju.stackanalyzer.model.StackTrace;
import se.mju.stackanalyzer.model.ThreadState;

public class StackpaneController implements EventHandler<MouseEvent> {
	private static final Color JAVA_COLOR = Color.web("e1b3ff");
	private static final Color SUN_COLOR = Color.web("e1b3ff").darker();
	private static final Color GRADLE_COLOR = Color.web("acd180");
	private static final Color AVINODE_COLOR = Color.rgb(152, 191, 12);
	private static final Color SELENIUM_COLOR = Color.LIGHTGREY;
	private static final Color DEFAULT_COLOR = Color.CYAN;
	
	private final ChildResizePane resizePane;
	private StacksPane currentRoot;
	private StacksPane root;
	private final Duration duration = Duration.millis(500);
	private ContextMenu contextMenu;
	private StacksPane contextMenuElement;
	// Start with filter that fixes reflection optimizations
	private Predicate<StackTraceElement> methodFilter = e -> !e.getClassName().startsWith("sun.reflect");
	private final List<ThreadState> unfilteredStacks;

	public StackpaneController(List<ThreadState> stacks) {
        StackTracesStatistics stats = new StackTracesStatistics(filter(stacks));
        StacksPane rootStack = new StacksPane(stats.getStatForRoot(), stats);
        this.resizePane = new ChildResizePane(rootStack);

        unfilteredStacks = stacks;
		root = rootStack;
		currentRoot = root;
		
		root.addMouseListener(this);
		
		contextMenu = new ContextMenu();
		MenuItem cmItem1 = new MenuItem("Exclude this class");
		cmItem1.setOnAction(e -> filterClass(contextMenuElement.getStackTrace().getClassName()));
		contextMenu.getItems().add(cmItem1);
	}

	public void handle(MouseEvent mouseEvent) {
		if(mouseEvent.getButton().equals(MouseButton.PRIMARY)){
			currentRoot = getStacksPane(mouseEvent);
			resizePane.zoominOn(currentRoot, duration);
		} else if(mouseEvent.getButton().equals(MouseButton.SECONDARY)){
			contextMenuElement = getStacksPane(mouseEvent);
			contextMenu.show(root ,mouseEvent.getScreenX(),mouseEvent.getScreenY());
		}
	}
	
	private void filterClass(String className) {
//		Predicate<StackTraceElement> notReflection = e -> !e.getClassName().startsWith("sun.reflect");
//		Predicate<StackTraceElement> notJunitInternal = e -> !e.getClassName().startsWith("org.junit.internal");
//		Predicate<StackTraceElement> classFilter = e -> !e.getClassName().equals("com.avinode.testing.data.CustomTestDataSetLoader");
		Predicate<StackTraceElement> classFilter = e -> !e.getClassName().equals(className);
		methodFilter = methodFilter.and(classFilter);
		
        StackTracesStatistics stats = new StackTracesStatistics(filter(unfilteredStacks));
        root = new StacksPane(stats.getStatForRoot(), stats);
        this.resizePane.setChild(root);
        root.addMouseListener(this);
	}
	
	private List<ThreadState> filter(List<ThreadState> stacks) {
		Predicate<ThreadState> threadFilter = ts -> true;
		return stacks.stream()
				.filter(threadFilter)
				.map(threadState -> threadState.filtered(methodFilter))
				.collect(Collectors.toList());
	}


	private StacksPane getStacksPane(MouseEvent mouseEvent) {
		Node n = (Node) mouseEvent.getSource();
		while (!(n instanceof StacksPane) && n!=null) {
			n = n.getParent();
		}
		return (StacksPane) n;
	}

	public void zoomOut() {
		resizePane.zoominOn(root, duration);
	}

	public static Color getColor(StackTrace stackTrace) {
		if (stackTrace == null) {
			return Color.GRAY;
		}
		String className = stackTrace.getClassName();
		if (className.startsWith("java")) {
			return JAVA_COLOR;
		} else if (className.startsWith("com.sun.") || className.startsWith("sun.")) {
			return SUN_COLOR;
		} else if (className.startsWith("org.gradle.")) {
			return GRADLE_COLOR;
		} else if (className.startsWith("com.avinode.")) {
			return AVINODE_COLOR;
		} else if (className.startsWith("org.openqa.selenium.")) {
			return SELENIUM_COLOR;
		}
		return DEFAULT_COLOR;
	}

	public Node getNode() {
		return resizePane;
	}
}

@@ -1448,16 +1448,76 @@
 Stacks;%0A
+%09private Predicate%3CThreadState%3E threadFilter = ts -%3E true;%0A%0A
 %0A%09public
@@ -2119,16 +2119,146 @@
 mItem1);
+%0A%0A%09%09cmItem1 = new MenuItem(%22Exclude %3C 30 deep%22);%0A%09%09cmItem1.setOnAction(e -%3E filterDepth());%0A%09%09contextMenu.getItems().add(cmItem1);
 %0A%09%7D%0A%0A%09pu
@@ -3174,19 +3174,194 @@
 r);%0A
+%0A
 %09%09
-%0A        
+updateFiltered();%0A%09%7D%0A%0A%09private void filterDepth() %7B%0A%09%09threadFilter = threadFilter.and(it -%3E it.getElements().size() %3E 30);%0A%0A%09%09updateFiltered();%0A%09%7D%0A%0A%09private void updateFiltered() %7B%0A%09%09
 Stac
@@ -3439,24 +3439,18 @@
 acks));%0A
-        
+%09%09
 root = n
@@ -3491,32 +3491,26 @@
 (), stats);%0A
-        
+%09%09
 this.resizeP
@@ -3529,24 +3529,18 @@
 (root);%0A
-        
+%09%09
 root.add
@@ -3559,26 +3559,25 @@
 r(this);%0A%09%7D%0A
-%09
 %0A
+
 %09private Lis
@@ -3630,60 +3630,8 @@
 ) %7B%0A
-%09%09Predicate%3CThreadState%3E threadFilter = ts -%3E true;%0A
 %09%09re
@@ -3776,13 +3776,12 @@
 ));%0A
+
 %09%7D%0A%0A
-%0A
 %09pri

Allow filtering stack depth for > 30 deep