public class

JunctionMaker

extends Object
/*
 * Copyright (C) 2010 Stanford University
 *
 * Licensed 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 edu.stanford.junction;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

import org.json.JSONObject;


import edu.stanford.junction.api.activity.ActivityScript;
import edu.stanford.junction.api.activity.Cast;
import edu.stanford.junction.api.activity.JunctionActor;
import edu.stanford.junction.api.messaging.MessageHeader;
import edu.stanford.junction.JunctionException;
import edu.stanford.junction.provider.JunctionProvider;
import edu.stanford.junction.provider.jvm.JVMSwitchboardConfig;
import edu.stanford.junction.provider.jx.JXSwitchboardConfig;
import edu.stanford.junction.provider.xmpp.XMPPSwitchboardConfig;
import edu.stanford.junction.provider.irc.IRCSwitchboardConfig;

/**
 * This class creates {@link Junction} objects, binding {@link JunctionActor}
 * objects to an activity.
 */
public class JunctionMaker {
	public static final String DIRECTOR_ACTIVITY = "edu.stanford.junction.director";
	public static final URI  CASTING_DIRECTOR = URI.create("junction://jx-director-local/cast");
	protected JunctionProvider mProvider;

	public static final URI SWITCHBOARD_ACTIVITY = URI.create("junction://sb.openjunction.org/switchboard");
	

        /**
         * Returns a new {@link JunctionMaker} object with the type specified
         * by the given {@link SwitchboardConfig} object.
         */
	public static JunctionMaker getInstance(SwitchboardConfig switchboardConfig) {
		// TODO: map config to maker?
		JunctionMaker maker = new JunctionMaker();
		maker.mProvider = maker.getProvider(switchboardConfig);
		maker.mProvider.setJunctionMaker(maker);
		return maker;
	}
	
	public static String getSessionIDFromURI(URI uri) {
		try {
			return uri.getPath().substring(1);
		} catch (Exception e) {
			return null;
		}
	}
	
	public JunctionMaker() {

	}

	/**
	 * Binds a {@link JunctionActor} to a session URI, using the default
	 * switchboard for that URI.
	 */
	public static Junction bind(URI uri, JunctionActor actor) throws JunctionException {
		return JunctionMaker.getInstance(JunctionMaker.getDefaultSwitchboardConfig(uri)).newJunction(uri, actor);
	}

	/**
	 * Binds a {@link JunctionActor} to a randomly generated sesssion, using the default
	 * switchboard.
	 */
	public static Junction bind(JunctionActor actor) throws JunctionException {
		// default
		SwitchboardConfig config = new XMPPSwitchboardConfig("prpl.stanford.edu");
		JunctionMaker maker = JunctionMaker.getInstance(config);
		return maker.newJunction(maker.generateSessionUri(), actor);
	}

	protected JunctionProvider getProvider(SwitchboardConfig switchboardConfig) {
		if (switchboardConfig instanceof XMPPSwitchboardConfig) {
			return new edu.stanford.junction.provider.xmpp.JunctionProvider((XMPPSwitchboardConfig)switchboardConfig);
		} else if (switchboardConfig instanceof JVMSwitchboardConfig){
			return new edu.stanford.junction.provider.jvm.JunctionProvider((JVMSwitchboardConfig)switchboardConfig);
		} else if (switchboardConfig instanceof JXSwitchboardConfig) {
			return new edu.stanford.junction.provider.jx.JunctionProvider((JXSwitchboardConfig)switchboardConfig);
		} else if (switchboardConfig instanceof IRCSwitchboardConfig) {
			return new edu.stanford.junction.provider.irc.JunctionProvider((IRCSwitchboardConfig)switchboardConfig);
		} else {
			// Unknown implementation;.
			return null;
		}
	}
	
