package net.ranides.demo.debugger.impl;

import com.sun.jdi.Bootstrap;
import com.sun.jdi.ClassType;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;

import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.generic.ValueUtils;

import net.ranides.demo.debugger.DiClass;
import net.ranides.demo.debugger.DiClasses;
import net.ranides.demo.debugger.DiConnection;
import net.ranides.demo.debugger.DiException;
import net.ranides.demo.debugger.DiLocation;
import net.ranides.demo.debugger.DiMirror;

import java.io.IOException;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class CDiConnection implements DiConnection {

    final VirtualMachine vm;

    final CDiMirror mirror;

    private CDiConnection(String host, String port) {
        AttachingConnector connector = Bootstrap.virtualMachineManager()
            .attachingConnectors()
            .stream()
            .filter(v -> v.name().equals("com.sun.jdi.SocketAttach"))
            .findFirst()
            .orElseThrow(() -> new UnsupportedOperationException("No SocketAttachingConnector found."));

        Map<String, Connector.Argument> args = connector.defaultArguments();
        args.get("hostname").setValue(host);
        args.get("port").setValue(port);

        try {
            this.vm = connector.attach(args);
            this.mirror = new CDiMirror(vm);
        } catch (IOException | IllegalConnectorArgumentsException cause) {
            throw new DiException(cause);
        }
    }

    public static DiConnection connect(String addr) {
        Matcher hit = Pattern.compile("([^:]+)?(?:([0-9]+))").matcher(addr);
        if(!hit.matches()) {
            throw new DiException(new IllegalArgumentException("Invalid address: " + addr));
        }
        String host = ValueUtils.or(hit.group(1), "localhost");
        String port = hit.group(2);
        return connect(host, port);
    }

    public static DiConnection connect(String host, String port) {
        return new CDiConnection(host, port);
    }

    @Override
    public DiLocation locate(String type, int line) {
        return types(type).first().map(c -> c.locate(line)).orElseThrow(NoSuchElementException::new);
    }

    @Override
    public DiLocation locate(String location) {
        String[] parts = location.split("@",2);
        return locate(parts[0], Integer.parseInt(parts[1]));
    }

    @Override
    public DiClasses types(String name) {
        CQuery<DiClass> query = CQuery.from()
            .collection(vm.classesByName(name))
            .filter(ClassType.class::isInstance)
            .map(ClassType.class::cast)
            .map(CDiClass::new);
        return new CDiClasses(query);
    }

    @Override
    public DiClasses types() {
        CQuery<DiClass> query = CQuery.from()
            .collection(vm.allClasses())
            .filter(ClassType.class::isInstance)
            .map(ClassType.class::cast)
            .map(CDiClass::new);
        return new CDiClasses(query);
    }

    @Override
    public VirtualMachine vm() {
        return vm;
    }

    @Override
    public DiMirror mirror() {
        return mirror;
    }

    @Override
    public void close() {
        vm.dispose();
    }
}
