public class

Junction

extends Junction
/*
 * 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.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.FromContainsFilter;
import org.jivesoftware.smack.filter.MessageTypeFilter;
import org.jivesoftware.smack.filter.OrFilter;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smackx.Form;
import org.jivesoftware.smackx.FormField;
import org.jivesoftware.smackx.muc.DiscussionHistory;
import org.jivesoftware.smackx.muc.MultiUserChat;
import org.json.JSONException;
import org.json.JSONObject;

import edu.stanford.junction.api.activity.ActivityScript;
import edu.stanford.junction.api.activity.JunctionActor;
import edu.stanford.junction.api.activity.JunctionExtra;
import edu.stanford.junction.api.messaging.MessageHandler;
import edu.stanford.junction.api.messaging.MessageHeader;
import edu.stanford.junction.api.messaging.target.MessageTarget;
import edu.stanford.junction.provider.ExtrasDirector;

public class Junction extends edu.stanford.junction.Junction {
	
	//TODO: XMPP won't let you query for room information
	// if the room is private.
	// Update getActivityScript() to join the room and get info.
	// or break the spec...
	private boolean PUBLIC_ROOM = true;
	
	public static String NS_JX = "jx";
	private ActivityScript mActivityDescription;
	private JunctionProvider mProvider;
	
	protected XMPPConnection mXMPPConnection;
	private MultiUserChat mSessionChat;
	private ConnectionListener mDebugConnectionListener;
	PacketFilter mMessageFilter = null;
	
	protected URI mAcceptedInvitation = null;
	/**
	 * Creates a new activity and registers it
	 * with a Junction server.
	 * 
	 * TODO: probably merge this function with registerActor().
	 */
	protected Junction(ActivityScript desc, XMPPConnection xmppConnection, 
					   XMPPSwitchboardConfig xmppConfig, JunctionProvider prov) {
		
		PacketFilter typeFilter = new OrFilter(new MessageTypeFilter(Message.Type.chat), 
											   new MessageTypeFilter(Message.Type.groupchat));

//		PacketFilter addrFilter = new FromContainsFilter("@"+xmppConfig.getChatService());
		
		mMessageFilter = typeFilter;//new AndFilter(typeFilter,addrFilter);
		
		if (xmppConnection == null) {
			throw new IllegalArgumentException("XMPPConnection must not be null.");
		}
		mActivityDescription=desc;
		mXMPPConnection = xmppConnection;
		mDebugConnectionListener = new ConnectionListener();
		mXMPPConnection.addConnectionListener(mDebugConnectionListener);
		mProvider=prov;
	}
	
	public String getActivityID() {
		return mActivityDescription.getActivityID();
	}
	
	public ActivityScript getActivityScript() {
		return mActivityDescription;
	}
	
	// TODO: move to constructor?
	public void registerActor(final JunctionActor actor) {
		//System.out.print("adding actor for roles: ");
		//String[] roles =  actor.getRoles();
		/*for(int i = 0; i<roles.length; i++) 
		  System.out.print(roles[i] + " ");
		  System.out.print("\n");*/
		
		setActor(actor);
		try {
			mSessionChat = joinSessionChat();
		} catch (Exception e) {
			e.printStackTrace();
			return;
		}
		
		MessageHandler handler = new MessageHandler() {
				@Override
				public void onMessageReceived(MessageHeader header,
											  JSONObject message) {
				
					Junction.this.triggerMessageReceived(header, message);
				}
			};
		
		if (handler != null) {
			registerMessageHandler(handler);
		}
		
		Junction.this.triggerActorJoin(mActivityDescription.isActivityCreator());
	}
	
	public void start() {
		Map<String,String>go = new HashMap<String,String>();
		
		try {
			mSessionChat.sendMessage(go.toString());
		} catch (XMPPException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void disconnect() {
		mSessionChat.leave();
		mProvider.remove(this);
	}
	
	public String[] getRoles() {
		return mActivityDescription.getRoles();
	}

	public String getSessionID() {
		return mActivityDescription.getSessionID();
	}
	public String getSwitchboard() {
		return mActivityDescription.getHost();
	}

	public void registerMessageHandler(final MessageHandler handler) {
		PacketListener packetListener = new PacketListener() {
				@Override
				public void processPacket(Packet packet) {
					Message message = (Message)packet;
					//System.out.println("got message " + message.toXML());
				
					JSONObject obj = null;
					try {
						obj = new JSONObject(message.getBody());
					} catch (Exception e) {
						System.out.println("Could not convert to json: " + message.getBody());
						//e.printStackTrace();
						return;
					}
				
					if (obj.has(NS_JX)) {
						JSONObject header = obj.optJSONObject(NS_JX);
						if (header.has("targetRole")) {
							String target = header.optString("targetRole");
							String[] roles = mOwner.getRoles();
							boolean forMe=false;
							for (int i=0;i<roles.length;i++) {
								if (roles[i].equals(target)) {
									forMe=true;
									break;
								}
								if (!forMe) return;
							}
						}
					}
					int i;
					String from = message.getFrom();
					if ((i =from.lastIndexOf('/')) >= 0) {
						from = from.substring(i+1);
					}
					handler.onMessageReceived(new MessageHeader(Junction.this,obj,from), obj);
				}
			};
		
		mXMPPConnection.addPacketListener(packetListener, mMessageFilter);
		//mSessionChat.addMessageListener(packetListener);
	}

	
	public void sendMessageToTarget(MessageTarget target, JSONObject message) {
		target.sendMessage(message);
	}
	
	public void doSendMessageToActor(String actorID, JSONObject message) {
		try {
			String privChat = mSessionChat.getRoom()+"/" + actorID;
			Chat chat = mSessionChat.createPrivateChat(privChat,null);
			chat.sendMessage(message.toString());
		} catch (XMPPException e) {
			e.printStackTrace();
		}
	}
	
	public void doSendMessageToRole(String role, JSONObject message) {
		try {
			JSONObject jx;
			if (message.has(NS_JX)) {
				jx = message.optJSONObject(NS_JX);
			} else {
				jx = new JSONObject();
				try {
					message.put(NS_JX, jx);
				} catch (JSONException j) {}
			}
			try {
				jx.put("targetRole", role);
			} catch (Exception e) {}
			mSessionChat.sendMessage(message.toString());
		} catch (XMPPException e) {
			e.printStackTrace();
		}
	}

	public void doSendMessageToSession(JSONObject message) {
		try {
			mSessionChat.sendMessage(message.toString());
		} catch (XMPPException e) {
			e.printStackTrace();
		}
	}

	public URI getBaseInvitationURI() {
		URI invitation = null;
		try {
			// TODO: strip query part from hostURL
			invitation = new URI("junction://"
								 +getSwitchboard()+"/"
								 +getSessionID()
								 );
		} catch (Exception e) {
			e.printStackTrace();
		}

		return invitation;
	}
	
	
	private MultiUserChat joinSessionChat() throws XMPPException {
		String room = mActivityDescription.getSessionID()+"@conference." + mXMPPConnection.getServiceName();
		
		DiscussionHistory history = new DiscussionHistory();
		history.setMaxChars(0);
		MultiUserChat chat = new MultiUserChat(mXMPPConnection, room);

		System.out.println("Joining " + room);
		//if (mActivityDescription.isActivityOwner()) {
		try {
			try {
				MultiUserChat.getRoomInfo(mXMPPConnection, room);
				chat.join(mOwner.getActorID(),null,history,10000);
				mActivityDescription.isActivityCreator(false);
				return chat;
			} catch (Exception e) { /*e.printStackTrace();*/ }
				
			System.out.println("Trying to create room");
				
				
			// TODO: is this an error? is there really a notion of ownership?
			mActivityDescription.isActivityCreator(true);
			chat.create(mOwner.getActorID());

			//mSessionChat.sendConfigurationForm(new Form(Form.TYPE_SUBMIT));
				
			System.out.println("sending config form");
			Form form = chat.getConfigurationForm();
			// Create a new form to submit based on the original form
			Form submitForm = form.createAnswerForm();
			// Add default answers to the form to submit
			for (Iterator<FormField> fields = form.getFields(); fields.hasNext();) {
				FormField field = (FormField) fields.next();
				//System.out.println(field.getVariable());
				if ("muc#roomconfig_roomdesc".equals(field.getVariable())) {
					//System.out.println("setting the room desc " + mActivityDescription.getJSON().toString());
					submitForm.setAnswer("muc#roomconfig_roomdesc", mActivityDescription.getJSON().toString());
				} else if (!FormField.TYPE_HIDDEN.equals(field.getType()) && field.getVariable() != null) {
					// Sets the default value as the answer
					submitForm.setDefaultAnswer(field.getVariable());
				}
			}
			      
			List<String>whois = new ArrayList<String>();
			whois.add("moderators");
			submitForm.setAnswer("muc#roomconfig_whois", whois);
			submitForm.setAnswer("muc#roomconfig_publicroom", PUBLIC_ROOM);
			chat.sendConfigurationForm(submitForm);
				
				
		} catch (XMPPException e) {
			System.out.println("Could not create room");
			e.printStackTrace();
			try {
				chat.join(mOwner.getActorID(),null,history,10000);
			} catch (XMPPException e2) {
				System.err.println("could not join or create room. ");
				e2.printStackTrace();
			}
		}
		return chat;
	}

	@Override
	public URI getAcceptedInvitation() {
		return mAcceptedInvitation;
	}

	@Override
	public JunctionActor getActor() {
		return mOwner;
	}

	class ConnectionListener implements org.jivesoftware.smack.ConnectionListener{

		/**
		 * Notification that the connection was closed normally or that the reconnection
		 * process has been aborted.
		 */
		public void connectionClosed(){
			System.out.println("XMPPConnection closed. ");
		}

		/**
		 * Notification that the connection was closed due to an exception. When
		 * abruptly disconnected it is possible for the connection to try reconnecting
		 * to the server.
		 *
		 * @param e the exception.
		 */
		public void connectionClosedOnError(Exception e){
			System.out.println("XMPPConnection closed due to error. Attempting reconnect. ");
		}
    
		/**
		 * The connection will retry to reconnect in the specified number of seconds.
		 * 
		 * @param seconds remaining seconds before attempting a reconnection.
		 */
		public void reconnectingIn(int seconds){
			System.out.println("XMPPConnection attempting reconnect in " + seconds + " seconds.");
		}
    
		/**
		 * The connection has reconnected successfully to the server. Connections will
		 * reconnect to the server when the previous socket connection was abruptly closed.
		 */
		public void reconnectionSuccessful(){
			System.out.println("XMPPConnection reconnected successfully!");
		}
    
		/**
		 * An attempt to connect to the server has failed. The connection will keep trying
		 * reconnecting to the server in a moment.
		 *
		 * @param e the exception that caused the reconnection to fail.
		 */
		public void reconnectionFailed(Exception e){
			System.out.println("XMPPConnection reconnect failed!");
		}
	}
}