/*************************************************************************
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright 2000, 2010 Oracle and/or its affiliates.
*
* OpenOffice.org - a multi-platform office productivity suite
*
* This file is part of OpenOffice.org.
*
* OpenOffice.org is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 3
* only, as published by the Free Software Foundation.
*
* OpenOffice.org 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 Lesser General Public License version 3 for more details
* (a copy is included in the LICENSE file that accompanied this code).
*
* You should have received a copy of the GNU Lesser General Public License
* version 3 along with OpenOffice.org.  If not, see
* <http://www.openoffice.org/license.html>
* for a copy of the LGPLv3 License.
************************************************************************/

package org.openoffice.test;

import com.sun.star.bridge.UnoUrlResolver;
import com.sun.star.bridge.XUnoUrlResolver;
import com.sun.star.comp.helper.Bootstrap;
import com.sun.star.connection.NoConnectException;
import com.sun.star.frame.XDesktop;
import com.sun.star.lang.DisposedException;
import com.sun.star.lang.XMultiComponentFactory;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.XComponentContext;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.Map;
import java.util.UUID;
import static org.junit.Assert.*;

/** Start up and shut down an OOo instance.

    Details about the OOo instance are tunneled in via
    org.openoffice.test.arg.... system properties.
*/

public final class OfficeConnection {
    /** Start up an OOo instance.
    */
    public void setUp() throws Exception {
        String sofficeArg = Argument.get("soffice");
        if (sofficeArg.startsWith("path:")) {
            String headless_flags = "";
            try {
                if(Argument.get("headless").length() > 0) {
                    headless_flags = "--headless";
                }
            } catch(Exception e) {}
            description = "pipe,name=oootest" + UUID.randomUUID();
            ProcessBuilder pb = new ProcessBuilder(
                sofficeArg.substring("path:".length()), "--quickstart=no",
                "--nofirststartwizard", "--norestore",
                "--accept=" + description + ";urp",
                "-env:UserInstallation=" + Argument.get("user"),
                "-env:UNO_JAVA_JFW_ENV_JREHOME=true",
                headless_flags);
            String envArg = Argument.get("env");
            if (envArg != null) {
                Map<String, String> env = pb.environment();
                int i = envArg.indexOf("=");
                if (i == -1) {
                    env.remove(envArg);
                } else {
                    env.put(envArg.substring(0, i), envArg.substring(i + 1));
                }
            }
            process = pb.start();
            outForward = new Forward(process.getInputStream(), System.out);
            outForward.start();
            errForward = new Forward(process.getErrorStream(), System.err);
            errForward.start();
        } else if (sofficeArg.startsWith("connect:")) {
            description = sofficeArg.substring("connect:".length());
        } else {
            fail(
                "\"soffice\" argument \"" + sofficeArg +
                " starts with neither \"path:\" nor \"connect:\"");
        }
        XUnoUrlResolver resolver = UnoUrlResolver.create(
            Bootstrap.createInitialComponentContext(null));
        for (;;) {
            try {
                context = UnoRuntime.queryInterface(
                    XComponentContext.class,
                    resolver.resolve(
                        "uno:" + description +
                        ";urp;StarOffice.ComponentContext"));
                break;
            } catch (NoConnectException e) {}
            if (process != null) {
                assertNull(waitForProcess(process, 1000)); // 1 sec
            }
        }
    }

    /** Shut down the OOo instance.
    */
    public void tearDown()
        throws InterruptedException, com.sun.star.uno.Exception
    {
        boolean desktopTerminated = true;
        if (process != null) {
            if (context != null) {
                XMultiComponentFactory factory = context.getServiceManager();
                assertNotNull(factory);
                XDesktop desktop = UnoRuntime.queryInterface(
                    XDesktop.class,
                    factory.createInstanceWithContext(
                        "com.sun.star.frame.Desktop", context));
                context = null;
                try {
                    desktopTerminated = desktop.terminate();
                } catch (DisposedException e) {}
                    // it appears that DisposedExceptions can already happen
                    // while receiving the response of the terminate call
                desktop = null;
            } else {
                process.destroy();
            }
        }
        int code = 0;
        if (process != null) {
            code = process.waitFor();
        }
        boolean outTerminated = outForward == null || outForward.terminated();
        boolean errTerminated = errForward == null || errForward.terminated();
        assertTrue(desktopTerminated);
        assertEquals(0, code);
        assertTrue(outTerminated);
        assertTrue(errTerminated);
    }

    /** Obtain the component context of the running OOo instance.
    */
    public XComponentContext getComponentContext() {
        return context;
    }

    //TODO: get rid of this hack for legacy qa/unoapi tests
    public String getDescription() {
        return description;
    }

    private static Integer waitForProcess(Process process, final long millis)
        throws InterruptedException
    {
        final Thread t1 = Thread.currentThread();
        Thread t2 = new Thread("waitForProcess") {
                public void run() {
                    try {
                        Thread.currentThread().sleep(millis);
                    } catch (InterruptedException e) {}
                    t1.interrupt();
                }
            };
        boolean old = Thread.interrupted();
            // clear interrupted status, get old status
        t2.start();
        int n = 0;
        boolean done = false;
        try {
            n = process.waitFor();
            done = true;
        } catch (InterruptedException e) {}
        t2.interrupt();
        try {
            t2.join();
        } catch (InterruptedException e) {
            t2.join();
        }
        Thread.interrupted(); // clear interrupted status
        if (old) {
            t1.interrupt(); // reset old status
        }
        return done ? new Integer(n) : null;
    }

    private static final class Forward extends Thread {
        public Forward(InputStream in, PrintStream out) {
            super("process output forwarder");
            this.in = in;
            this.out = out;
        }

        public void run() {
            for (;;) {
                byte[] buf = new byte[1024];
                int n;
                try {
                    n = in.read(buf);
                } catch (IOException e) {
                    throw new RuntimeException("wrapping", e);
                }
                if (n == -1) {
                    break;
                }
                out.write(buf, 0, n);
            }
            done = true;
        }

        public boolean terminated() throws InterruptedException {
            join();
            return done;
        }

        private final InputStream in;
        private final PrintStream out;
        private boolean done = false;
    }

    private String description;
    private Process process = null;
    private Forward outForward = null;
    private Forward errForward = null;
    private XComponentContext context = null;
}
