/*
 * Copyright (c) 2015 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.jme3.network.service.rpc;

import com.jme3.network.HostedConnection;
import com.jme3.network.Server;
import com.jme3.network.serializing.Serializer;
import com.jme3.network.service.AbstractHostedConnectionService;
import com.jme3.network.util.SessionDataDelegator;
import com.jme3.network.service.HostedServiceManager;
import com.jme3.network.service.rpc.msg.RpcCallMessage;
import com.jme3.network.service.rpc.msg.RpcResponseMessage;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 *  RPC service that can be added to a network Server to
 *  add RPC send/receive capabilities.  For a particular
 *  HostedConnection, Remote procedure calls can be made to the 
 *  associated Client and responses retrieved.  Any remote procedure 
 *  calls that the Client performs for this connection will be 
 *  received by this service and delegated to the appropriate RpcHandlers.
 *
 *  Note: it can be dangerous for a server to perform synchronous
 *  RPC calls to a client but especially so if not done as part
 *  of the response to some other message.  ie: iterating over all
 *  or some HostedConnections to perform synchronous RPC calls
 *  will be slow and potentially block the server's threads in ways
 *  that can cause deadlocks or odd contention. 
 *
 *  @author    Paul Speed
 */
public class RpcHostedService extends AbstractHostedConnectionService {

    private static final String ATTRIBUTE_NAME = "rpcSession";

    static final Logger log = Logger.getLogger(RpcHostedService.class.getName());

    private SessionDataDelegator delegator;

    /**
     *  Creates a new RPC host service that can be registered
     *  with the Network server and will automatically 'host'
     *  RPC services and each new network connection.
     */
    public RpcHostedService() {
        this(true);
    }
    
    /**
     *  Creates a new RPC host service that can be registered
     *  with the Network server and will optionally 'host'
     *  RPC services and each new network connection depending
     *  on the specified 'autoHost' flag.
     */
    public RpcHostedService( boolean autoHost ) {
        super(autoHost);
        
        // This works for me... has to be different in
        // the general case
        Serializer.registerClasses(RpcCallMessage.class, RpcResponseMessage.class);
    }

    /**
     *  Used internally to setup the message delegator that will
     *  handle HostedConnection specific messages and forward them
     *  to that connection's RpcConnection.
     */
    @Override
    protected void onInitialize( HostedServiceManager serviceManager ) {
        Server server = serviceManager.getServer();
         
        // A general listener for forwarding the messages
        // to the client-specific handler
        this.delegator = new SessionDataDelegator(RpcConnection.class, 
                                                  ATTRIBUTE_NAME,
                                                  true);
        server.addMessageListener(delegator, delegator.getMessageTypes());

        if( log.isLoggable(Level.FINEST) ) {
            log.log(Level.FINEST, "Registered delegator for message types:{0}", Arrays.asList(delegator.getMessageTypes()));
        }
    }

    /**
     *  Retrieves the RpcConnection for the specified HostedConnection
     *  if that HostedConnection has had RPC services started using
     *  startHostingOnConnection() (or via autohosting).  Returns null
     *  if the connection currently doesn't have RPC hosting services
     *  attached.
     */
    public RpcConnection getRpcConnection( HostedConnection hc ) {
        return hc.getAttribute(ATTRIBUTE_NAME);
    }

    /**
     *  Sets up RPC hosting services for the hosted connection allowing
     *  getRpcConnection() to return a valid RPC connection object.
     *  This method is called automatically for all new connections if
     *  autohost is set to true.
     */
    @Override
    public void startHostingOnConnection( HostedConnection hc ) {
        if( log.isLoggable(Level.FINEST) ) {
            log.log(Level.FINEST, "startHostingOnConnection:{0}", hc);
        }
        hc.setAttribute(ATTRIBUTE_NAME, new RpcConnection(hc));
    }

    /**
     *  Removes any RPC hosting services associated with the specified
     *  connection.  Calls to getRpcConnection() will return null for
     *  this connection.  The connection's RpcConnection is also closed,
     *  releasing any waiting synchronous calls with a "Connection closing"
     *  error.
     *  This method is called automatically for all leaving connections if
     *  autohost is set to true.
     */
    @Override
    public void stopHostingOnConnection( HostedConnection hc ) {
        RpcConnection rpc = hc.getAttribute(ATTRIBUTE_NAME);
        if( rpc == null ) {
            return;
        }
        if( log.isLoggable(Level.FINEST) ) {
            log.log(Level.FINEST, "stopHostingOnConnection:{0}", hc);
        }
        hc.setAttribute(ATTRIBUTE_NAME, null);
        rpc.close();
    }

    /**
     *  Used internally to remove the message delegator from the
     *  server.
     */
    @Override
    public void terminate(HostedServiceManager serviceManager) {
        Server server = serviceManager.getServer();
        server.removeMessageListener(delegator, delegator.getMessageTypes());
    }

}

