/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.openejb.test;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Properties;

public class RemoteTestServer implements org.apache.openejb.test.TestServer {

    static {
        System.setProperty("noBanner", "true");
    }

    private boolean serverRunning = false;
    private Process serverProcess = null;
    private Properties properties;

    @Override
    public void init(final Properties props) {
        properties = props;
        if (props.contains("java.naming.security.principal"))
            throw new IllegalArgumentException("Not allowed 'java.naming.security.principal'");
//        props.put("test.server.class","org.apache.openejb.test.RemoteTestServer");
        props.put("java.naming.factory.initial", "org.apache.openejb.client.RemoteInitialContextFactory");
        props.put("java.naming.provider.url", "127.0.0.1:4201");
//        props.put("java.naming.security.principal", "testuser");
//        props.put("java.naming.security.credentials", "testpassword");
    }

    @Override
    public synchronized void start() {
        if (!connect()) {
            try {
                System.out.println("[] START SERVER");

                final String openejbHome = System.getProperty("openejb.home");

                final File home = new File(openejbHome);
                System.out.println("OPENEJB_HOME = " + home.getAbsolutePath());
                final String systemInfo = "Java " + System.getProperty("java.version") + "; " + System.getProperty("os.name") + "/" + System.getProperty("os.version");
                System.out.println("SYSTEM_INFO  = " + systemInfo);


                File openejbJar = null;
                final File lib = new File(home, "lib");
                final File[] files = lib.listFiles();
                if (null != files) {
                    for (int i = 0; i < files.length && openejbJar == null; i++) {
                        final File file = files[i];
                        if (file.getName().startsWith("openejb-core") && file.getName().endsWith("jar")) {
                            openejbJar = file;
                        }
                    }
                }

                if (openejbJar == null) {
                    throw new IllegalStateException("Cannot find the openejb-core jar in " + lib.getAbsolutePath());
                }

                //File openejbJar = new File(lib, "openejb-core-" + version + ".jar");

                //Not really required here for exec, but as a reminder that we run on all platforms
                final boolean isWindows = System.getProperty("os.name").toLowerCase().contains("win");

                //DMB: If you don't use an array, you get problems with jar paths containing spaces
                // the command won't parse correctly
                final String[] args = {(isWindows ? "java.exe" : "java"), "-jar", openejbJar.getAbsolutePath(), "start"};
                this.serverProcess = Runtime.getRuntime().exec(args);

                // Pipe the processes STDOUT to ours
                final InputStream out = serverProcess.getInputStream();
                final Thread serverOut = new Thread(new Pipe(out, System.out));

                serverOut.setDaemon(true);
                serverOut.start();

                // Pipe the processes STDERR to ours
                final InputStream err = serverProcess.getErrorStream();
                final Thread serverErr = new Thread(new Pipe(err, System.err));

                serverErr.setDaemon(true);
                serverErr.start();

                connect(10);

                serverRunning = true;

            } catch (final Exception e) {
                throw (RuntimeException) new RuntimeException("Cannot start the server.").initCause(e);
            }
        }
    }

    @Override
    public synchronized void stop() {
        if (serverRunning) {
            try {
                System.out.println("[] STOP SERVER");

                final Socket socket = new Socket("localhost", 4200);
                final OutputStream out = socket.getOutputStream();

                out.write("Stop".getBytes());

            } catch (final Exception e) {
                e.printStackTrace();
            }
        }

        if (null != serverProcess) {
            try {
                serverProcess.waitFor();
            } catch (final Exception e) {
                //Ignore
            }
        }
    }

    @Override
    public Properties getContextEnvironment() {
        return (Properties) properties.clone();
    }

    private boolean connect() {
        return connect(1);
    }

    private boolean connect(int tries) {
        //System.out.println("CONNECT "+ tries);
        OutputStream out = null;
        try {
            final Socket socket = new Socket("localhost", 4200);
            out = socket.getOutputStream();
        } catch (final Exception e) {
            //System.out.println(e.getMessage());
            if (tries < 2) {
                return false;
            } else {
                try {
                    Thread.sleep(2000);
                } catch (final Exception e2) {
                    e.printStackTrace();
                }
                return connect(--tries);
            }
        } finally {
            if (null != out) {
                try {
                    out.close();
                } catch (final Throwable e) {
                    //Ignore
                }
            }
        }

        return true;
    }

    private static final class Pipe implements Runnable {
        private final InputStream is;
        private final OutputStream out;

        private Pipe(final InputStream is, final OutputStream out) {
            super();
            this.is = is;
            this.out = out;
        }

        @Override
        public void run() {
            try {
                int i = is.read();
                out.write(i);

                while (i != -1) {
                    i = is.read();
                    out.write(i);
                }

            } catch (final Exception e) {
                e.printStackTrace();
            }
        }
    }
}