	/*
	 * JunctionMaker has three functions:
	 * (1) Connect an Actor to a Junction
	 * (2) Retrieve an activity's script given a URI
	 * (3) Support various invitation mechanisms (often platform-specific)
	 */

	
	/**
	 * This method has been deprecated. Please see 
	 * {@link newJunction(URI, ActivityScript, JunctionActor)}
	 */
	public Junction newJunction(URI uri, JunctionActor actor) throws JunctionException {
		return mProvider.newJunction(uri, null, actor);
	}
	
	@Deprecated
	public Junction newJunction(URI uri, ActivityScript script, JunctionActor actor) throws JunctionException{
		return mProvider.newJunction(uri, script, actor);
	}
	
	@Deprecated
	public Junction newJunction(ActivityScript desc, JunctionActor actor) throws JunctionException{
		URI sessionUri;
		if (desc == null) {
			desc = new ActivityScript();
		}
		if (desc.getSessionID() == null) {
			sessionUri = mProvider.generateSessionUri();
			desc.isActivityCreator(true);
			desc.setUri(sessionUri);
		} else {
			// TODO: get rid of this.
			try {
				sessionUri = new URI("junction://" + desc.getHost() + "/" + desc.getSessionID());
			} catch (URISyntaxException e) {
				throw new IllegalArgumentException("Bad URI from activity script");
			}
		}

		return mProvider.newJunction(sessionUri, desc, actor);
	}
	
	public URI generateSessionUri() {
		return mProvider.generateSessionUri();
	}
	
	/**
	 * Creates a new Junction and requests the casting of various roles.
	 * There is no guarantee if and when the roles will be filled.
	 * There may or may not be user interaction at the casting director.
	 * 
	 * @param desc
	 * @param actor
	 * @param support
	 * @return
	 */
	public Junction newJunction(ActivityScript desc, JunctionActor actor, Cast support) throws JunctionException{
		Junction jx = newJunction(desc, actor);
		//System.out.println("creating activity " + desc.getJSON());
		int size=support.size();
		System.out.println("going to cast " + size + " roles");
		for (int i=0;i<size;i++){
			if (support.getDirector(i) != null) {
				//System.out.println("Casting role " + support.getRole(i) + " on " + support.getDirector(i));
				URI invitationURI = jx.getInvitationURI(support.getRole(i));
				this.castActor(support.getDirector(i), invitationURI);
			}
		}
		
		return jx;
	}
	
	/**
	 * Sends a message to an activity. In the
	 * unoptimized case, joins an activity, sends
	 * a message, and leaves.
	 * 
	 * @param activitySession
	 * @param msg
	 */
	public void sendMessageToActivity(URI activitySession, JSONObject msg) throws JunctionException{
		mProvider.sendMessageToActivity(activitySession,msg);
	}
	
	
	public ActivityScript getActivityScript(URI uri) throws JunctionException{
		return mProvider.getActivityScript(uri);
	}
	
	/**
	 * Sends a request to a Director activity
	 * to cast an actor to accept a given invitation.
	 * 
	 * @param directorURI The director listening for requests
	 * @param invitationURI The activity to join (potentially including role information)
	 */
	public void castActor(final URI directorURI, final URI invitationURI) throws JunctionException{
		JunctionActor actor = new JunctionActor("inviter") {
				@Override
				public void onActivityJoin() {
					JSONObject invitation = new JSONObject();
					try {
						invitation.put("action","cast");
						invitation.put("activity", invitationURI.toString());
						getJunction().sendMessageToSession(invitation);
					} catch (Exception e) {
						e.printStackTrace(System.err);
					}

					leave();
				}
			
				@Override
				public void onMessageReceived(MessageHeader header,
											  JSONObject message) {
				
				}
			};
		
		JunctionMaker.this.newJunction(directorURI, actor);
	}
	
	
	/**
	 * Returns the role associated with a given Junction invitation.
	 * @param uri
	 * @return
	 */
	public static String getRoleFromInvitation(URI uri) {
		String query = uri.getQuery();
		if (query == null) return null;
		int pos = query.indexOf("role=");
		if (pos == -1) {
			return null;
		}

		query = query.substring(pos+5);
		pos = query.indexOf('&');
		if (pos > -1) {
			query = query.substring(0,pos);
		}
		return query;
	}
	
