/*
 * Copyright (c) 2002, 2022, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package nsk.share.jdb;

import nsk.share.*;
import nsk.share.jpda.*;
import nsk.share.jdi.ArgumentHandler;

import java.io.*;
import java.util.*;

/**
 * This class provides launching of <code>jdb</code> and debuggee in local
 * or remote mode according to test command line options.
 */

public class Launcher extends DebugeeBinder {

    /* Delay in milliseconds after launching jdb.*/
    static final long DEBUGGEE_START_DELAY = 5 * 1000;

    protected static Jdb jdb;

    protected static Debuggee debuggee;

    /** Pattern for message of jdb has started. */
    protected static String JDB_STARTED = "Initializing jdb";

    /**
     * Get version string.
     */
    public static String getVersion () {
        return "@(#)Launcher.java %I% %E%";
    }

    // -------------------------------------------------- //

    /**
     * Handler of command line arguments.
     */
    protected static JdbArgumentHandler argumentHandler = null;

    /**
     * Return <code>argumentHandler</code> of this binder.
     */
    public static JdbArgumentHandler getJdbArgumentHandler() {
        return argumentHandler;
    }

    /**
     * Return <code>jdb</code> mirror of this binder.
     */
    public static Jdb getJdb() {
        return jdb;
    }

    /**
     * Return debuggee mirror of this binder.
     */
    public static Debuggee getDebuggee() {
        return debuggee;
    }

    /**
     * Incarnate new Launcher obeying the given
     * <code>argumentHandler</code>; and assign the given
     * <code>log</code>.
     */
    public Launcher (JdbArgumentHandler argumentHandler, Log log) {
        super(argumentHandler, log);
        setLogPrefix("launcher > ");
        this.argumentHandler = argumentHandler;
    }

    /**
     * Defines mode (local or remote) and type of connector (default, launching,
     * raw launching, attaching or listening) according to options
     * parsed by <code>JdbArgumentHandler</code>. And then launches <code>jdb</code>
     * and debuggee in defined mode.
     */
    public void launchJdbAndDebuggee (String classToExecute) throws IOException {

        String[] jdbCmdArgs = makeJdbCmdLine(classToExecute);

        if (argumentHandler.isLaunchedLocally()) {

            if (argumentHandler.isDefaultConnector()) {

                localDefaultLaunch(jdbCmdArgs, classToExecute);

            } else if (argumentHandler.isRawLaunchingConnector()) {

                localRawLaunch(jdbCmdArgs, classToExecute);

            } else if (argumentHandler.isLaunchingConnector()) {

                localLaunch(jdbCmdArgs, classToExecute);

            } else if (argumentHandler.isAttachingConnector()) {

                localLaunchAndAttach(jdbCmdArgs, classToExecute);

            } else if (argumentHandler.isListeningConnector()) {

                localLaunchAndListen(jdbCmdArgs, classToExecute);

            } else {
                throw new TestBug("Unexpected connector type for local launch mode"
                                  + argumentHandler.getConnectorType());
            }

        } else if (argumentHandler.isLaunchedRemotely()) {

            connectToBindServer(classToExecute);

            if (argumentHandler.isAttachingConnector()) {

                remoteLaunchAndAttach(jdbCmdArgs, classToExecute);

            } else if (argumentHandler.isListeningConnector()) {

                remoteLaunchAndListen(jdbCmdArgs, classToExecute);

            } else {
                throw new TestBug("Unexpected connector type for remote launch mode"
                                  + argumentHandler.getConnectorType());
            }
        } else {
            throw new Failure("Unexpected launching mode: " + argumentHandler.getLaunchMode());
        }
    }

