/* ======================================================================
   Parts Copyright 2006 University of Leeds, Oxford University, University of the Highlands and Islands.

   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 org.bodington.messaging;

import org.apache.log4j.Logger;

import java.util.Hashtable;
import java.util.Vector;
import java.util.Enumeration;
import javax.swing.tree.TreeNode;
import org.bodington.database.PrimaryKey;
import org.bodington.server.*;
import org.bodington.server.realm.*;
import org.bodington.server.resources.Resource;
import org.bodington.server.realm.User;
import org.bodington.server.realm.Permission;
import org.bodington.server.events.MessagingEvent;
import org.bodington.messaging.*;
import org.bodington.text.BigString;
import org.bodington.util.Visibility;
import org.bodington.database.*;

public class MessagingRoomSessionImpl 
	extends org.bodington.server.SingleResourceSession 
	implements org.bodington.messaging.MessagingRoomSession
	{
    
    private static Logger log = Logger.getLogger(MessagingRoomSessionImpl.class);
    
	//if the client needs the server to keep track of
	//open and colapsed branches the ids of visible
	//nodes will be stored in this hashtable.
	//keys are message_ids values are Visibility
	//if a message doesn't have an entry it is implied that it is 
	//closed and only visible if its parent has an entry and 
	//is visible or if it is a top level message.
	private Hashtable visible_message_ids=null;


	//keys to messages that this user in this session
	//has been told about - allows GUI client to request
	//information only about changed items.
	private Hashtable message_keys;
	

    private PrimaryKey selected_message_id = null;
    
    
    public boolean canPostDraft( PrimaryKey key )
		throws BuildingServerException
    	{
		prepareMethodCall();
		try
			{
			User user;
			BuildingContext context;
			MessageSummary summary;
	    	MessagingRoom room = getMessagingRoom();

	    	if ( room == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !room.checkPermission( Permission.VIEW ) )
				throw new BuildingServerException( "View access denied",
						"You don't have 'view' level access to this room." );

			summary = room.getMessageSummary( key );
			if ( summary==null )
				throw new BuildingServerException( "Can't find the message details." );

			// can post a draft if it isn't a draft
			if ( !summary.isDraft() )
				return false;

			// can't post anything without permission to post
			if ( !room.checkPermission( Permission.POST ) )
				return false;

			context = BuildingContext.getContext();
			user = (User)context.getUser();
			// author can post draft if has post level access.
			if ( summary.getUserId().equals( user.getUserId() ) )
				return true;

			// another user can post draft if they have post and manage level access.
			return room.checkPermission( Permission.MANAGE );
			}
		finally
			{
			disposeMethodCall();
			}
    	}

    public boolean canEditMessage( PrimaryKey key )
		throws BuildingServerException
    	{
		prepareMethodCall();
		try
			{
			User user;
			BuildingContext context;
			MessageSummary summary;
	    	MessagingRoom room = getMessagingRoom();

	    	if ( room == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !room.checkPermission( Permission.VIEW ) )
				throw new BuildingServerException( "View access denied",
						"You don't have 'view' level access to this room." );

			summary = room.getMessageSummary( key );
			if ( summary==null )
				throw new BuildingServerException( "Can't find the message details." );

            Resource res = getResource();
            if (res == null)
              throw new BuildingServerException(
                  "Can't find the resource for this message.");
            // can edit if user has MANAGE, ADMINISTER or SYSADMIN permission
            if (res.checkPermission(Permission.MANAGE))
                return true;

			// even author can't edit if marked as seen
			if ( summary.beenSeen() )
				return false;

			context = BuildingContext.getContext();
			user = (User)context.getUser();
			// author can edit if not seen
			return summary.getUserId().equals( user.getUserId() );
			}
		finally
			{
			disposeMethodCall();
			}
    	}

	
	public MessagingRoomSessionImpl()
		{
		super();
		}

	public boolean containsMessage( PrimaryKey mid )
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
			MessagingRoom room;
			Message message, parent_message=null;
			User user;
			BigString text_obj;
			
			
	    	room = getMessagingRoom();
	    	
	    	if ( room == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !room.checkPermission( Permission.VIEW ) )
				throw new BuildingServerException( "View access denied",
						"You don't have 'view' level access to this room." );
						
			return room.containsMessage( mid );
			}
		finally
			{
			disposeMethodCall();
			}

		}
		
	public PrimaryKey postMessage( String subject, String text, int style )
		throws BuildingServerException
		{
		return postMessage( subject, text, style, null, true );
		}
		
	public PrimaryKey postMessage( String subject, String text, int style, PrimaryKey parent_mid, boolean branch )
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
			MessagingRoom room;
			Message message;
			User user;
			BigString text_obj;
			
			
	    	room = getMessagingRoom();
	    	
	    	if ( room == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !room.checkPermission( Permission.POST ) )
				throw new BuildingServerException( "Post access denied",
						"You don't have 'post' level access to this room." );
						
			user = (User)BuildingContext.getContext().getUser();
			
			if ( parent_mid != null &&!room.containsMessage( parent_mid ) )
				{
				throw new BuildingServerException( "Couldn't find the message this is a reply to." );
				}
			
			
			if ( subject == null )
				subject = "(No subject)";
			if ( text == null )
				text = "";
				
			text = text.trim();
			if ( text.length() == 0 )
				throw new BuildingServerException( "No message given.",
					"No message was specified.  It isn't possble to post an empty message." );
					
			text_obj = new BigString();
			text_obj.setString( text );
			text_obj.save();

			log.debug( "Message text: " );
			log.debug( text_obj.getBigStringId().toString() );
			
			message = room.createMessage( parent_mid, branch );
			message.setUser( user );
			message.setAuthorName( user.getName() );  //so the name on the message doesn't change
			message.setStyle( style );
			message.setSubject( subject );
			message.setBigStringId( text_obj.getBigStringId() );
			log.debug( "Message: " );
			log.debug( message.getBigStringId().toString() );
			room.postMessage( message );

			//newly posted messages are always visible to poster but closed
			if ( visible_message_ids!=null )
				visible_message_ids.put( message.getMessageId(), Visibility.VISIBLE_CLOSED );
				
			
                        // sometimes the caller doesn't want the branch to be
                        // opened so don't do it here - the caller must do it
			//open the parent message so siblings are visible....
			//if ( message.getParentMessageId() != null )
			//	{
			//	openMessageBranch( message.getParentMessageId() );
			//	}

				
			try
				{
				User author=null;
				MessageSummary replyto_summary =null;
				if ( parent_mid!=null )
					{
					replyto_summary = getMessageSummary( parent_mid );
					author = User.findUser( replyto_summary.getUserId() );
					}
				
        		MessagingEvent event = new MessagingEvent( 
        						(parent_mid==null) ? MessagingEvent.EVENT_POST : MessagingEvent.EVENT_REPLY,
        						room.getResourceId(), 
        						user.getUserId(), 
        						author==null?null:author.getUserId(), 
        						new Integer( message.getMessageId().intValue() ), 
        						parent_mid==null ?
        							null : new Integer( parent_mid.intValue() ) 
        						);
        		event.save();
				}
			catch ( Exception ex )
				{
				log.error( ex.getMessage(), ex );
				}

				
                        // sometimes the caller doesn't want the message
                        // selected
			//selected_message_id = message.getMessageId();
			//return selected_message_id;
                        return message.getMessageId();
			}
		finally
			{
			disposeMethodCall();
			}

		}


	public PrimaryKey saveMessage( String subject, String text, int style, PrimaryKey mid, boolean branch )
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
			MessagingRoom room;
			Message message;
			User user;
			BigString text_obj;
			PrimaryKey parent_mid, replyto_mid;
			boolean edit_message, edit_big_string, is_branched, edit_tree;
			
	    	room = getMessagingRoom();
	    	
	    	if ( room == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !room.checkPermission( Permission.POST ) )
				throw new BuildingServerException( "Post access denied",
						"You don't have 'post' level access to this room." );
						
			user = (User)BuildingContext.getContext().getUser();
			
			if ( mid == null || !room.containsMessage( mid ) )
				throw new BuildingServerException( "Couldn't find the message to edit." );
			message = Message.findMessage( mid );
			if ( message == null )
				throw new BuildingServerException( "Couldn't load the message to edit it." );
				
			replyto_mid = message.getReplyToMessageId();
			parent_mid = message.getParentMessageId();
			
			if ( parent_mid != null && !room.containsMessage( parent_mid ) )
				{
				throw new BuildingServerException( "Couldn't find the message this is a reply to." );
				}
			
			
			if ( subject == null )
				subject = "(No subject)";
			if ( text == null )
				text = "";
				
			text = text.trim();
			if ( text.length() == 0 )
				throw new BuildingServerException( "No message given.",
					"No message was specified.  It isn't possble to post an empty message." );
					

			text_obj = BigString.findBigString( message.getBigStringId() );
			
			//work out what needs to be edited to save on database accesses if possible
			edit_message =	style != message.getStyle() 			|| 
							!subject.equals( message.getSubject() );
							
			edit_big_string = !text.equals( text_obj.getString() );
			
			is_branched = parent_mid==null || replyto_mid==null || parent_mid.equals( replyto_mid );
			edit_tree = branch != is_branched;
					
			if ( edit_big_string )
				{
				text_obj.setString( text );
				text_obj.save();						  //save text of message
				}

			if ( edit_big_string || edit_message )
				{
				message.setStyle( style );				  // new style setting
				message.setSubject( subject );			  // new subject
				message.save();
				}

			if ( !edit_tree )
				return mid;
				
			log.debug( "Message moving not implemented yet." );
			// now we have to move the message in the tree.....
				
			// not yet implemented!!!
			
			selected_message_id = mid;
			return mid;
			}
		finally
			{
			disposeMethodCall();
			}

		}


    public MessageTreeNode[] getMessageTreeNodes( int sort_type )
        throws BuildingServerException
    {
        prepareMethodCall();
        try
        {
            MessageTreeNode root_node = getMessageTree();
            MessagingRoom room = getMessagingRoom();
            Enumeration enumeration = root_node.preorderEnumeration();
            Vector list = new Vector();
            while (  enumeration.hasMoreElements() )
                list.addElement( enumeration.nextElement() );
            MessageTreeNode[] oarray = new MessageTreeNode[list.size()];
            oarray = (MessageTreeNode[])list.toArray( oarray );
            MessageTreeNodeComparator comparator = new MessageTreeNodeComparator( room, sort_type );
            java.util.Arrays.sort( oarray, comparator );

            return oarray;
        }
        finally
        {
            disposeMethodCall();
        }
    }

	public MessageTreeNode getMessageTree()
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
	    	MessagingRoom room = getMessagingRoom();
	    	
	    	if ( room == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !room.checkPermission( Permission.VIEW ) )
				throw new BuildingServerException( "View access denied",
						"You don't have 'view' level access to this room." );


			return room.getMessageRoot();
			}
		finally
			{
			disposeMethodCall();
			}
		}

	public String getMessageText( PrimaryKey key, String mime_type )
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
			User user = (User)BuildingContext.getContext().getUser();

			MessageSummary summary;
	    	MessagingRoom room = getMessagingRoom();

	    	if ( room == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !room.checkPermission( Permission.VIEW ) )
				throw new BuildingServerException( "View access denied",
						"You don't have 'view' level access to this room." );

                if ( key == null )
                    return room.getIntroduction();
                
			summary = room.getMessageSummary( key );
			if ( summary==null )
				throw new BuildingServerException( "Can't find the message text." );

			if ( !summary.beenSeen() )  //if message has not been seen should it be marked so now?
				{
				if ( !summary.isDraft() && !summary.getUserId().equals( user.getUserId() ) )
					{
					// the message is not a draft and the current reader is
					// not the author so this counts as seeing the final version of the message.
					Message message = Message.findMessage( summary.getMessageId() );
					message.setSeen( true );
					message.save();
					}
				}
			
			//record event....
			
			try
				{
				User author;
				author = User.findUser( summary.getUserId() );
				
        		MessagingEvent event = new MessagingEvent( 
        						MessagingEvent.EVENT_READ,
        						room.getResourceId(), 
        						user.getUserId(), 
        						author.getUserId(), 
        						new Integer( summary.getMessageId().intValue() ), 
        						summary.getReplyToMessageId()==null?null:
        						new Integer( summary.getReplyToMessageId().intValue() ) );
        		event.save();
				}
			catch ( Exception ex )
				{
				log.error( ex.getMessage(), ex );
				}
			
			
			return Message.getMessageText( summary, mime_type );
			}
		finally
			{
			disposeMethodCall();
			}
		}
		
	public String getMessageTextSample( PrimaryKey key, int max_length )
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
			User user = (User)BuildingContext.getContext().getUser();

			MessageSummary summary;
	    	MessagingRoom room = getMessagingRoom();

	    	if ( room == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !room.checkPermission( Permission.VIEW ) )
				throw new BuildingServerException( "View access denied",
						"You don't have 'view' level access to this room." );

			summary = room.getMessageSummary( key );
			if ( summary==null )
				throw new BuildingServerException( "Can't find the message text." );

			// if only a small sample is being fetched we don't mark the
                        // message as read.
                        
                        
			return Message.getMessageTextSample( summary, max_length );
			}
		finally
			{
			disposeMethodCall();
			}
		}

    public Vector getReaders( PrimaryKey key, boolean with_dates )
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
			User user;
			Vector list;
			MessageSummary summary;
	    	MessagingRoom room = getMessagingRoom();

	    	if ( room == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !room.checkPermission( Permission.VIEW ) )
				throw new BuildingServerException( "View access denied",
						"You don't have 'view' level access to this room." );

			summary = room.getMessageSummary( key );
			if ( summary==null )
				throw new BuildingServerException( "Can't find the message." );

			list = new Vector();
			
			StringBuffer where = new StringBuffer();
			where.append( "user_id IN (SELECT active_user_id FROM events, messaging_events " );
			where.append( "WHERE event_id = messaging_event_id AND resource_id = " );
			where.append( room.getResourceId().toString() );
			where.append( " AND event_code = " );
			where.append( MessagingEvent.EVENT_READ );
			where.append( " AND message_id = " );
			where.append( key.toString() );
			where.append( " )" );
			Enumeration enumeration = User.findUsers( where.toString(), "surname" );

			if ( !with_dates )
				{
				//just list the users.
				while ( enumeration.hasMoreElements() )
					{
					user = (User)enumeration.nextElement();
					list.addElement( user.getName() );
					}
				}
			else
				{
				StringBuffer line = new StringBuffer( 50 );
				//get the events
				where.setLength( 0 );
				where.append( "resource_id = " );
				where.append( room.getResourceId().toString() );
				where.append( " AND event_code = " );
				where.append( MessagingEvent.EVENT_READ );
				where.append( " AND message_id = " );
				where.append( key );
				enumeration = MessagingEvent.findMessagingEvents( where.toString(), "event_time" );
				MessagingEvent event;
				while ( enumeration.hasMoreElements() )
					{
					event = (MessagingEvent)enumeration.nextElement();
					line.setLength( 0 );
					line.append( event.getEventTime().toString() );
					line.append( " " );
					user = event.getActiveUser();
					line.append( user.getName() );
					list.addElement( line.toString() );
					}
				}
			
			return list;
			}
		finally
			{
			disposeMethodCall();
			}
		}

    public Vector getNonReaders( PrimaryKey key )
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
			Principal principal;
			User user;
			Hashtable reader_list;
			Vector non_reader_list;
			MessageSummary summary;
	    	MessagingRoom room = getMessagingRoom();

	    	if ( room == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !room.checkPermission( Permission.VIEW ) )
				throw new BuildingServerException( "View access denied",
						"You don't have 'view' level access to this room." );

			summary = room.getMessageSummary( key );
			if ( summary==null )
				throw new BuildingServerException( "Can't find the message." );

			//put user_ids into the reader list
			reader_list = new Hashtable();
			
			StringBuffer where = new StringBuffer();
			where.append( "user_id IN (SELECT active_user_id FROM events, messaging_events " );
			where.append( "WHERE event_id = messaging_event_id AND resource_id = " );
			where.append( room.getResourceId().toString() );
			where.append( " AND event_code = " );
			where.append( MessagingEvent.EVENT_READ );
			where.append( " AND message_id = " );
			where.append( key.toString() );
			where.append( " )" );
			Enumeration enumeration = User.findUsers( where.toString() );

			while ( enumeration.hasMoreElements() )
				{
				user = (User)enumeration.nextElement();
				reader_list.put( user.getUserId(), user.getUserId() );
				}
			
			// now work through people who can view and build
			// list of those who are not in the reader list
			non_reader_list=new Vector();
			Acl acl = room.getAcl();
			enumeration = room.everyoneWhoCan( Permission.VIEW, false );
			while ( enumeration.hasMoreElements() )
				{
				user = (User)enumeration.nextElement();
				if ( !reader_list.containsKey( user.getUserId() ) )
					non_reader_list.addElement( user.getName() );
				}
			
			return non_reader_list;
			}
		finally
			{
			disposeMethodCall();
			}
		}

    public Vector getParticipants( int style )
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
	    	MessagingRoom room = getMessagingRoom();

	    	if ( room == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !room.checkPermission( Permission.VIEW ) )
				throw new BuildingServerException( "View access denied",
						"You don't have 'view' level access to this room." );


			switch ( style )
				{
				case MessagingParticipantStyle.PARTICIPANTS_CAN_VIEW:
					return getCanParticipate( room, false );
				case MessagingParticipantStyle.PARTICIPANTS_CAN_POST:
					return getCanParticipate( room, true );
				case MessagingParticipantStyle.PARTICIPANTS_READERS:
					return getReadOrPostParticipants( room, false );
				case MessagingParticipantStyle.PARTICIPANTS_POSTERS:
					return getReadOrPostParticipants( room, true );
				case MessagingParticipantStyle.PARTICIPANTS_NON_READERS:
					return getReadOrPostNonParticipants( room, false );
				case MessagingParticipantStyle.PARTICIPANTS_NON_POSTERS:
					return getReadOrPostNonParticipants( room, true );
				}
			throw new BuildingServerException( "Unknown style of participant list requested." );
			}
		finally
			{
			disposeMethodCall();
			}
		}

    private Vector getCanParticipate( MessagingRoom room, boolean post )
		throws BuildingServerException
		{
		Vector list = new Vector();
		String group_name, description;
		User user;
		Group group;
		Resource other_resource;
		Enumeration groups, users;
        
		if ( post )
            {
            groups = room.everyGroupWhoCan( Permission.POST, false ).elements();
            users = room.everyoneWhoCan( Permission.POST, false );
            }
		else
            {
            groups = room.everyGroupWhoCan( Permission.VIEW, false ).elements();
            users = room.everyoneWhoCan( Permission.VIEW, false );
            }
        
        /*
         * Enumerate the groups:
         */
        while ( groups.hasMoreElements() )
            {
            group = (Group)groups.nextElement();
            group_name = group.getName();
            description = null;
            
            if ( group.isOwnersGroup() )
                {
                if ( group.getResourceId().equals( room.getResourceId() ) )
                    description = "Owners of this discussion room";
                else
                    {
                    other_resource = group.getResource();
                    description = "Owners of \"" + other_resource.getTitle() + "\".";
                    }
                }
            else if ( group.isAdhocGroup() )
                {
                if ( group.getResourceId().equals( room.getResourceId() ) )
                    description = "An adhoc group of users of this discussion room";
                else
                    {
                    other_resource = group.getResource();
                    description = "An adhoc group of users of \"" + other_resource.getTitle() + "\".";
                    }
                }
            
            if ( group.isLocalGroup() )
                {
                    group_name = group.getLocalName();
                }
            //three entries for each group
            list.addElement( "$group" );
            list.addElement( group_name );
            
            if ( description == null )
                {
                if ( group.getSpecialGroup() != null )
                    description = group.getDescription() + " It isn't possible to list the members of this group.";
                else
                    description = group.getDescription() + " Members of this group will be listed individually.";
                }
            list.addElement( description );
            }
        
        /*
         * Enumerate the users:
         */
		while ( users.hasMoreElements() )
			{
			user = (User)users.nextElement();
			//one entry for each user
			list.addElement( user.getName() );
			}
		
		return list;
		}
	
    private Vector getReadOrPostParticipants( MessagingRoom room, boolean posters )
		throws BuildingServerException
		{
		Vector list;
		User user;

		list = new Vector();
			
		StringBuffer where = new StringBuffer();
		where.append( "user_id IN (SELECT active_user_id FROM events, messaging_events "	);
		where.append( "WHERE event_id = messaging_event_id AND resource_id = "  			);
		where.append( room.getResourceId().toString()										);
		if ( posters )
			{
			where.append( " AND ( event_code = "											);
			where.append( MessagingEvent.EVENT_POST 										);
			where.append( " OR event_code = "												);
			where.append( MessagingEvent.EVENT_REPLY										);
			where.append( " )"																);
			}
		else
			{
			where.append( " AND event_code = "												);
			where.append( MessagingEvent.EVENT_READ 										);
			}
		where.append( " )"																	);
		Enumeration enumeration = User.findUsers( where.toString(), "surname"						);

		while ( enumeration.hasMoreElements() )
			{
			user = (User)enumeration.nextElement();
			list.addElement( user.getName() );
			}
			
		return list;
		}
	
    private Vector getReadOrPostNonParticipants( MessagingRoom room, boolean posters )
		throws BuildingServerException
		{
		Hashtable participant_list;
		Vector non_participants;
		Principal principal;
		User user;

		participant_list = new Hashtable();
			
		StringBuffer where = new StringBuffer();
		where.append( "user_id IN (SELECT active_user_id FROM events, messaging_events "	);
		where.append( "WHERE event_id = messaging_event_id AND resource_id = "  			);
		where.append( room.getResourceId().toString()										);
		if ( posters )
			{
			where.append( " AND ( event_code = "											);
			where.append( MessagingEvent.EVENT_POST 										);
			where.append( " OR event_code = "												);
			where.append( MessagingEvent.EVENT_REPLY										);
			where.append( " )"																);
			}
		else
			{
			where.append( " AND event_code = "												);
			where.append( MessagingEvent.EVENT_READ 										);
			}
		where.append( " )"																	);
		Enumeration enumeration = User.findUsers( where.toString(), "surname"						);

		while ( enumeration.hasMoreElements() )
			{
			user = (User)enumeration.nextElement();
			participant_list.put( user.getUserId(), user.getUserId() );
			}
			
			
		// now work through people who can view and build
		// list of those who are not in the reader list
		non_participants=new Vector();
		Acl acl = room.getAcl();
		log.debug( "Looking for non " + (posters?"posters":"readers") );
		enumeration = room.everyoneWhoCan( posters?Permission.POST:Permission.VIEW, false );
		while ( enumeration.hasMoreElements() )
			{
			user = (User)enumeration.nextElement();
			if ( !participant_list.containsKey( user.getUserId() ) )
				non_participants.addElement( user.getName() );
			}
			
			
		return non_participants;
		}

	
	public MessageSummary getMessageSummary( PrimaryKey key )
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
	    	MessagingRoom room = getMessagingRoom();
	    	
	    	if ( room == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !room.checkPermission( Permission.VIEW ) )
				throw new BuildingServerException( "View access denied",
						"You don't have 'view' level access to this room." );


			return room.getMessageSummary( key );
			}
		finally
			{
			disposeMethodCall();
			}
		}

	public Hashtable getMessageSummaries()
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
			Enumeration enumeration;
			PrimaryKey key;
	    	MessagingRoom room = getMessagingRoom();
	    	
	    	if ( room == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !room.checkPermission( Permission.VIEW ) )
				throw new BuildingServerException( "View access denied",
						"You don't have 'view' level access to this room." );


			message_keys = new Hashtable();

			Hashtable r = room.getMessageSummaries();
			if ( r==null ) return null;

			enumeration = r.keys();
			while ( enumeration.hasMoreElements() )
				{
				key = (PrimaryKey)enumeration.nextElement();
				message_keys.put( key, key );
				}


			return (Hashtable)r.clone();
			}
		finally
			{
			disposeMethodCall();
			}
		}
		
	public Hashtable getMoreMessageSummaries()
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
			PrimaryKey key;
			Enumeration enumeration;
			Message message;
			MessageSummary summary;
	    	MessagingRoom room = getMessagingRoom();
	    	
	    	if ( room == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !room.checkPermission( Permission.VIEW ) )
				throw new BuildingServerException( "View access denied",
						"You don't have 'view' level access to this room." );

			if ( message_keys==null )
				return getMessageSummaries();

			Hashtable r = room.getDifferentMessageSummaries( message_keys );
			if ( r==null ) return null;

			enumeration = r.keys();
			while ( enumeration.hasMoreElements() )
				{
				key = (PrimaryKey)enumeration.nextElement();
				message_keys.put( key, key );
				}


			return (Hashtable)r.clone();
			}
		finally
			{
			disposeMethodCall();
			}
		}


	public PrimaryKey getSelectedMessage()
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
			return selected_message_id;
			}
		finally
			{
			disposeMethodCall();
			}
		}


	public void selectMessage( PrimaryKey mid )
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
			MessagingRoom room;
			
	    	room = getMessagingRoom();
	    	
	    	if ( room == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
						
			if ( mid != null && !room.containsMessage( mid ) )
				throw new BuildingServerException( "Couldn't find the message to edit." );
				
			selected_message_id = mid;
			}
		finally
			{
			disposeMethodCall();
			}
		}


    public void selectMessageAndSimplifyTree( PrimaryKey mid )
    throws BuildingServerException
    {
        prepareMethodCall();
        try
        {
            MessagingRoom room;
            MessageTreeNode node, current_node, child;
            Hashtable vis;
            Enumeration children;
            boolean has_children, is_thread;

            room = getMessagingRoom();

            if ( room == null )
                throw new BuildingServerException( "Can't find correct resource." );

            node = room.getMessageNode( mid );
            if ( node == null )
                throw new BuildingServerException( "Couldn't find the message to select." );
            
            has_children = !node.isLeaf();
            is_thread = has_children || node.getLevel()<=1;
            
            // start by collapsing whole tree
            vis = getVisibleMessages();
            vis.clear();
            
            // make selected node visible and open if it has children
            if ( node.getPrimaryKey()!=null )
                vis.put( node.getPrimaryKey(), has_children?Visibility.VISIBLE_OPEN:Visibility.VISIBLE_CLOSED );
            
            // make ancestors visible
            current_node = node;
            while ( (current_node = (MessageTreeNode)current_node.getParent()) != null )
                if ( current_node.getPrimaryKey()!=null )
                    vis.put( current_node.getPrimaryKey(), Visibility.VISIBLE_OPEN );
            
            // if is thread, show children otherwise show siblings
            //if ( is_thread )
                current_node = node;
            //else
            //    current_node = (MessageTreeNode)node.getParent();
            
            if ( current_node != null )
            {
                children = current_node.children();
                while ( children.hasMoreElements() )
                {
                    child = (MessageTreeNode)children.nextElement();
                    vis.put( child.getPrimaryKey(), Visibility.VISIBLE_CLOSED );
                }
            }
            
            selected_message_id = mid;
            
            
        }
        finally
        {
            disposeMethodCall();
        }
    }
    
    public void selectMessageAndMakeVisible( PrimaryKey mid )
    throws BuildingServerException
    {
        prepareMethodCall();
        try
        {
            MessagingRoom room;
            MessageTreeNode node, current_node, child;
            Hashtable vis;
            Enumeration children;
            boolean has_children, is_thread;

            room = getMessagingRoom();

            if ( room == null )
                throw new BuildingServerException( "Can't find correct resource." );

            node = room.getMessageNode( mid );
            if ( node == null )
                throw new BuildingServerException( "Couldn't find the message to select." );
            
            // get ancestors of node
            // open every ancestor but don't open selected node.
            TreeNode[] path = node.getPath();
            
            int i;
            for ( i=0; i<(path.length-1); i++ )
            {
                current_node = (MessageTreeNode)path[i];
                openMessageBranch( current_node.getPrimaryKey() );
            }
            
            selected_message_id = mid;
            
            
        }
        finally
        {
            disposeMethodCall();
        }
    }

	public Hashtable getVisibleMessages()
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
			Enumeration children;
			MessageTreeNode root, child;
	    	MessagingRoom room = getMessagingRoom();
	    	PrimaryKey mid;
	    	
	    	if ( room == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !room.checkPermission( Permission.VIEW ) )
				throw new BuildingServerException( "View access denied",
						"You don't have 'view' level access to this room." );

			if ( visible_message_ids == null )
				{
				// this user, in this session hasn't asked before
				// so make initial list
				visible_message_ids = new Hashtable();
				
				root = room.getMessageRoot();
				
				children = root.children();
				
				while ( children.hasMoreElements() )
					{
					child = (MessageTreeNode)children.nextElement();
					mid = child.getPrimaryKey();
					if ( mid != null )
						{
						//if ( child.getChildCount()==0 )
						//	visible_message_ids.put( mid, Boolean.TRUE );
						//else
						visible_message_ids.put( mid, Visibility.VISIBLE_CLOSED );
						}
					}
				}
				
			return visible_message_ids;
			}
		finally
			{
			disposeMethodCall();
			}
		}

	public void openMessageBranch( PrimaryKey mid )
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
			Enumeration children=null;
			MessageTreeNode root, node, child;
	    	MessagingRoom room = getMessagingRoom();
	    	PrimaryKey nodemid;
	    	Enumeration enumeration;
	    	Hashtable vis;
	    	
	    	if ( room == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !room.checkPermission( Permission.VIEW ) )
				throw new BuildingServerException( "View access denied",
						"You don't have 'view' level access to this room." );


			vis = getVisibleMessages();

			root = room.getMessageRoot();
			enumeration = root.preorderEnumeration();
			while ( enumeration.hasMoreElements() )
				{
				node = (MessageTreeNode)enumeration.nextElement();
				nodemid = node.getPrimaryKey();
				if ( nodemid==null ) continue;
				if ( !nodemid.equals( mid ) ) continue;
				
				//indicate that selected node is now open
				vis.put( nodemid, Visibility.VISIBLE_OPEN );
				
				children = node.children();
				while ( children.hasMoreElements() )
					{
					child = (MessageTreeNode)children.nextElement();
					vis.put( child.getPrimaryKey(), Visibility.VISIBLE_CLOSED );
					}
				break;
				}
				
			selected_message_id = mid;
			return;
			}
		finally
			{
			disposeMethodCall();
			}
		
		}

	
	public void closeMessageBranch( PrimaryKey mid )
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
			Enumeration children=null;
			MessageTreeNode root, node, child;
	    	MessagingRoom room = getMessagingRoom();
	    	PrimaryKey nodemid;
	    	Enumeration enumeration;
	    	Hashtable vis;
	    	
	    	if ( room == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !room.checkPermission( Permission.VIEW ) )
				throw new BuildingServerException( "View access denied",
						"You don't have 'view' level access to this room." );


			vis = getVisibleMessages();

			root = room.getMessageRoot();
			enumeration = root.preorderEnumeration();
			while ( enumeration.hasMoreElements() )
				{
				node = (MessageTreeNode)enumeration.nextElement();
				nodemid = node.getPrimaryKey();
				if ( nodemid==null ) continue;
				if ( !nodemid.equals( mid ) ) continue;
				
				//indicate that selected node is now closed
				vis.put( nodemid, Visibility.VISIBLE_CLOSED );
				
				//now get all children from this branch and make sure they
				//are made invisible.
				children = node.preorderEnumeration();
				//skip the node itself - leave it visible
				children.nextElement();
				while ( children.hasMoreElements() )
					{
					child = (MessageTreeNode)children.nextElement();
					vis.put( child.getPrimaryKey(), Visibility.INVISIBLE_CLOSED );
					}
				break;
				}
				
			selected_message_id = mid;
			return;
			}
		finally
			{
			disposeMethodCall();
			}
		
		}
	
	
	public void openAllMessageBranches()
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
			MessageTreeNode root, node;
	    	MessagingRoom room = getMessagingRoom();
	    	PrimaryKey nodemid;
	    	Enumeration enumeration;
	    	Hashtable vis;
	    	
	    	if ( room == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !room.checkPermission( Permission.VIEW ) )
				throw new BuildingServerException( "View access denied",
						"You don't have 'view' level access to this room." );


			vis = getVisibleMessages();

			root = room.getMessageRoot();
			enumeration = root.preorderEnumeration();
			while ( enumeration.hasMoreElements() )
				{
				node = (MessageTreeNode)enumeration.nextElement();
				nodemid = node.getPrimaryKey();
				if ( nodemid==null )
					continue;
				
				//indicate that node is now open
				if ( node.getChildCount()>0 )
					vis.put( nodemid, Visibility.VISIBLE_OPEN );
				else
					// but empty nodes are closed
					vis.put( nodemid, Visibility.VISIBLE_CLOSED );
				}
				
			selected_message_id = null;
			return;
			}
		finally
			{
			disposeMethodCall();
			}
		
		}

	
	public void closeAllMessageBranches()
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
			MessageTreeNode root, node;
	    	MessagingRoom room = getMessagingRoom();
	    	PrimaryKey nodemid;
	    	Enumeration enumeration;
	    	Hashtable vis;
	    	
	    	if ( room == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !room.checkPermission( Permission.VIEW ) )
				throw new BuildingServerException( "View access denied",
						"You don't have 'view' level access to this room." );


			vis = getVisibleMessages();

			root = room.getMessageRoot();
			enumeration = root.preorderEnumeration();
			while ( enumeration.hasMoreElements() )
				{
				node = (MessageTreeNode)enumeration.nextElement();
				nodemid = node.getPrimaryKey();
				if ( nodemid==null )
					continue;

				//top level messages left visible (but closed)
				if ( node.getLevel()<=1 )
					vis.put( nodemid, Visibility.VISIBLE_CLOSED );
				else
					// but all others hidden
					vis.put( nodemid, Visibility.INVISIBLE_CLOSED );
				}
				
			selected_message_id = null;
			return;
			}
		finally
			{
			disposeMethodCall();
			}
		
		}

	
	
	
	
	private synchronized MessagingRoom getMessagingRoom()
		throws BuildingServerException
		{
		Resource r = getResource();
		if ( r == null || !(r instanceof MessagingRoom) )
			throw new BuildingServerException( 
				"Wrong type of session for this type of resource.",
				"There was a technical problem attempting to access this tool." );
				
		return (MessagingRoom)r;
		}
		
	}
	