	@Deprecated
	public void inviteActorByListenerService(final URI invitationURI, URI listenerServiceURI) throws JunctionException {
		JunctionActor actor = new JunctionActor("inviter") {
				@Override
				public void onActivityJoin() {
					JSONObject invitation = new JSONObject();
					try {
						invitation.put("activity", invitationURI.toString());
						getJunction().sendMessageToSession(invitation);
					} 
					catch (Exception e) {
						e.printStackTrace(System.err);
					}
					leave();
				}
			
				@Override
				public void onMessageReceived(MessageHeader header,
											  JSONObject message) {
				
				}
			};
		
		JunctionMaker.this.newJunction(listenerServiceURI, actor);
	}
	
	
	/**
	 * Requests a listening service to join this activity as the prescribed role. Here,
	 * the service must be detailed in the activity description's list of roles.
	 * 
	 * An example platform in the role specification:
	 * { role: "dealer", platforms: [ 
	 * 						{ platform: "jxservice", 
	 * 						  classname: "edu.stanford.prpl.poker.dealer",
	 * 						  switchboard: "my.foreign.switchboard" } ] }
	 * 
	 * If switchboard is not present, it is assumed to be on the same switchboard
	 * on which this activity is being run.
	 * 
	 * @param role
	 * @param host
	 * @param serviceName
	 */
	@Deprecated
	public void inviteActorService(final Junction jx, final String role)  throws JunctionException{
		ActivityScript desc = jx.getActivityScript();
		System.out.println("Desc: " + desc.getJSON().toString());
		// find service platform spec
		
		System.out.println("inviting service for role " + role);
			
		JSONObject platform = desc.getRolePlatform(role, "jxservice");
		System.out.println("got platform " + platform);
		if (platform == null) return;
			
		String switchboard = platform.optString("switchboard");
		System.out.println("switchboard: " + switchboard);
		if (switchboard == null || switchboard.length() == 0) {
			switchboard = jx.getSwitchboard();
			System.out.println("switchboard is null, new: " + switchboard);
		}
		final String serviceName = platform.optString("serviceName");
			
		// // // // // // // // // // // // // // // // 
		JunctionActor actor = new JunctionActor("inviter") {
				@Override
				public void onActivityJoin() {
					JSONObject invitation = new JSONObject();
					try {
						invitation.put("activity", jx.getInvitationURI(role));
						invitation.put("serviceName",serviceName);
					} catch (Exception e) {}
					getJunction().sendMessageToSession(invitation);
					leave();
				}
				
				@Override
				public void onMessageReceived(MessageHeader header,
											  JSONObject message) {
					
				}
			};
			
		// remote jxservice activity:
		URI remoteServiceActivity=null;
		try {
			remoteServiceActivity = new URI("junction://"+switchboard+"/jxservice");
		} catch (URISyntaxException e) {
			e.printStackTrace();
			return;
		}
		System.out.println("Inviting service at uri " + remoteServiceActivity);
		JunctionMaker.this.newJunction(remoteServiceActivity, actor);
	}

	public static SwitchboardConfig getDefaultSwitchboardConfig(URI uri) {
		String fragment = uri.getFragment();
		if (fragment == null) {
			fragment = "xmpp";
		}
		fragment = fragment.toLowerCase();
		if (fragment.equals("jvm")) {
			return new edu.stanford.junction.provider.jvm.JVMSwitchboardConfig();
		} else if (fragment.equals("jx")) {
			return new edu.stanford.junction.provider.jx.JXSwitchboardConfig();
		} else if (fragment.equals("jx")) {
			return new edu.stanford.junction.provider.jx.JXSwitchboardConfig();
		} else if (fragment.equals("irc")) {
			return new edu.stanford.junction.provider.irc.IRCSwitchboardConfig();
		}

		// assume xmpp
		return new edu.stanford.junction.provider.xmpp.XMPPSwitchboardConfig(uri);
	}
}