    /**
     * Creates String array to launch <code>jdb</code> according to options
     * parsed by <code>JdbArgumentHandler</code>.
     */
    private String[] makeJdbCmdLine (String classToExecute) {

        Vector<String> args = new Vector<String>();

        String jdbExecPath = argumentHandler.getJdbExecPath();
        args.add(jdbExecPath.trim());

        if (argumentHandler.isLaunchingConnector()) {
            /* Need --enable-preview on the debuggee in order to support virtual threads. */
            boolean vthreadMode = "Virtual".equals(System.getProperty("main.wrapper"));
            if (vthreadMode) {
                args.add("-R--enable-preview");
            }
        }

        args.addAll(argumentHandler.enwrapJavaOptions(argumentHandler.getJavaOptions()));

        String jdbOptions = argumentHandler.getJdbOptions();
        if (jdbOptions.trim().length() > 0) {
            StringTokenizer tokenizer = new StringTokenizer(jdbOptions);
            while (tokenizer.hasMoreTokens()) {
                String option = tokenizer.nextToken();
                args.add(option);
            }
        }
        if (classToExecute == null)
            return args.toArray(new String[args.size()]);
        args.add("-connect");
        StringBuffer connect = new StringBuffer();

        if (argumentHandler.isLaunchingConnector()) {

// Do not need to use quote symbol.
//            String quote = '\"';
//            connect.append(quote + argumentHandler.getConnectorName() + ":");
            connect.append(argumentHandler.getConnectorName() + ":");

            String connectorAddress;
            String vmAddress = makeTransportAddress();

            if (argumentHandler.isRawLaunchingConnector()) {

                if (argumentHandler.isSocketTransport()) {
                    if (argumentHandler.isLaunchedLocally()) {
                        connectorAddress = argumentHandler.getTransportPort();
                    } else {
                        connectorAddress = argumentHandler.getDebugeeHost() + ":" + argumentHandler.getTransportPort();
                    }
                } else if (argumentHandler.isShmemTransport() ) {
                    connectorAddress = argumentHandler.getTransportSharedName();
                } else {
                    throw new TestBug("Launcher: Undefined transport type for RawLaunchingConnector");
                }

                connect.append("address=" + connectorAddress.trim());
                connect.append(",command=" + makeCommandLineString(classToExecute, vmAddress, " ").trim());

            } else /* LaunchingConnector or DefaultConnector */ {

                connect.append("vmexec=" + argumentHandler.getLaunchExecName().trim());
                String debuggeeOpts = argumentHandler.getDebuggeeOptions();
                if (debuggeeOpts.trim().length() > 0) {
                    //connect.append(",options=" + debuggeeOpts.trim());
                    connect.append(",options=");
                    for (String arg : debuggeeOpts.split("\\s+")) {
                       connect.append(" \"");
                       connect.append(arg);
                       connect.append("\"");
                    }
                }
                String cmdline = classToExecute + " " + ArgumentHandler.joinArguments(argumentHandler.getArguments(), " ");
                if (System.getProperty("main.wrapper") != null) {
                    cmdline = MainWrapper.class.getName() + " " + System.getProperty("main.wrapper") +  " " + cmdline;
                }
                connect.append(",main=" + cmdline.trim());

            }

//            connect.append(quote);

        } else {

            connect.append(argumentHandler.getConnectorName() + ":");

            if (argumentHandler.isAttachingConnector()) {

                if (argumentHandler.isSocketTransport()) {
                    connect.append("port=" + argumentHandler.getTransportPort().trim());
                    if (argumentHandler.isLaunchedRemotely())
                        connect.append(",hostname=" + argumentHandler.getDebugeeHost().trim());
                } else if (argumentHandler.isShmemTransport()) {
                    connect.append("name=" + argumentHandler.getTransportSharedName().trim());
                } else {
                    throw new TestBug("Launcher: Undefined transport type for AttachingConnector");
                }


            } else if (argumentHandler.isListeningConnector()) {

                if (!argumentHandler.isTransportAddressDynamic()) {
                    if (argumentHandler.isSocketTransport()) {
                        connect.append("port=" + argumentHandler.getTransportPort().trim());
                    } else if (argumentHandler.isShmemTransport()) {
                        connect.append("name=" + argumentHandler.getTransportSharedName().trim());
                    } else {
                        throw new TestBug("Launcher: Undefined transport type for AttachingConnector");
                    }
                }

            } else {
                throw new TestBug("Launcher: Undefined connector type");
            }

        }

        args.add(connect.toString().trim());

        String[] argsArray = new String[args.size()];
        for (int i = 0; i < args.size(); i++) {
            argsArray[i] = (String) args.elementAt(i);
        }

        return argsArray;
    }

