public class

JunctionProvider

extends JunctionProvider
/*
 * 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.provider.xmpp;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.Date;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;


import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.muc.MultiUserChat;
import org.jivesoftware.smackx.muc.RoomInfo;
import org.json.JSONException;
import org.json.JSONObject;

import edu.stanford.junction.Junction;
import edu.stanford.junction.JunctionMaker;
import edu.stanford.junction.JunctionException;
import edu.stanford.junction.api.activity.ActivityScript;
import edu.stanford.junction.api.activity.JunctionActor;
import edu.stanford.junction.api.messaging.MessageHeader;

public class JunctionProvider extends edu.stanford.junction.provider.JunctionProvider {
	protected XMPPSwitchboardConfig mConfig;
	protected static final boolean ONE_CONNECTION_PER_SESSION=false;
	protected static final int XMPP_PORT=5222;
	
	// TODO: Can't use a single connection right now-
	// must support multiple actors in the same activity
	// and this can't be done over a single XMPP connection now.
	// BJD 2/2/10
	
	//private XMPPConnection mXMPPConnection;

	public JunctionProvider(XMPPSwitchboardConfig config) {
		mConfig = config;		
	}
	
	public Junction newJunction(URI invitation, ActivityScript script, JunctionActor actor) throws JunctionException{
		// this needs to be made more formal
		if (script == null) {
			script = new ActivityScript();
		}
		if (null == script.getHost()) {
			script.setUri(invitation);
		}
		
		XMPPConnection mXMPPConnection 
		    = getXMPPConnection(mConfig,JunctionMaker.getSessionIDFromURI(invitation));
		edu.stanford.junction.provider.xmpp.Junction jx
			= new edu.stanford.junction.provider.xmpp.Junction(script,mXMPPConnection,mConfig,this);
		
		jx.mAcceptedInvitation=invitation;
		jx.registerActor(actor);
		
		this.requestServices(jx,script);
		
		
		// creating an activity is an activity using a JunctionService.
		// Invite the JunctionMaker service to the session.
		// This service will be bundled with all Junction servers.
		//activity.requestService("JunctionMaker", mHostURL, "edu.stanford.prpl.junction.impl.JunctionMakerService");		
		return jx;
	}
	
	public ActivityScript getActivityScript(URI uri) throws JunctionException {
		
		// TODO: Move the XMPPConnection into the JunctionMaker
		// (out of Junction)
		/*
		  JunctionMaker jm = null;
		  String host = uri.getHost();
		  if (host.equals(mSwitchboard)) {
		  jm = this;
		  } else {
		  jm = new JunctionMaker(host);
		  }
		*/
		
		String host = uri.getHost();
		String sessionID = uri.getPath().substring(1);
		
		// pretty broken..
		XMPPSwitchboardConfig config = new XMPPSwitchboardConfig(host);
		XMPPConnection conn = getXMPPConnection(config,sessionID);
		
		String room = sessionID+"@" +config.getChatService();
		System.err.println("looking up info from xmpp room " + room);
		
		
		try {
			RoomInfo info = MultiUserChat.getRoomInfo(conn, room);
			String descString = info.getDescription();

			if (descString == null || descString.trim().length()==0) {
				throw new JunctionException("No MUC room description found.");
			}
			
			JSONObject descJSON = new JSONObject(descString);
			
			return new ActivityScript(descJSON);

		} catch (Exception e) {
			throw new JunctionException("Failed to initialize XMPP Chat Room.", e);
		}

	}

	private static ArrayList<XMPPConnection> sConnections = new ArrayList<XMPPConnection>();
	private static ArrayList<HashSet<String>> sConnectionSessions
		= new ArrayList<HashSet<String>>();


	class ConnectionThread extends Thread{
		public volatile XMPPConnection connection = null;
		public volatile Throwable failureReason = null;
		public volatile boolean success = false;

		private CountDownLatch waitForConnect;
		private XMPPSwitchboardConfig config;

		public ConnectionThread(XMPPSwitchboardConfig config, CountDownLatch latch){
			this.waitForConnect = latch;
			this.config = config;
		}

		public void run(){
			while(!isInterrupted()){

				if(this.connection != null){
					connection.disconnect();
				}


                ConnectionConfiguration conf = new ConnectionConfiguration(config.host, XMPP_PORT);
                conf.setReconnectionAllowed(false);
				connection = new XMPPConnection(conf);

				try {
					connection.connect();
					if (config.user != null) {
						connection.login(config.user, config.password);
					} 
					else {
						connection.loginAnonymously();
					}

					if(isInterrupted()){
						success = false;
						break;
					}
					else{ // All is good. Finish up. 
						failureReason = null;
						success = true;
						waitForConnect.countDown();
						System.out.println("Got connection, ending connection thread.");
						break;
					}

				}
				catch (XMPPException e) {
					Throwable ex = e.getWrappedThrowable();
					if(ex instanceof IOException){
						failureReason = ex;
						continue;
					}
					else if(ex instanceof UnknownHostException){
						failureReason = ex;
						continue;
					}
					else {
						// Otherwise, we consider the exception
						// unrecoverable.
						failureReason = e;
						this.connection.disconnect();
						waitForConnect.countDown();
						break;
					}
				}
				catch (Exception e) {
					failureReason = e;
					this.connection.disconnect();
					waitForConnect.countDown();
					break;
				}
			}
		}
	}



	private synchronized XMPPConnection getXMPPConnection(XMPPSwitchboardConfig config, String roomid) throws JunctionException{
		final CountDownLatch waitForConnect = new CountDownLatch(1);
		ConnectionThread t = new ConnectionThread(config, waitForConnect);
		t.start();

		boolean timedOut = false;
		try{
			timedOut = !(waitForConnect.await(config.connectionTimeout, TimeUnit.MILLISECONDS));
		}
		catch(InterruptedException e){
			throw new JunctionException("Interrupted while waiting for connection.");
		}

		if(timedOut){ 
			System.out.println("Connection timed out after " + 
							   config.connectionTimeout + 
							   " milliseconds.");
			t.interrupt(); // Thread may still be looping...

			String msg = "Connection attempt failed to complete within provided timeout of " + 
				config.connectionTimeout + " milliseconds.";
			throw new JunctionException(msg, new ConnectionTimeoutException(msg));
		}

		if(t.success){
			System.out.println("Connection succeeded.");
			sConnections.add(t.connection);
			HashSet<String> set = new HashSet<String>();
			set.add(roomid);
			sConnectionSessions.add(set);
			return t.connection;
		}
		else{
			if(t.failureReason != null){
				throw new JunctionException("Connection attempt failed.", t.failureReason);
			}
			else{
				throw new JunctionException("Connection attempt failed for unknown reason.", t.failureReason);
			}
		}
	}
	
	protected synchronized void remove(edu.stanford.junction.provider.xmpp.Junction jx) {
		// O(n) can be improved, but n is small.
		XMPPConnection conn = jx.mXMPPConnection;
		for (int i=0;i<sConnections.size();i++) {
			if (sConnections.get(i).equals(conn)) {
				sConnectionSessions.get(i).remove(jx.getSessionID());
			}
		}
	}
	
	// test
	public static void main(String[] args) {
		//ConnectionConfiguration config = new ConnectionConfiguration("prpl.stanford.edu",5222);
		//XMPPConnection con = new XMPPConnection(config);
		XMPPConnection con = new XMPPConnection("prpl.stanford.edu");
	
		try {
			con.connect();
			System.out.println("Connected.");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public URI generateSessionUri() {
		String session = UUID.randomUUID().toString();
		try {
			return new URI("junction://" + mConfig.host + "/" + session);
		} catch (URISyntaxException e) {
			throw new AssertionError("Invalid URI");
		}
	}

}