package me.coley.recaf.control.headless;

import me.coley.recaf.command.impl.Disassemble;
import me.coley.recaf.command.impl.LoadWorkspace;
import org.jline.reader.*;
import org.jline.reader.impl.DefaultParser;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import org.objectweb.asm.*;
import picocli.CommandLine;
import picocli.shell.jline3.PicocliJLineCompleter;

import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.logging.*;
import java.util.stream.Collectors;

import static me.coley.recaf.util.Log.*;

/**
 * JLine tie-in for the headless controller. Allows tab-completion on supported terminals.
 *
 * @author Matt
 */
public class JLineAdapter implements Opcodes {
	private HeadlessController controller;
	private final Consumer<String> handler;
	private Terminal terminal;
	private LineReader reader;

	/**
	 * @param controller
	 * 		Controller context.
	 * @param handler
	 * 		Command input handler.
	 *
	 * @throws IOException
	 * 		Thrown when the terminal cannot be built.
	 */
	JLineAdapter(HeadlessController controller, Consumer<String> handler)throws IOException {
		this.controller = controller;
		this.handler = handler;
		setupJLine();
		checkWorkspace();
	}

	private void setupJLine() throws IOException {
		terminal = TerminalBuilder.builder().build();
		reader = setupCompletionReader(terminal, controller.getLookup());
	}

	private void checkWorkspace() {
		// Ensure a workspace is open
		if(controller.getWorkspace() == null) {
			info("Please input the path to a java program (class, jar) " + "or workspace " +
					"file (json).\nSee documentation below:\n");
			usage(controller.get(LoadWorkspace.class));
		}
	}

	/**
	 * Handle input with JLine.
	 */
	void loop() {
		// Prompt & run commands from user input
		while(controller.isRunning()) {
			try {
				String line = reader.readLine("\n$ ", null, (MaskingCallback) null, null);
				ParsedLine pl = reader.getParser().parse(line, 0);
				handler.accept(pl.line());
			} catch(UserInterruptException e) {
				// Ignore
			} catch(EndOfFileException e) {
				return;
			}
		}
	}

	/**
	 * Print usage of command.
	 *
	 * @param command
	 * 		Command to show usage of.
	 */
	private void usage(Callable<?> command) {
		CommandLine cmd = new CommandLine(command);
		cmd.usage(cmd.getOut());
	}

	/**
	 * @param terminal
	 * 		Terminal to add tab-completion to.
	 * @param lookup
	 * 		Map containing commands.
	 *
	 * @return Reader with tab-completion.
	 */
	private static LineReader setupCompletionReader(Terminal terminal, Map<String, Class<?>> lookup) {
		// Filter root level commands
		Collection<Class<?>> commands =	lookup.entrySet().stream()
				.filter(e -> !e.getKey().contains(" "))
				.map(Map.Entry::getValue)
				.collect(Collectors.toList());
		// Pass dummy to pico for tab-completion
		CommandLine cmd = new CommandLine(SubContainerGenerator.generate(commands));
		return LineReaderBuilder.builder()
				.terminal(terminal)
				.completer(new PicocliJLineCompleter(cmd.getCommandSpec()))
				.parser(new DefaultParser())
				.build();
	}

	/**
	 * Use the JLine nano tool to modify the disassembled code.
	 *
	 * @param result
	 * 		Disassemble output wrapper.
	 */
	void handleDisassemble(Disassemble.Result result) {
		JLineEditor editor = new JLineEditor(terminal);
		editor.open(result);
	}

	static {
		// Disable native JLine logging
		java.util.logging.Logger.getLogger("org.jline").setLevel(Level.OFF);
	}
}