    // ---------------------------------------------- //

    /**
     * Run test in local mode using default connector.
     */
    private void localDefaultLaunch
       (String[] jdbCmdArgs, String classToExecute) throws IOException {
        localLaunch(jdbCmdArgs, classToExecute);
    }

    /**
     * Run test in local mode using raw launching connector.
     */
    private void localRawLaunch
       (String[] jdbCmdArgs, String classToExecute) throws IOException {
        localLaunch(jdbCmdArgs, classToExecute);
    }

    /**
     * Run test in local mode using launching connector.
     */
    private void localLaunch
       (String[] jdbCmdArgs, String classToExecute) throws IOException {

        jdb = new Jdb(this);
        display("Starting jdb launching local debuggee");
        jdb.launch(jdbCmdArgs);

        if (classToExecute != null)
            jdb.waitForMessage(0, JDB_STARTED);
//        jdb.waitForPrompt(0, false);

    }

    /**
     * Run test in local mode using attaching connector.
     */
    private void localLaunchAndAttach
       (String[] jdbCmdArgs, String classToExecute) throws IOException {

        debuggee = new LocalLaunchedDebuggee(this);
        String address = makeTransportAddress();
        String[] javaCmdArgs = makeCommandLineArgs(classToExecute, address);
        debuggee.launch(javaCmdArgs);

        display("Start jdb attaching to local debuggee");
        jdb = Jdb.startAttachingJdb (this, jdbCmdArgs, JDB_STARTED);
//        jdb.waitForPrompt(0, false);
    }

    /**
     * Run test in local mode using listening connector.
     */
    private void localLaunchAndListen
       (String[] jdbCmdArgs, String classToExecute) throws IOException {

        jdb = new Jdb(this);
        display("Starting jdb listening to local debuggee");
        jdb.launch(jdbCmdArgs);
        String address = jdb.waitForListeningJdb();
        display("Listening address found: " + address);

        debuggee = new LocalLaunchedDebuggee(this);
        String[] javaCmdArgs = makeCommandLineArgs(classToExecute, address);
        debuggee.launch(javaCmdArgs);

//        jdb.waitForPrompt(0, false);
    }

    /**
     * Run test in remote mode using attaching connector.
     */
    private void remoteLaunchAndAttach
       (String[] jdbCmdArgs, String classToExecute) throws IOException {

        debuggee = new RemoteLaunchedDebuggee(this);
        String address = makeTransportAddress();
        String[] javaCmdArgs = makeCommandLineArgs(classToExecute, address);
        try {
            debuggee.launch(javaCmdArgs);
        } catch (IOException e) {
            throw new Failure("Caught exception while launching debuggee VM process:\n\t"
                            + e);
        };

        display("Start jdb attaching to remote debuggee");
        jdb = Jdb.startAttachingJdb (this, jdbCmdArgs, JDB_STARTED);
//        jdb.waitForPrompt(0, false);
    }

    /**
     * Run test in remote mode using listening connector.
     */
    private void remoteLaunchAndListen
       (String[] jdbCmdArgs, String classToExecute) throws IOException {

        jdb = new Jdb(this);
        display("Starting jdb listening to remote debuggee");
        jdb.launch(jdbCmdArgs);
        String address = jdb.waitForListeningJdb();
        display("Listening address found: " + address);

        debuggee = new RemoteLaunchedDebuggee(this);
        String[] javaCmdArgs = makeCommandLineArgs(classToExecute);
        try {
            debuggee.launch(javaCmdArgs);
        } catch (IOException e) {
            throw new Failure("Caught exception while launching debuggee VM process:\n\t"
                            + e);
        };

        jdb.waitForMessage(0, JDB_STARTED);
//        jdb.waitForPrompt(0, false);
    }

} // End of Launcher