package me.coley.recaf.ui.component;

import java.util.Arrays;
import java.util.List;

import org.objectweb.asm.tree.*;
import org.objectweb.asm.tree.analysis.*;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
import me.coley.recaf.ui.FormatFactory;
import me.coley.recaf.ui.component.InsnListEditor.OpcodeList;
import me.coley.recaf.util.JavaFX;
import me.coley.recaf.util.Lang;

// TODO: When user updates bytecode, regen frames
public class StackWatcher extends Stage implements ListChangeListener<AbstractInsnNode>, ChangeListener<Number> {
	private final ClassNode owner;
	private final MethodNode method;
	private final OpcodeList list;
	private final TableView<SourceValue> stack = new TableView<>();
	private final TableView<SourceValue> local = new TableView<>();
	private List<Frame<SourceValue>> frames;

	public StackWatcher(ClassNode owner, MethodNode method, OpcodeList list) {
		this.owner = owner;
		this.method = method;
		this.list = list;
		setupUI();
	}

	@SuppressWarnings("unchecked")
	private void setupUI() {
		// setup table
		// TODO: second column for "ui.edit.method.stackhelper.colstackvalue"
		TableColumn<SourceValue, Node> colStackSrc = new TableColumn<>(Lang.get("ui.edit.method.stackhelper.colstackopcode"));
		colStackSrc.setCellValueFactory(cell -> {
			SourceValue v = cell.getValue();
			Node box;
			if (v != null) {
				box = FormatFactory.opcodeSet(v.insns, method);
			} else {
				box = new TextHBox();
				((TextHBox) box).append("?");
			}
			return JavaFX.observable(box);
		});
		colStackSrc.setCellFactory(cell -> new TableCell<SourceValue, Node>() {
			@Override
			protected void updateItem(Node box, boolean empty) {
				super.updateItem(box, empty);
				if (empty || box == null) {
					setGraphic(null);
				} else {
					setGraphic(box);
				}
			}
		});
		colStackSrc.setMinWidth(250);
		TableColumn<SourceValue, Node> colLocalSrc = new TableColumn<>(Lang.get("ui.edit.method.stackhelper.collocalopcode"));
		colLocalSrc.setCellValueFactory(cell -> {
			SourceValue v = cell.getValue();
			Node box;
			if (v != null) {
				box = FormatFactory.opcodeSet(v.insns, method);
			} else {
				box = new TextHBox();
				((TextHBox) box).append("?");
			}
			return JavaFX.observable(box);
		});
		colLocalSrc.setCellFactory(cell -> new TableCell<SourceValue, Node>() {
			@Override
			protected void updateItem(Node box, boolean empty) {
				super.updateItem(box, empty);
				if (empty || box == null) {
					setGraphic(null);
				} else {
					setGraphic(box);
				}
			}
		});
		colLocalSrc.setMinWidth(250);
		stack.getColumns().add(colStackSrc);
		local.getColumns().add(colLocalSrc);
		// create scene
		Scene scene = JavaFX.scene(new SplitPane(stack, local), 500, 300);
		setScene(scene);
	}

	public void update() {
		try {
			Analyzer<SourceValue> analyzer = new Analyzer<>(new SourceInterpreter());
			frames = Arrays.asList(analyzer.analyze(owner.name, method));
		} catch (Exception e) {

		}
	}

	public void select(int selected) {
		// offset by one so that the selected index is the change that the
		// selected opcode does
		int index = selected + 1;
		stack.getItems().clear();
		local.getItems().clear();
		if (frames != null && index >= 0 && index < frames.size() - 1) {
			Frame<SourceValue> frame = frames.get(index);
			for (int s = 0; s < frame.getStackSize(); s++) {
				SourceValue val;
				try {
					val = frame.getStack(s);
				} catch (IndexOutOfBoundsException e) {
					val = new SourceValue(0);
				}
				stack.getItems().add(val);
				
			}
			for (int l = 0; l < frame.getLocals(); l++) {
				SourceValue val;
				try {
					val = frame.getLocal(l);
				} catch (IndexOutOfBoundsException e) {
					// TODO: Allow for local variables to have 'default' values
					// 
					// Primary example would be 'this' and method arguments.
					//
					val = new SourceValue(0);
				}
				System.out.println("L: @" + l + " " + val);
				local.getItems().add(val);
			}
		}
		stack.refresh();
		local.refresh();
	}

	/**
	 * Listener for selection changes. Update UI.
	 */
	@Override
	public void changed(ObservableValue<? extends Number> ob, Number last, Number current) {
		select(current.intValue());
	}

	/**
	 * Listener for content changes. Regenerate stack-frames.
	 */
	@Override
	public void onChanged(Change<? extends AbstractInsnNode> change) {
		update();
	}
}

@@ -2146,26 +2146,26 @@
 setMinWidth(
-25
+30
 0);%0A%09%09TableC
@@ -2873,10 +2873,10 @@
 dth(
-25
+30
 0);%0A
@@ -3033,10 +3033,10 @@
 l), 
-50
+61
 0, 3
@@ -4177,56 +4177,8 @@
 %09%09%7D%0A
-%09%09%09%09System.out.println(%22L: @%22 + l + %22 %22 + val);%0A
 %09%09%09%09

remove debugging + increase width of stack assit