/**
 * 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.server.ejbd;

import junit.framework.TestCase;
import org.apache.openejb.server.DiscoveryAgent;
import org.apache.openejb.server.DiscoveryListener;
import org.apache.openejb.server.ServerService;
import org.apache.openejb.server.ServerServiceFilter;
import org.apache.openejb.server.ServiceDaemon;
import org.apache.openejb.server.ServiceException;

import javax.ejb.Remote;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.Socket;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * @version $Rev$ $Date$
 */
public class ClientMulticastDiscoveryTest extends TestCase {

    private DiscoveryAgent agent;

    public void test() throws Exception {
        /*
        System.setProperty("openejb.client.requestretry", "true");
        Properties initProps = new Properties();
        initProps.setProperty("openejb.deployments.classpath.include", "");
        initProps.setProperty("openejb.deployments.classpath.filter.descriptors", "true");
        OpenEJB.init(initProps, new ServerFederation());

        Assembler assembler = SystemInstance.get().getComponent(Assembler.class);
        ConfigurationFactory config = new ConfigurationFactory();

        EjbJar ejbJar = new EjbJar();
        ejbJar.addEnterpriseBean(new StatelessBean(Target.class));
        assembler.createApplication(config.configureApplication(ejbJar));


        MulticastDiscoveryAgent multicastAgent = new MulticastDiscoveryAgent();
        DiscoveryRegistry registry = new DiscoveryRegistry(multicastAgent);
        SystemInstance.get().setComponent(DiscoveryRegistry.class, registry);
        this.agent = registry;
        multicastAgent.start();


        ServerService red = server(Host.RED);
        ServerService blue = server(Host.BLUE);
        ServerService green = server(Host.GREEN);

        red.start();

        Properties props = new Properties();
        props.put("java.naming.factory.initial", "org.apache.openejb.client.RemoteInitialContextFactory");

        props.put("java.naming.provider.url", "multicast://239.255.2.3:6142");

        Context context = new InitialContext(props);
        TargetRemote target = (TargetRemote) context.lookup("TargetRemote");

        assertEquals(Host.RED, target.getHost());

        red.stop();

        // Blue is not in the cluster list as it was started
        // after the last invocation list was sent.
        // We're testing that the client can fail all
        // the way out of the cluster list and back to
        // the original multicast://239.255.2.3:6142 uri.
        blue.start();

        assertEquals(Host.BLUE, target.getHost());

        blue.stop();

        try {
            target.getHost();
        } catch (EJBException e) {
            // pass..  no server available
        }
    	*/
    }

    private ServerService server(final Host host) throws Exception {
        ServerService server = new EjbServer();

        server = new HostFilter(server, host);

        server = new ServiceDaemon(server, 0, "localhost");

        server = new AgentFilter(server, agent, host);

        server.init(new Properties());

        return server;
    }

    // Simple single-threaded version, way easier on testing
    public static class TestAgent implements DiscoveryAgent {

        private final List<DiscoveryListener> listeners = new ArrayList<DiscoveryListener>();

        @Override
        public void registerService(final URI serviceUri) throws IOException {
            for (final DiscoveryListener listener : listeners) {
                listener.serviceAdded(serviceUri);
            }
        }

        @Override
        public void reportFailed(final URI serviceUri) throws IOException {
        }

        @Override
        public void setDiscoveryListener(final DiscoveryListener listener) {
            listeners.add(listener);
        }

        @Override
        public void unregisterService(final URI serviceUri) throws IOException {
            for (final DiscoveryListener listener : listeners) {
                listener.serviceRemoved(serviceUri);
            }
        }

    }

    public static enum Host {
        RED,
        BLUE,
        GREEN
    }

    public static final ThreadLocal<Host> host = new ThreadLocal<Host>();

    public static Socket serverSideSocket;

    public static class AgentFilter extends ServerServiceFilter {

        private final Host host;
        private final DiscoveryAgent agent;
        private URI uri;

        public AgentFilter(final ServerService service, final DiscoveryAgent agent, final Host host) {
            super(service);
            this.agent = agent;
            this.host = host;
        }

        @Override
        public void start() throws ServiceException {
            super.start();
            try {
                uri = new URI("ejb:ejbd://localhost:" + getPort() + "/" + host);
                agent.registerService(uri);
            } catch (Exception e) {
                throw new ServiceException(e);
            }
        }

        @Override
        public void stop() throws ServiceException {
            super.stop();
            try {
                agent.unregisterService(uri);
            } catch (Exception e) {
                throw new ServiceException(e);
            }
        }
    }

    public static class HostFilter extends ServerServiceFilter {

        private final Host me;

        public HostFilter(final ServerService service, final Host me) {
            super(service);
            this.me = me;
        }

        @Override
        public void service(final InputStream in, final OutputStream out) throws ServiceException, IOException {
            try {
                host.set(me);
                super.service(in, out);
            } finally {
                host.remove();
            }
        }

        @Override
        public void service(final Socket socket) throws ServiceException, IOException {
            serverSideSocket = socket;
            try {
                host.set(me);
                super.service(socket);
            } finally {
                host.remove();
            }
        }
    }

    public static class Target implements TargetRemote {

        @Override
        public Host getHost() {
            return host.get();
        }

        @Override
        public Wrapper kill(final Host... hosts) {
            final Host host = getHost();
            for (final Host h : hosts) {
                if (h == host) {
                    return new Wrapper(host, serverSideSocket);
                }
            }
            return new Wrapper(host, null);
        }
    }

    public static class Wrapper implements Serializable {

        private static final long serialVersionUID = 5812936504765768722L;
        transient Socket socket;
        private final Host host;

        public Wrapper(final Host host, final Socket socket) {
            this.host = host;
            this.socket = socket;
        }

        private void writeObject(final java.io.ObjectOutputStream out) throws IOException {
            out.defaultWriteObject();
            if (socket != null) {
                socket.close();
            }
        }
    }

    @Remote
    public static interface TargetRemote {

        Host getHost();

        Wrapper kill(Host... hosts);
    }
}
