/*
 *        JacORB  - a free Java ORB
 *
 *   Copyright (C) 1997-2006 The JacORB project.
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Library General Public
 *   License as published by the Free Software Foundation; either
 *   version 2 of the License, or (at your option) any later version.
 *
 *   This library 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
 *   Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this library; if not, write to the Free
 *   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package org.jacorb.test.harness;

import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import org.jacorb.test.harness.launch.Launcher;

/**
 * @author Alphonse Bendt
 * @author Nick Cross
 */
public class ServerSetup
{
    private static class ProcessShutdown extends Thread
    {
        // only hold a weak reference to the process to
        // allow it to be gc'ed
        private final WeakReference<Process> processRef;

        public ProcessShutdown(Process process)
        {
            processRef = new WeakReference<Process>(process);
        }

        @Override
        public void run()
        {
            Process process = processRef.get();
            if (process != null)
            {
                try
                {
                    process.destroy();
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
        }
    }

    private final Properties serverOrbProperties = new Properties();

    private final String servantName;
    private final String testServer;

    private Process serverProcess;
    private StreamListener outListener, errListener;
    private String serverIOR;

    protected String outName = "OUT";
    protected String errName = "ERR";

    private final List<String> serverArgs = new ArrayList<String>();

    private String serverIORFailedMesg;


    /**
     * Server setup with explicit server class/servant name and optional properties.
     * @param testServer
     * @param servantName
     * @param optionalProperties
     * @throws IOException
     */
    public ServerSetup(String testServer, String servantName, Properties optionalProperties) throws IOException
    {
        this(testServer, servantName, null , optionalProperties);
    }

    public ServerSetup(String testServer, String servantName, String[] testServantArgs, Properties optionalProperties) throws IOException
    {
        this.testServer = getTestServer(testServer);
        this.servantName = servantName;

        serverArgs.add(servantName);

        if (TestUtils.verbose)
        {
            serverOrbProperties.setProperty("jacorb.log.default.verbosity", "4");
        }
        else
        {
            serverOrbProperties.setProperty("jacorb.log.default.verbosity", "0");
        }
        if (TestUtils.isSSLEnabled)
        {
            // In this case we have been configured to run all the tests
            // in SSL mode. For simplicity, we will use the demo/ssl keystore
            // and properties (partly to ensure that they always work)
            Properties serverProps = CommonSetup.loadSSLProps("jsse_server_props", "jsse_server_ks");

            serverOrbProperties.putAll(serverProps);
        }

        if (optionalProperties != null)
        {
            serverOrbProperties.putAll(optionalProperties);
        }

        for (int i = 0; testServantArgs != null && i < testServantArgs.length; i++)
        {
            serverArgs.add(testServantArgs[i]);
        }
    }

    /**
     * Default server setup
     * @param servantName
     * @throws IOException
     */
    public ServerSetup(String servantName) throws IOException
    {
        this(null, servantName, null);
    }

    private String getTestServer(String optionalTestServer)
    {
        if (optionalTestServer == null)
        {
            return "org.jacorb.test.harness.TestServer";
        }
        return optionalTestServer;
    }

    public void setUp()
    {
        Properties serverProperties = new Properties();
        serverProperties.setProperty("org.omg.CORBA.ORBClass", "org.jacorb.orb.ORB");
        serverProperties.setProperty("org.omg.CORBA.ORBSingletonClass", "org.jacorb.orb.ORBSingleton");
        serverProperties.put ("jacorb.implname", servantName);
        serverProperties.putAll (serverOrbProperties);

        final String prefix = "jacorb.test.serverproperty.";
        final Iterator<String> i = System.getProperties().stringPropertyNames().iterator();

        while(i.hasNext())
        {
            String key = i.next();

            if (!key.startsWith(prefix))
            {
                continue;
            }

            String value = System.getProperty(key);

            String propName = key.substring(prefix.length());

            serverProperties.setProperty(propName, value);
        }

        final Launcher launcher = getLauncher(System.getProperty("java.class.path"),
                                        serverProperties,
                                        getTestServerMain(),
                                        serverArgs.toArray(new String[serverArgs.size()]));

        serverProcess = launcher.launch();

        // add a shutdown hook to ensure that the server process
        // is shutdown even if this JVM is going down unexpectedly
        Runtime.getRuntime().addShutdownHook(new ProcessShutdown(serverProcess));

        outListener = new StreamListener (serverProcess.getInputStream(), servantName + '-' + outName);
        errListener = new StreamListener (serverProcess.getErrorStream(), servantName + '-' + errName);
        outListener.start();
        errListener.start();
        serverIOR = outListener.getIOR(TestUtils.timeout);
    }

    public void tearDown() throws Exception
    {
        if (serverProcess != null)
        {
            outListener.setDestroyed();
            errListener.setDestroyed();
            outListener = null;
            errListener = null;

            serverProcess.destroy();
            serverProcess.waitFor();
            serverProcess = null;

            serverIOR = null;
        }
    }

    public String getServerIOR()
    {
        if (serverIOR == null)
        {
            if (serverIORFailedMesg == null)
            {
                String exc = errListener.getException(1000);

                String details = dumpStreamListener();

                serverIORFailedMesg = "could not access IOR for Server.\nServant: " + servantName + "\nTimeout: " + TestUtils.timeout + " millis.\nThis maybe caused by: " + exc + '\n' + details;
            }
            fail(serverIORFailedMesg);
        }

        return serverIOR;
    }

    public String getTestServerMain()
    {
        return testServer;
    }

    private String dumpStreamListener()
    {
        StringBuffer details = new StringBuffer();
        details.append(outListener.toString());
        details.append(errListener.toString());
        return details.toString();
    }

    /**
     * Returns a launcher for the specified JacORB version.
     * If coverage is true, sets up the launcher to that
     * coverage information will be gathered.
     * @param classpath
     * @param properties
     * @param mainClass
     * @param processArgs
     */
    private Launcher getLauncher (String classpath,
                                 Properties properties,
                                 String mainClass,
                                 String[] processArgs)
    {
        String home = null;

        try
        {
            home = locateHome(properties);
        }
        catch(Exception e)
        {
            TestUtils.getLogger().debug("unable to locate JacORB home. classpath will be only be set using the System property java.class.path: " + e.getMessage());
        }

        final Properties props = new Properties();

        if (properties != null)
        {
            props.putAll(properties);
        }

        Iterator<Object> it = System.getProperties().keySet().iterator();
        while (it.hasNext())
        {
            String key = (String)it.next();
            if (key.startsWith("jacorb.test"))
            {
                props.put(key, System.getProperty(key));
            }
        }

        // Extract any VM arguments e.g. java agent (used by coverage) etc and pass
        // them to the server laucher so it uses the same parameters.
        RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
        List<String> jvmArgs = new ArrayList<String>();
        Iterator<String> s = runtimeMxBean.getInputArguments().iterator();
        while (s.hasNext())
        {
            String jvmArg = s.next();
            // Don't pass Xboot or -D - they are handled separately.
            if ( ! jvmArg.startsWith("-Xbootclasspath") && ! jvmArg.startsWith("-D"))
            {
                jvmArgs.add(jvmArg);
            }
        }

        try
        {
            Launcher launcher = new Launcher();

            launcher.setClasspath(classpath);
            launcher.setMainClass(mainClass);
            launcher.setArgs(processArgs);
            launcher.setVmArgs(jvmArgs);

            if (home != null)
            {
                launcher.setJacorbHome(new File(home));
            }
            launcher.setProperties(props);

            launcher.init();

            return launcher;
        }
        catch (Exception e)
        {
            StringWriter out = new StringWriter();
            e.printStackTrace(new PrintWriter(out));
            throw new IllegalArgumentException(out.toString());
        }
    }

    private String locateHome(Properties props)
    {
        String jacorbHome = props.getProperty("jacorb.home");

        if (jacorbHome == null)
        {
            jacorbHome = System.getProperty("jacorb.home");
        }

        if (jacorbHome != null)
        {
            return jacorbHome;
        }

        String testHome = props.getProperty("jacorb.test.home");

        if (testHome == null)
        {
            testHome = System.getProperty("jacorb.test.home");
        }

        if (testHome == null)
        {
            testHome = TestUtils.testHome();
        }

        if (testHome == null)
        {
            throw new RuntimeException("cannot determine jacorb.test.home");
        }

        return new File(testHome).getParentFile().getParentFile().toString();
    }
}
