/* ======================================================================
The Bodington System Software License, Version 1.0
  
Copyright (c) 2001 The University of Leeds.  All rights reserved.
  
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1.  Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2.  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.

3.  The end-user documentation included with the redistribution, if any,
must include the following acknowledgement:  "This product includes
software developed by the University of Leeds
(http://www.bodington.org/)."  Alternately, this acknowledgement may
appear in the software itself, if and wherever such third-party
acknowledgements normally appear.

4.  The names "Bodington", "Nathan Bodington", "Bodington System",
"Bodington Open Source Project", and "The University of Leeds" must not be
used to endorse or promote products derived from this software without
prior written permission. For written permission, please contact
d.gardner@leeds.ac.uk.

5.  The name "Bodington" may not appear in the name of products derived
from this software without prior written permission of the University of
Leeds.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO,  TITLE,  THE IMPLIED WARRANTIES 
OF QUALITY  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO 
EVENT SHALL THE UNIVERSITY OF LEEDS OR ITS 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.
=========================================================

This software was originally created by the University of Leeds and may contain voluntary 
contributions from others.  For more information on the Bodington Open Source Project, please 
see http://bodington.org/

====================================================================== */

package org.bodington.servlet.facilities;

import org.apache.log4j.Logger;

import org.bodington.servlet.*;

import java.io.*;
import java.sql.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.text.*;
import java.util.*;
import java.rmi.RemoteException;


import org.bodington.database.*;
import org.bodington.server.*;
import org.bodington.server.realm.*;
import org.bodington.server.realm.Group;
import org.bodington.server.resources.Resource;
import org.bodington.server.resources.UploadedFile;
import org.bodington.messaging.*;
import org.bodington.util.DateFormatter;
import org.bodington.util.Visibility;
import org.bodington.util.ResourceBundleHelper;


/**
 * An object of this class
 * is constructed by the BuildingServlet system at initialisation and its insert 
 * method is called by BuildingServlet whenever a BUILDING tag is encountered in 
 * an HTML template that belongs to a messaging room.  This class provides the 
 * HTTP/HTML UI onto the org.bodington.messaging.MessagingRoomSession object within
 * the bodington system.
 * 
 * @author Jon Maber
 * @version 1.0
 */
public class MessagingRoomFacility extends org.bodington.servlet.facilities.Facility
	{
    
    private static Logger log = Logger.getLogger(MessagingRoomFacility.class);

    public boolean canCopy( Resource resource )
    {
        return true;
    }

	/**
	 * Called when a new messinging room is to be created via the servlet interface.
	 * This class instantiates the messaging room and sets some of its properties.
	 * 
	 * @return The MessagingRoom cast to a Resource.
	 */
	public Resource newResource()
		{
		MessagingRoom room = new MessagingRoom();
		room.setTypeOfRoom( MessagingRoom.TYPE_PLAIN );
		return room;
		}

	/**
	 * Checks HTTP form input to se if it is valid for creating a new
	 * Messaging Room.
	 * 
	 * @param breq The Building HTTP request.
	 * @param out An output stream for error messages.
	 * @return True if the user input is OK.
	 */
	public boolean createCheck( Request breq, PrintWriter out )
		{
		String type;

//		try
//			{
			type = breq.getParameter( "type" );
			if ( type==null )
				{
				out.print( "The type of messaging room was not specified." );
				return false;
				}
			
			if ( type.equalsIgnoreCase( "plain" ) )
				return true;
			if ( type.equalsIgnoreCase( "multiplexed" ) )
				return true;
			if ( type.equalsIgnoreCase( "personal" ) )
				return true;

			out.println( "An unknown type of messaging room was requested." );
			return false;
//			}
//		catch ( IOException ioex )
//			{
//			return false;
//			}		
		}

	/**
	 * Read HTTP form data to find out initial settings and set properties of
	 * the newly instantiated MessagingRoom.
	 * 
	 * @param breq The building HTTP request.
	 * @param new_resource The new resource.
	 * @return True if initialisation was OK.
	 * @exception java.lang.Exception Thrown if there is any problem initialising the resource.
	 */
	public boolean initResource( Request breq, Resource new_resource )
		throws Exception
		{
		String type;
		MessagingRoom room;
		
		if ( new_resource == null || !(new_resource instanceof MessagingRoom) )
			throw new Exception( "Unable to initialise resource - not a proper Messaging Room" );
		
		room = (MessagingRoom)new_resource;
		
		type = breq.getParameter( "type" );
		if ( type==null )
			throw new Exception( "The type of messaging room was not specified." );
			
		if ( type.equalsIgnoreCase( "plain" ) )
			{
			room.setTypeOfRoom( MessagingRoom.TYPE_PLAIN );
			return true;
			}
			
		if ( type.equalsIgnoreCase( "multiplexed" ) )
			{
			room.setTypeOfRoom( MessagingRoom.TYPE_MULTIPLEXED );
			return true;
			}

		if ( type.equalsIgnoreCase( "personal" ) )
			{
			room.setTypeOfRoom( MessagingRoom.TYPE_PERSONAL );
			return true;
			}
		
		throw new Exception( "An unknown type of messaging room was requested." );
		}

	public boolean initResource( Resource original_resource, Resource new_resource )
		throws BuildingServerException
		{
	         if ( !(new_resource instanceof MessagingRoom) )
		   throw new BuildingServerException( "Error: The wrong type of resource was created." );

	         int type = ((MessagingRoom)original_resource).getTypeOfRoom();
	         ((MessagingRoom)new_resource).setTypeOfRoom(type);

		 return true;
		}


	/* WebLearn modification (method added): 02/12/2003 Colin Tatham */
//	public void copyContent( Resource original_resource, Resource new_resource, Request breq )
//		throws BuildingServerException
//		{
//	  authored : nothing
//	  userdata : messages /** @todo  */
//
//		 BuildingSession session = BuildingSessionManagerImpl.getSession( original_resource );
//		 if ( !(session instanceof MessagingRoomSession) )
//		   throw new BuildingServerException( "Unable to access appropriate resource session." );
//
//                 if ( breq.getParameter( "userdata" ) != null )
//		   copyUserContent( original_resource, new_resource );
//	}


	public void insert( Request req, PrintWriter out, String command, String insertname )
		throws ServletException, IOException
		{

		log.debug( " AliasFacility insert()" );


		if ( !command.equalsIgnoreCase( "index" )   		&&
			 !command.equalsIgnoreCase( "header" )  		&&
			 !command.equalsIgnoreCase( "message" ) 		&&
			 !command.equalsIgnoreCase( "attachments" )		&&
			 !command.equalsIgnoreCase( "hiddenmessage" )   &&
			 !command.equalsIgnoreCase( "editform" )		&&
			 !command.equalsIgnoreCase( "postform" )		&&
			 !command.equalsIgnoreCase( "post" )    		&&
			 !command.equalsIgnoreCase( "savemessage" ) 	&&
			 !command.equalsIgnoreCase( "readers" ) 		&&
			 !command.equalsIgnoreCase( "participants" ) 	&&
			 !command.equalsIgnoreCase( "ifcanedit" )   			)
			{
			super.insert( req, out, command, insertname );
			return;
			}

		try
			{
			BuildingSession session;
			MessagingRoomSession mr_session;

		    session = BuildingSessionManagerImpl.getSession( req.getResource() );
		    if ( !(session instanceof MessagingRoomSession) )
		    	{
		    	out.println( "<HR>Technical problem: unable to access appropriate tool session.<HR>" );
		    	return;
		    	}
		    mr_session = (MessagingRoomSession)session;

			if ( command.equalsIgnoreCase( "index" ) )
		    	{
   				index( req, out, mr_session, insertname );
				return;
		    	}
			if ( command.equalsIgnoreCase( "header" ) )
		    	{
   				header( req, out, insertname, mr_session );
				return;
		    	}
			if ( command.equalsIgnoreCase( "message" ) )
		    	{
   				message( req, out, mr_session );
				return;
		    	}
			if ( command.equalsIgnoreCase( "attachments" ) )
		    	{
   				attachments( req, out, mr_session );
				return;
		    	}
			if ( command.equalsIgnoreCase( "hiddenmessage" ) )
		    	{
   				hiddenmessage( req, out, insertname, mr_session );
				return;
		    	}
			if ( command.equalsIgnoreCase( "editform" ) )
				{
				postform( req, out, mr_session, true );
				return;
				}
			if ( command.equalsIgnoreCase( "postform" ) )
				{
				postform( req, out, mr_session, false );
				return;
				}
			if ( command.equalsIgnoreCase( "post" ) )
				{
				post( req, out, mr_session );
				return;
				}
			if ( command.equalsIgnoreCase( "savemessage" ) )
				{
				savemessage( req, out, mr_session );
				return;
				}
			if ( command.equalsIgnoreCase( "readers" ) )
				{
				readers( req, out, insertname, mr_session );
				return;
				}
			if ( command.equalsIgnoreCase( "participants" ) )
				{
				participants( req, out, insertname, mr_session );
				return;
				}
			if ( command.equalsIgnoreCase( "ifcanedit" ) )
				{
				PrimaryKey id=null;
                                int param_count = req.getTemplateParameterCount();
                                if ( param_count == 1 || param_count == 2 )
                                        {
                                        try
                                                {
                                                id=new PrimaryKey( Integer.parseInt( (String)req.getTemplateParameter( param_count-1 ) ) );
						}
					catch ( NumberFormatException nfex )
						{
						req.setSwitchedOff(  true );
						return;
						}
					if ( !mr_session.containsMessage( id ) )
						{
						req.setSwitchedOff(  true );
						return;
						}
					}
				if ( id==null )	{ req.setSwitchedOff(  true ); return; }
				req.setSwitchedOff(  !mr_session.canEditMessage( id ) );
				return;
				}

		    }
		catch ( BuildingServerException bsex )
			{
			logException( out, "MessagingRoomFacility", "insert", 
			    "A technical problem occured.",
			    bsex );
			return;
			}
		catch ( RemoteException rex )
			{
			logException( out, "MessagingRoomFacility", "insert", 
			    "A technical problem occured.",
			    rex );
			return;
			}
		}


	/**
	 * Used to decide if a node in the message index is visible and/or closed.
	 * If a node is in the visibility hashtable that is used to decide.  If the
	 * node represents a new message it may not be in the table and this method
	 * works out what to do based on the visibility/openness of its parent node.
	 * 
	 * @param visibility_table A hashtable of org.bodington.util.Visibility objects keyed against
	 * message IDs.
	 * @param node The node to test.
	 * @return An object to indicate visibility/openness of the node.
	 */
	private Visibility getVisibility( Hashtable visibility_table, MessageTreeNode node, MessageTreeNode base_node )
		{
		Visibility visibility;
		MessageTreeNode parent;
		PrimaryKey mid, pmid;
		
		mid = node.getPrimaryKey();
                // null id indicates root of messaging room which will be
                // always visible and open
		if ( mid == null )
			return Visibility.VISIBLE_OPEN;
			
		visibility = (Visibility)visibility_table.get( mid );
		if ( visibility==null )
			{
			visibility = Visibility.INVISIBLE_CLOSED;
			}

                if ( base_node == null )
                    return visibility;
                
        // there is an override to visibility for new UI thread indexes
            if ( base_node.equals( node ) )
            {
                // if this node is the base node it is visible and open
                return Visibility.VISIBLE_OPEN;
            }
            else if ( base_node.isNodeAncestor( node ) )
            {
                // if this node comes above it should be visible and open
                visibility = Visibility.VISIBLE_OPEN;
            }
            else if ( base_node.isNodeChild( node ) )
            {
                // if this node is a direct child it must be visible but
                // may or may not be open
                if ( visibility.isOpen() )
                    return Visibility.VISIBLE_OPEN;
                else
                    return Visibility.VISIBLE_CLOSED;
            }
            else if ( !base_node.isNodeDescendant( node ) )
            {
                // if this node is an uncle, nephew or cousin of base....
                return Visibility.INVISIBLE_CLOSED;
            }
                
            // node is a descendant of the base but not a child
            // so visibility/openness doesn't need to be overridden
                
		return visibility;
		}

		
		
	private class IndexState
	{
	    Calendar whencreated, whenupdated, yesterday, lastweek;
        int date_format;
	    Hashtable visibility_table;
	    PrimaryKey selected_mid;
	    PrimaryKey base_mid;
            MessageTreeNode selected_node;
            MessageTreeNode base_node;
	    java.util.Date now;
            boolean show_root;
            int root_level;
            boolean one_frame;
            boolean date_in_title;
            boolean author_in_title;
            String index_page_name;
            boolean post_permission;
            boolean post_form_in_index;
            boolean number_messages;
            boolean link_in_title;
            boolean sub_threads;
            boolean flat;
            boolean expanders;
            ResourceBundleHelper resource_bundle_helper;
	}

	/**
	 * Outputs a tree diagram of the messages in the room.
	 * 
	 * @param req The building HTTP request.
	 * @param out The output stream.
	 * @param mr_session A MessagingRoomSession object for the current user.
	 * @exception java.io.IOException Thrown if there is a problem outputting the HTML.
	 * @exception BuildingServerException Thrown is there is a problem deciding what to output.
	 * @exception java.rmi.RemoteException Thrown if there is a problem accessing the Session remotely.
	 */
	private void index( Request req, PrintWriter out, MessagingRoomSession mr_session, String indextype )
		throws IOException, BuildingServerException, RemoteException
		{
                    int i;
		MessageTreeNode root_node, node=null;
		Enumeration enumeration;
		PrimaryKey jump_mid=null, base_mid=null;
		String strmid, op;

		if ( req.getTemplateParameterCount() >= 1 )
                {
                    try
                    {
                        base_mid=new PrimaryKey( Integer.parseInt( (String)req.getTemplateParameter( 0 ) ) );
                    }
                    catch ( NumberFormatException nfex )
                    {
                        base_mid = null;
                    }

                    if ( !mr_session.containsMessage( base_mid ) )
                    {
                        base_mid = null;
                    }
                }

                String frames = this.getPreference( req, "preference.messaging.frames" );
                String p1 = this.getPreference( req, "preference.messaging.date_in_title" );
                String p2 = this.getPreference( req, "preference.messaging.author_in_title" );
                String p3 = this.getPreference( req, "preference.messaging.number_messages" );
                String p4 = this.getPreference( req, "preference.messaging.date_style" );
                String p5 = this.getPreference( req, "preference.messaging.index_link_in_title" );
                String p6 = this.getPreference( req, "preference.messaging.allow_sub_threads" );
                String p7 = this.getPreference( req, "preference.messaging.expanders" );

		//before printing index see if a branch needs to be
		//opened or closed.
		strmid = req.getParameter( "message_id" );
		op = req.getParameter( "op" );
		log.debug( strmid );
		log.debug( op );
		
		if ( op!=null && op.length()>0 )
		{
                    try
                    {
                        jump_mid=new PrimaryKey( Integer.parseInt( strmid ) );
                        if ( jump_mid.intValue() <= 0 )
                            jump_mid = null;
                    }
                    catch ( NumberFormatException nfex )
                    {
                        jump_mid =null;
                    }

                    if ( op!=null && op.equalsIgnoreCase( "post" ) )
                        this.post( req, out, mr_session, true );
                    if ( op!=null && op.equalsIgnoreCase( "open" ) )
                        mr_session.openMessageBranch( jump_mid );
                    if ( op!=null && op.equalsIgnoreCase( "close" ) )
                        mr_session.closeMessageBranch( jump_mid );
                    if ( op!=null && op.equalsIgnoreCase( "select" ) )
                        mr_session.selectMessage( jump_mid );
                    if ( op!=null && op.equalsIgnoreCase( "select_and_simplify" ) )
                        mr_session.selectMessageAndSimplifyTree( jump_mid );
                    if ( op!=null && op.equalsIgnoreCase( "select_and_make_visible" ) )
                        mr_session.selectMessageAndMakeVisible( jump_mid );
		}
		
		if ( op!=null && op.equalsIgnoreCase( "openall" ) )
			mr_session.openAllMessageBranches();
		if ( op!=null && op.equalsIgnoreCase( "closeall" ) )
			mr_session.closeAllMessageBranches();
			

		IndexState state = new IndexState();
                
                state.flat = indextype != null && indextype.startsWith( "flat_" );
                
                state.resource_bundle_helper = 
                    ResourceBundleHelper.getResourceBundleHelper( "org.bodington.messaging.strings", 
                                            req.getHeader( "Accept-Language" ) );
                if ( "long".equalsIgnoreCase( p4 ) )
                    state.date_format = DateFormatter.DEFAULT;
                else if ( "medium".equalsIgnoreCase( p4 ) )
                    state.date_format = DateFormatter.MEDIUM;
                else
                    state.date_format = DateFormatter.SHORT;
                
                state.now=new java.util.Date();
		state.whencreated=Calendar.getInstance();
		state.whenupdated=Calendar.getInstance();
		state.yesterday=Calendar.getInstance();
		state.yesterday.setTime( state.now );
		state.yesterday.add( Calendar.DATE, -1 );
		state.lastweek=Calendar.getInstance();
		state.lastweek.setTime( state.now );
		state.lastweek.add( Calendar.DATE, -7 );

		root_node = mr_session.getMessageTree();
		state.visibility_table = mr_session.getVisibleMessages();
		state.base_mid = base_mid;
		state.selected_mid = mr_session.getSelectedMessage();
                if ( state.selected_mid == null )
                    state.selected_node = null;
                else
                    state.selected_node = (MessageTreeNode)root_node.findNode( state.selected_mid );
                if ( state.base_mid == null )
                    state.base_node = root_node;
                else
                    state.base_node = (MessageTreeNode)root_node.findNode( state.base_mid );
                state.show_root = "one".equals( frames );
                state.root_level = state.show_root?0:1;
                state.one_frame = "one".equals( frames );
                state.date_in_title = "yes".equalsIgnoreCase( p1 ) || "true".equalsIgnoreCase( p1 );
                state.author_in_title = "yes".equalsIgnoreCase( p2 ) || "true".equalsIgnoreCase( p2 );
                state.number_messages = "yes".equalsIgnoreCase( p3 ) || "true".equalsIgnoreCase( p3 );
                state.link_in_title = "yes".equalsIgnoreCase( p5 ) || "true".equalsIgnoreCase( p5 );
                state.sub_threads = "yes".equalsIgnoreCase( p6 ) || "true".equalsIgnoreCase( p6 );
                state.expanders = !("no".equalsIgnoreCase( p7 ) || "false".equalsIgnoreCase( p7 ));
                state.index_page_name = "one".equals( frames )?"bs_template_newcontents.html":"bs_template_contents.html";
                state.post_permission = BuildingContext.getContext().checkPermission( Permission.POST );
                state.post_form_in_index = state.one_frame && state.post_permission;

                
                if ( state.flat )
                {
                    out.print( "<p>" );
                    out.print( state.resource_bundle_helper.getString( "index_intro_" + indextype ) );
                    out.println( "</p>" );
                    int sort_type = MessageTreeNodeComparator.REVERSE_CREATED_DATE;
                    if ( "flat_created_date".equalsIgnoreCase( indextype ) )
                        sort_type = MessageTreeNodeComparator.CREATED_DATE;
                    if ( "flat_reverse_created_date".equalsIgnoreCase( indextype ) )
                        sort_type = MessageTreeNodeComparator.REVERSE_CREATED_DATE;
                    if ( "flat_updated_date".equalsIgnoreCase( indextype ) )
                        sort_type = MessageTreeNodeComparator.UPDATED_DATE;
                    if ( "flat_reverse_updated_date".equalsIgnoreCase( indextype ) )
                        sort_type = MessageTreeNodeComparator.REVERSE_UPDATED_DATE;
                    if ( "flat_author".equalsIgnoreCase( indextype ) )
                        sort_type = MessageTreeNodeComparator.AUTHOR;
                    if ( "flat_reverse_author".equalsIgnoreCase( indextype ) )
                        sort_type = MessageTreeNodeComparator.REVERSE_AUTHOR;
                    if ( "flat_subject".equalsIgnoreCase( indextype ) )
                        sort_type = MessageTreeNodeComparator.SUBJECT;
                    if ( "flat_reverse_subject".equalsIgnoreCase( indextype ) )
                        sort_type = MessageTreeNodeComparator.REVERSE_SUBJECT;
                    MessageTreeNode[] nodes = mr_session.getMessageTreeNodes( sort_type );
                    for ( i=0; i < nodes.length; i++ )
                    {
                        node = nodes[i];
                        state.root_level = node.getLevel();
                        if ( node.getLevel() > 0 )
                            indexNode( req, out, mr_session, node, state, true, Integer.toString( i ) );
                    }
                    
                    return;
                }
                
		
                if ( state.one_frame )
                {
                    out.print( "<p>" );
                    if ( state.selected_node == null || state.selected_node.getLevel() == 0 )
                        out.print( state.resource_bundle_helper.getString( "index_intro_top" ) );
                    else if ( state.selected_node.getLevel() == 1 )
                        out.print( state.resource_bundle_helper.getString( "index_intro_thread" ) );
                    else
                        out.print( state.resource_bundle_helper.getString( "index_intro_sub_thread" ) );
                    out.println( "</p>" );
                }
                
                if ( state.show_root )
                {
                    indexNode( req, out, mr_session, root_node, state, true, "" );
                }
                else
                {
                    enumeration = root_node.children();

                    for ( i=1; enumeration.hasMoreElements(); i++ )
                            {
                            node = (MessageTreeNode)enumeration.nextElement();
                            indexNode( req, out, mr_session, node, state, true, Integer.toString( i ) );
                            }
                }
		
        }

		
	/**
	 * Outputs formatted date.
	 * 
	 * @param out The output stream.
	 * @exception java.io.IOException Thrown if there is a problem outputting the HTML.
	 * @exception BuildingServerException Thrown is there is a problem deciding what to output.
	 * @exception java.rmi.RemoteException Thrown if there is a problem accessing the Session remotely.
	 */
    private void writeMessageDate( PrintWriter out, MessageSummary summary, IndexState state, boolean created, boolean comma )
	throws IOException, BuildingServerException, RemoteException
    {
        Timestamp ts;
        Calendar cal;
        
        if ( created )
        {
            ts = summary.getCreatedTime();
            cal = state.whencreated;
        }
        else
        {
            ts = summary.getUpdatedTime();
            cal = state.whenupdated;
        }
        
        cal.setTime( ts );
        if ( cal.after( state.yesterday ) )
            out.print( "<span class=\"bs-messaging-day\">" );
        else
            if ( cal.after( state.lastweek ) )
                out.print( "<span class=\"bs-messaging-week\">" );
            else
                out.print( "<span class=\"bs-messaging-date\">" );
        out.print( DateFormatter.formatDate( ts, state.date_format ) );
        if ( comma )
            out.print( ", " );
        //Facility.outputDate( out, ts, 1 );
        out.print( "</span>" );
    }
    
    private void writeTreeNodeIcon( Request req, PrintWriter out, IndexState state, MessageTreeNode node, PrimaryKey mid, int level, Visibility visibility )
	throws IOException
    {
        String alt;
        int n;
        
        if ( state.one_frame )
        {
            // if it is a thread index then no icon is needed on the nodes
            // that indicate the ancestors of the base node
            // NB that API considers a node is its own ancestor !!
            if (    !state.expanders || 
                    node.getChildCount()==0 || 
                    ( state.base_node != null && state.base_node.isNodeAncestor( node ) )
                )
                n=0;
            else if ( visibility.isOpen() ) n=1;
            else n=2;
        }
        else
        {
            if ( node.getChildCount()==0  ) n=0;
            else if ( visibility.isOpen() ) n=1;
            else n=2;
        }
        
	// choose icon depending on whether node is empty, closed or open
        // but no icon at all if showing root because that can't be
        // opened or closed.
        if ( level != 0 && !state.flat )
        {
            if ( n==0 )
            {
                alt = state.resource_bundle_helper.getString( "tree_node_empty_icon_alt" );
                writeTemplateGifTag( 
                    req, out, "", "img", "bs_template_tree_node_empty_22_22.gif", 
                    "border=\"0\" alt=\"" + alt + "\" class=\"tree_node_expander_icon\"" );
            }
            else
            {
                out.print( "<a href=\"" );
                //out.print( req.absoluteURL() );
                out.print( state.index_page_name );
                //out.print( mid.toString() );
                out.print( "?message_id=" );
                out.print( mid.toString() );
                if ( n==1 )
                {
                    alt = state.resource_bundle_helper.getString( "tree_node_open_icon_alt" );
                    out.print( "&op=close#" );
                    out.print( mid.toString() );
                    out.print( "\" onclick=\"storeScroll()\">" );
                    writeTemplateGifTag( 
                        req, out, "", "img", "bs_template_tree_node_open_22_22.gif", 
                        "alt=\"" + alt + "\" border=\"0\" class=\"tree_node_expander_icon\"" );
                }
                else
                {
                    alt = state.resource_bundle_helper.getString( "tree_node_closed_icon_alt" );
                    out.print( "&op=open#" );
                    out.print( mid.toString() );
                    out.print( "\" onclick=\"storeScroll()\">" );
                    writeTemplateGifTag( 
                        req, out, "", "img", "bs_template_tree_node_closed_22_22.gif", 
                        "alt=\"" + alt + "\" border=\"0\" class=\"tree_node_expander_icon\"" );
                }
                out.println( "</a>" );
            }
        }
    }

    
    
    private void writeMessageIcon( Request req, PrintWriter out, IndexState state, MessageTreeNode node, int level, boolean link_to_index, StringBuffer index_url )
	throws IOException
    {
        String alt;
    
	// the message icon
        if ( level == 0 )
            alt = state.resource_bundle_helper.getString( "tree_messaging_room_alt" );
        else
            if ( level == 1 || node.getChildCount()>0 )
                alt = state.resource_bundle_helper.getString( "tree_message_thread_alt" );
            else
                alt = state.resource_bundle_helper.getString( "tree_message_alt" );
        
        if ( link_to_index )
        {
            out.print( " <a href=\"" );
            out.print( index_url.toString() );
            out.print( "\" >" );
            if ( level == 0 )
                alt = alt + " " + state.resource_bundle_helper.getString( "command_index_room" );
            else
                if ( level == 1 )
                    alt = alt + " " + state.resource_bundle_helper.getString( "command_index_thread" );
                else if ( node.isLeaf() )
                    alt = alt + " " + state.resource_bundle_helper.getString( "command_index_message" );
                else
                    alt = alt + " " + state.resource_bundle_helper.getString( "command_index_sub_thread" );
        }

        if ( level == 0 )
            writeTemplateGifTag( 
                req, out, "", "img", "bs_template_iconsmall.gif", 
                "alt=\"" + alt + "\" border=\"0\" class=\"tree_node_icon\"" );
        else if ( level==1 || node.getChildCount()>0 )
            writeTemplateGifTag( 
                req, out, "", "img", "bs_template_thread.gif", 
                "alt=\"" + alt + "\" border=\"0\" class=\"tree_node_icon\"" );
        else
            writeTemplateGifTag( 
                req, out, "", "img", "bs_template_message.gif", 
                "alt=\"" + alt + "\" border=\"0\" class=\"tree_node_icon\"" );
        
        if ( link_to_index )
            out.print( "</a>" );

    }

    
    
    private void writeMessageContent( Request req, PrintWriter out, MessagingRoomSession mr_session, IndexState state, MessageTreeNode node, MessageSummary summary, PrimaryKey mid, int level, int heading_level, boolean detailed, String item, boolean link_to_index, StringBuffer index_url )
	throws IOException, BuildingServerException
    {
        if ( summary==null )
        {
            out.print( "<p>" );
            out.print( state.resource_bundle_helper.getString( "error_no_message_summary" ) );
            out.println( "</p>" );
            return;
        }
        
        if ( level == 0 )
        {
            out.print( "<h" + heading_level + ">" );
            //if ( state.number_messages )
            //{
            //    out.print( "<span class=\"bs-messaging-number\">" );
            //    out.print( state.resource_bundle_helper.getString( "number_root" ) );
            //    out.print( "</span>" );
            //}
            if ( state.selected_mid==null )
                out.print( "<strong>" );
            out.print( summary.getSubject() );
            if ( state.link_in_title && link_to_index )
                out.print( ", " );
            if ( state.selected_mid==null )
                out.print( "</strong>" );
            if ( state.link_in_title && link_to_index )
            {
                out.print( "<span class=\"bs-messaging-number\">" );
                out.print( " <a href=\"" );
                out.print( req.absoluteURL() );
                out.print( state.index_page_name );
                out.print( "?op=select_and_simplify\">" );
                out.print( state.resource_bundle_helper.getString( "command_index_room" ) );
                out.print( "</a>" );
                out.print( "</span>" );
            }
            out.println( "</h" + heading_level + ">" );

            if ( detailed )
            {
                out.print( "<div class=\"bs-messaging-introduction\">" );
                out.print( mr_session.getMessageText( null, "text/html" ) );
                out.println( "</div>" );
            }
            return;
        }
        
        // message subject as heading with link to message
        out.println( "<h" + heading_level + ">" );
        out.print( "<a name=\"" );
        out.print( mid.toString() );
        out.println( "\"></a>" );

        out.print( "<span class=\"bs-messaging-title\">" );

        if ( state.number_messages )
        {
            out.print( "<span class=\"bs-messaging-number\">" );
            //out.print( state.resource_bundle_helper.getString( "number_level" ) );
            //out.print( level );
            out.print( state.resource_bundle_helper.getString( "number_item" ) );
            out.print( item );
            out.print( ", " );
            out.print( "</span>" );
        }

        if ( state.date_in_title )
        {
            writeMessageDate( out, summary, state, false, true );
        }
        if ( state.author_in_title )
        {
            out.print( "<span class=\"bs-messaging-author\">" );
            out.print( summary.getAuthorName() );
            out.print( ", </span> " );
        }

        if ( !state.one_frame )
        {
            out.print( "<a target=\"message\" href=\"" );
            out.print( req.absoluteURL() );
            out.print( mid.toString() );
            out.print( "/bs_template_message.html\" onclick=\"refreshIndex( " );
            out.print( mid.toString() );
            out.print( " )\">" );
        }

        if ( state.selected_mid!=null && state.selected_mid.equals( mid ) )
            out.print( "<strong>" );
        out.print( summary.getSubject() );
        if ( state.link_in_title && link_to_index )
            out.print( ", " );
        if ( state.selected_mid!=null && state.selected_mid.equals( mid ) )
            out.print( "</strong>" );

        if ( !state.one_frame  )
            out.print( "</a>" );

        if ( state.link_in_title && link_to_index )
        {
            out.print( "<span class=\"bs-messaging-number\">" );
            out.print( " <a href=\"" );
            out.print( index_url.toString() );
            out.print( "\" >" );
            if ( level > 1 )
            {
                if ( node.isLeaf() )
                    out.print( state.resource_bundle_helper.getString( "command_index_message" ) );
                else
                    out.print( state.resource_bundle_helper.getString( "command_index_sub_thread" ) );
            }
            else
                out.print( state.resource_bundle_helper.getString( "command_index_thread" ) );
            out.print( "</a>" );
            out.print( "</span>" );  // end of bs-messaging-number
        }

        out.println( "</span>" );   // end of bs-messaging-title
        out.println( "</h" + heading_level + ">" );

        boolean extra_info = false;

        // other descriptive text...
        if ( !detailed )
            return;
        
        out.println( "<div class=\"bs-messaging-detail\">" );
        if ( !state.author_in_title )
        {
            extra_info = true;
            out.print( "<p>" );
            out.print( "<span class=\"bs-messaging-author\">" );
            out.print( summary.getAuthorName() );
            out.println( "</span>" );
        }

        if ( !summary.getCreatedTime().equals( summary.getUpdatedTime() ) )
        {
            if ( extra_info )
                out.println( "<br />" );
            else
            {
                out.println( "<p>" );
                extra_info = true;
            }
            out.print( "<span class=\"bs-messaging-date\">" );
            if ( state.one_frame )
                out.print( state.resource_bundle_helper.getString( "date_started" ) );
            else
                out.print( state.resource_bundle_helper.getString( "date_range_intro" ) );
            out.print( "</span>" );
            writeMessageDate( out, summary, state, true, false );
            out.print( " <span class=\"bs-messaging-date\">" );
            if ( state.one_frame )
                out.print( state.resource_bundle_helper.getString( "date_replied" ) );
            else
                out.print( state.resource_bundle_helper.getString( "date_range_and" ) );
            out.print( "</span> " );
            writeMessageDate( out, summary, state, false, false );
        }
        else
        {
            if ( !state.date_in_title )
            {
                if ( extra_info )
                    out.println( "<br />" );
                else
                {
                    out.println( "<p>" );
                    extra_info = true;
                }
            writeMessageDate( out, summary, state, true, false );
            }
        }

        if ( extra_info )
            out.println( "</p>" );

        if ( state.one_frame )
        {
            String text = mr_session.getMessageTextSample( node.getPrimaryKey(), 100 );
            out.print( "<div class=\"bs-messaging-sample\">" );
            out.print( state.resource_bundle_helper.getString( "message_sample" ) );
            out.print( "<span class=\"bs-messaging-sample-text\">" );
            out.println( text );
            out.println( ".....</span></div>" );  // end of bs-messaging-sample



            if ( !state.flat )
            {
                out.print( "<div class=\"bs-messaging-commands\">" );
                out.print( "<span class=\"bs-messaging-command\">" );
                out.print( "<a href=\"" );
                out.print( mid.toString() );
                out.print( "/bs_template_messagetext.html\">" );
                out.print( state.resource_bundle_helper.getString( "command_view_full_text" ) );
                out.print( "</a></span>" );
                out.print( "<span class=\"bs-messaging-command\">" );
                out.print( "<a href=\"" );
                out.print( mid.toString() );
                out.print( "/bs_template_newreaders.html\">" );
                out.print( state.resource_bundle_helper.getString( "command_readers" ) );
                out.print( "</a></span>" );
                if ( mr_session.canEditMessage( mid ) )
                {
                    out.print( "<span class=\"bs-messaging-command\">" );
                    out.print( "<a href=\"" );
                    out.print( mid.toString() );
                    out.print( "/bs_template_editmessage.html\">" );
                    out.print( state.resource_bundle_helper.getString( "command_edit" ) );
                    out.print( "</a></span>" );
                }
                if ( state.sub_threads && level>1 && node.isLeaf() )
                {
                    out.print( "<span class=\"bs-messaging-command\">" );
                    out.print( "<a href=\"" );
                    out.print( req.absoluteURL() );
                    out.print( mid.toString() );
                    out.print( "/" );
                    out.print( state.index_page_name );
                    out.print( "?message_id=" );
                    out.print( mid.toString() );
                    out.print( "&op=select_and_simplify\" onclick=\"storeScroll()\">" );
                    out.print( state.resource_bundle_helper.getString( "command_convert_subthread" ) );
                    out.print( "</a></span>" );
                }
                out.println( "</div>" );  // end of bs-messaging-commands\
            }
        }
        out.println( "</div>" );  // end of bs-messaging-detail
    }        
    
    
    private void writePostForm( Request req, PrintWriter out, IndexState state, 
        MessageSummary summary, PrimaryKey mid, int level, int heading_level, int next_level_mod, 
        boolean last_child_invisible, String quote_text, String author_name )
	throws IOException, BuildingServerException
    {
        String alt;
        String form_name = state.resource_bundle_helper.getString( "post_form_name" );

	out.println( "<script language=javascript>" );
        hiddenmessage( out, "hiddenmessage", quote_text, author_name );
	out.println( "" );
	out.println( "function doit()" );
	out.println( "   {" );
	out.println( "   if ( document.forms[0].subject.value.length==0 )" );
	out.println( "	{" );
	out.println( "	alert( \"You need to enter a subject for your message. (Dismiss this box first.)\" );" );
	out.println( "	document.forms[0].subject.focus();" );
	out.println( "	return false;" );
	out.println( "	}" );
	out.println( "   if ( document.forms[0].message.value.length==0 )" );
	out.println( "	{" );
	out.println( "	alert( \"You need to enter your message. (Dismiss this box first.)\" );" );
	out.println( "	document.forms[0].message.focus();" );
	out.println( "	return false;" );
	out.println( "	}" );
	out.println( "   return true;" );
	out.println( "   }" );
	out.println( "   " );
	out.println( "function quote()" );
	out.println( "	{" );
	out.println( "	var q;" );
	out.println( "	" );
	out.println( "	if ( document.forms[0].message.value.length == 0 )" );
	out.println( "		q = authorname;" );
	out.println( "	else" );
	out.println( "		{" );
	out.println( "		q = document.forms[0].message.value;" );
	out.println( "		q += authorname;" );
	out.println( "		}" );
	out.println( "		" );
	out.println( "	q += \" said:\\n\\n\";" );
	out.println( "	q += hiddenmessage;" );
	out.println( "	q += \"\\n\\n\";" );
	out.println( "	" );
	out.println( "	document.forms[0].message.value =q;" );
	out.println( "	document.forms[0].message.focus();" );
	out.println( "	document.forms[0].message.select();" );
	out.println( "	return;" );
	out.println( "	}" );
	out.println( "	" );
	out.println( "</script>" );
        
        
        
        if ( last_child_invisible )
            out.println( "<div class=\"tree_node_syucdnbn\">" );
        else
            out.println( "<div class=\"tree_node_syuydnbn\">" );
        alt = state.resource_bundle_helper.getString( "tree_node_empty_icon_alt" );
        writeTemplateGifTag( 
            req, out, "", "img", "bs_template_tree_node_empty_22_22.gif", 
            "border=\"0\" alt=\"" + alt + "\" class=\"tree_node_expander_icon\"" );
        alt = state.resource_bundle_helper.getString( "tree_post_message_alt" );
        writeTemplateGifTag( 
            req, out, "", "img", "bs_template_message.gif", 
            "alt=\"" + alt + "\" class=\"tree_node_icon\"" );
        out.print( "<div class=\"tree_node_content_" );
        out.print( next_level_mod );
        out.println( "\">" );
        out.print( "<span class=\"bs-messaging-title\">" );
        out.print( "<h" + heading_level + ">" );
        if ( level == 0 )
            out.print( state.resource_bundle_helper.getString( "post_new_thread_heading" ) );
        else
            out.print( state.resource_bundle_helper.getString( "post_in_thread_heading" ) );
        out.println( "</h" + heading_level + ">" );
        out.println( "</span>" );

        out.print( "<p>" );
        if ( level == 0 )
            out.print( state.resource_bundle_helper.getString( "post_new_thread_advice" ) );
        else
            out.print( state.resource_bundle_helper.getString( "post_in_thread_advice" ) );
        out.print( "</p>" );

        out.print( "<form name=\"" );
        out.print( form_name );
        out.print( "\" onsubmit=\"return doit()\" method=\"POST\" action=\"bs_template_newcontents.html\" enctype=\"multipart/form-data\">" );
        out.println( "<input type=\"HIDDEN\" name=\"op\" value=\"post\" />" );
        if ( mid !=null )
            out.println( "<input type=\"HIDDEN\" name=\"inreplyto\" value=\"" + mid + "\" />" );
        out.println( "<input type=\"HIDDEN\" name=\"branch\" VALUE=\"yes\" />" );

        out.print( "<label for=\"subject\"><em>" );
        out.print( state.resource_bundle_helper.getString( "post_form_subject" ) );
        out.print( "</em></label><input name=\"subject\" id=\"subject\" " );
        if ( summary!=null && summary.getSubject()!=null )
        {
            out.print( "value=\"" );
            // guess subject from thread subject but leave
            // blank if this is new top level thread
            if ( level > 0 )
            {
                //remove the quotes and new lines from subject
                StringTokenizer tok;
                tok = new StringTokenizer( summary.getSubject(), "\"\n\r" );
                while ( tok.hasMoreElements() )
                    out.print( tok.nextToken() );
            }
            out.print( "\"" );
        }
        out.println( " size=\"40\"><br />" );

        out.print( "<fieldset><legend><em>" );
        out.print( state.resource_bundle_helper.getString( "post_form_message_style" ) );
        out.print( "</em></legend> " );

        out.print( "<label for=\"style_1\"><em> " );
        out.print( state.resource_bundle_helper.getString( "post_form_plain_text" ) );
        out.print( " </em></label> " );
        out.print( "<input type=\"radio\" id=\"style_1\" name=\"style\" value=\"0\" />" );

        out.print( "<label for=\"style_2\"><em> " );
        out.print( state.resource_bundle_helper.getString( "post_form_smart_text" ) );
        out.print( " </em></label> " );
        out.print( "<input type=\"radio\" id=\"style_2\" name=\"style\" value=\"1\" checked=\"yes\" />" );

        out.print( "<label for=\"style_3\"><em> " );
        out.print( state.resource_bundle_helper.getString( "post_form_html" ) );
        out.print( " </em></label> " );
        out.print( "<input type=\"radio\" id=\"style_3\" name=\"style\" value=\"2\" />" );

        out.print( "</fieldset><br />" );

        out.print( "<label for=\"message\"><em>" );
        out.print( state.resource_bundle_helper.getString( "post_form_message" ) );
        out.println( "</em></label><br />" );
        out.print( "<span class=\"bs-textarea\"><textarea id=\"message\" name=\"message\" cols=\"100\" rows=\"10\" wrap=\"SOFT\">" );
        out.println( "</textarea></span><br />" );

        out.print( "<label for=\"file\"><em>" );
        out.print( state.resource_bundle_helper.getString( "post_form_file" ) );
        out.println( "</em></label> " );
        out.print( "<input type=\"file\" id=\"file\" name=\"file\" /><br />" );

        if ( quote_text != null )
        {
            out.print( "<input type=\"BUTTON\" value=\"" );
            out.print( state.resource_bundle_helper.getString( "post_form_quote_button" ) );
            out.println( "\" onclick=\"quote()\" />" );
        }

        out.print( "<input type=\"SUBMIT\" value=\"" );
        out.print( state.resource_bundle_helper.getString( "post_form_submit_button" ) );
        out.println( "\" />" );
        out.println( "</form>" );
        out.println( "</div>" );
        out.println( "</div>" );
    }    

    
    private void nodeClassCode( 
        StringBuffer class_code, IndexState state, int level, 
        boolean invisible_previous_sibling, boolean invisible_next_sibling, 
        boolean invisible_first_child,
        boolean end_sibling, boolean visible_children )
    {
        if ( level==0 )
            class_code.append( "sn" );
        else
            class_code.append( "sy" );

        if ( invisible_previous_sibling )
            class_code.append( "uc" );
        else if ( level==state.root_level )
            class_code.append( "un" );
        else
            class_code.append( "uy" );

        if ( invisible_next_sibling )
            class_code.append( "dc" );
        else if ( end_sibling )
            class_code.append( "dn" );
        else
            class_code.append( "dy" );
        
        if ( visible_children )
        {
            if ( invisible_first_child )
                class_code.append( "bc" );
            else
                class_code.append( "by" );
        }
        else
            class_code.append( "bn" );
        
        if ( state.flat )
        {
            class_code.setLength( 0 );
            class_code.append( "snundnbn" );
        }
    }    
    
	/**
	 * Outputs a node and its children from a tree diagram of the messages in the room.
	 * 
	 * @param req The building HTTP request.
	 * @param out The output stream.
	 * @param mr_session A MessagingRoomSession object for the current user.
	 * @exception java.io.IOException Thrown if there is a problem outputting the HTML.
	 * @exception BuildingServerException Thrown is there is a problem deciding what to output.
	 * @exception java.rmi.RemoteException Thrown if there is a problem accessing the Session remotely.
	 */
    private void indexNode( Request req, PrintWriter out, MessagingRoomSession mr_session, MessageTreeNode node, IndexState state, boolean end_sibling, String item )
	throws IOException, BuildingServerException, RemoteException
	{
	MessageSummary summary;
	PrimaryKey mid;
        MessageTreeNode previous_sibling, next_sibling, parent;
	Visibility visibility, sibling_visibility, child_visibility;
	int level, level_mod, next_level_mod, heading_level, next_heading_level;
	boolean visible_children;
        boolean invisible_previous_sibling, invisible_next_sibling, invisible_first_child;
        boolean extra_info, show_post_form, detailed, link_to_index;
        String alt;
        StringBuffer index_url = new StringBuffer();

        // null mid means root node
	mid = node.getPrimaryKey();

        detailed =  !state.one_frame ||
                     state.flat ||
                     state.base_node == null || 
                     node.equals( state.base_node ) ||  
                     node.isNodeAncestor( state.base_node );
        
	visibility = getVisibility( state.visibility_table, node, state.base_node );
        
	if ( !state.flat && !visibility.isVisible() )
	    return;

	level = node.getLevel();
        
	// the "root" of all messages represents the tool itself
	if ( level == 0 && !state.show_root )
	    return;
        
        // choose heading level - rotates from 1 to 10
        level_mod = ((level-state.root_level) % 10)+1;
        next_level_mod = ((level-state.root_level+1) % 10)+1;
        heading_level = level-state.root_level+1;
        next_heading_level = heading_level+1;
        if ( heading_level > 6 )
            heading_level = 6;
        if ( next_heading_level > 6 )
            next_heading_level = 6;
        
        if ( mid == null )
            show_post_form = state.post_form_in_index && state.base_mid==null;
        else
            show_post_form = state.post_form_in_index && mid.equals( state.base_mid );
        
	visible_children = node.getChildCount() > 0 && visibility.isOpen();
        if ( show_post_form )
            visible_children = true;
        
        parent = (MessageTreeNode)node.getParent();
        // find out if there is a previous sibling but it is not visible
        previous_sibling = (MessageTreeNode)node.getPreviousSibling();
        sibling_visibility = previous_sibling==null?null:getVisibility(state.visibility_table, previous_sibling, state.base_node );
        invisible_previous_sibling = sibling_visibility != null && !sibling_visibility.isVisible();
        // find out if there is a previous sibling but it is not visible
        next_sibling = (MessageTreeNode)node.getNextSibling();
        sibling_visibility = next_sibling==null?null:getVisibility(state.visibility_table, next_sibling, state.base_node );
        invisible_next_sibling = sibling_visibility != null && !sibling_visibility.isVisible();
        

        invisible_first_child = false;
        if ( node.getChildCount() > 0 )
        {
            MessageTreeNode child = (MessageTreeNode)node.getChildAt( 0 );
            child_visibility = child==null?null:getVisibility(state.visibility_table, child, state.base_node );
            invisible_first_child = child_visibility != null && !child_visibility.isVisible();
        }

        link_to_index = false;
        if ( state.one_frame && (level <=1 || !node.isLeaf() || state.flat ) )
        {
            if ( level == 0 )
                link_to_index = true;
            if ( level > 0 )
                link_to_index = state.flat || state.base_mid==null || !state.base_mid.equals( mid );
        }
        
        
        index_url.append( req.absoluteURL() );
        if ( level>0 )
        {
            if ( node.isLeaf() && level>1 )
                index_url.append( parent.getPrimaryKey().toString() );
            else
                index_url.append( mid.toString() );
            index_url.append( "/" );
        }
        index_url.append( state.index_page_name );
        index_url.append( "?" );
        if ( level>0 )
        {
            index_url.append( "message_id=" );
            if ( node.isLeaf() && level>1 )
                index_url.append( parent.getPrimaryKey().toString() );
            else
                index_url.append( mid.toString() );
            index_url.append( "&" );
        }
        index_url.append( "op=select_and_simplify" );
        if ( state.flat && level>0 )
        {
            index_url.append( "#" );
            index_url.append( mid.toString() );
        }
        
        
        
        // treat each level 1 message like a root if not showing root
	if ( level==state.root_level || state.flat )
	{
	    if ( /*node.getChildCount()==0 ||*/ level == 0 || state.flat )
		out.println( "<div class=\"tree_without_stalk\">" );
	    else
                out.println( "<div class=\"tree\">" );
	}


	// if there are children this is more than a node - its a branch
	if ( visible_children )
	    {
	    if ( end_sibling || invisible_next_sibling )
		out.println( "<div class=\"tree_end_branch\">" );
	    else
		out.println( "<div class=\"tree_branch\">" );
	    }

	// start the node itself
        // the node class is selected using a code which is also
        // used by the style sheet to select a background GIF
        StringBuffer class_code = new StringBuffer( 10 );
        nodeClassCode( class_code, state, level, invisible_previous_sibling, 
                invisible_next_sibling, invisible_first_child, 
                end_sibling, visible_children );
	out.print( "<div class=\"tree_node_" );
        out.print( class_code.toString() );
        out.println( "\">" );


        writeTreeNodeIcon( req, out, state, node, mid, level, visibility );

        writeMessageIcon( req, out, state, node, level, link_to_index, index_url );
        
        
	// start text content of node
	if ( level==0 /*|| (level==state.root_level && node.getChildCount()==0)*/ || state.flat )
            out.print( "<div class=\"tree_node_content_1_without_stalk\">" );
        else
        {
            out.print( "<div class=\"tree_node_content_" );
            out.print( level_mod );
            out.println( "\">" );
        }

        summary = mr_session.getMessageSummary( mid );
        writeMessageContent( req, out, mr_session, state, node, summary, mid, 
                level, heading_level, detailed, item, link_to_index, index_url );

	out.println( "</div>" );  // end of tree_node_content_...
	out.println( "</div>" );  // end of tree_node_...
	
	if ( !state.flat && visible_children )
	{
            if ( level == 0 )
                out.print( "<div class=\"tree_siblings_2_without_stalk\">" );
            else
            {
                out.print( "<div class=\"tree_siblings_" );
                out.print( next_level_mod );
                out.println( "\">" );
            }
	    Enumeration enumeration = node.children();
            boolean last_child_invisible = false;
            String item_base = item;
            if ( item.length() > 0 )
                item_base = item + ".";
            for ( int i=1; enumeration.hasMoreElements(); i++ )
	    {
		MessageTreeNode child = (MessageTreeNode)enumeration.nextElement();
                visibility = getVisibility( state.visibility_table, child, state.base_node );
                if ( visibility.isVisible() )
                    indexNode( req, out, mr_session, child, state, !enumeration.hasMoreElements() && !show_post_form, item_base + i );
                if ( !enumeration.hasMoreElements() && !visibility.isVisible() )
                    last_child_invisible = true;
	    }
            
            if ( show_post_form )
            {
                String quote_text = null;
                String author_name = null;
                if ( state.selected_mid != null )
                {
                    quote_text = mr_session.getMessageText( state.selected_mid, "text/plain" );
                    MessageSummary sel_summary = mr_session.getMessageSummary( state.selected_mid );
                    if ( sel_summary != null )
                        author_name = sel_summary.getAuthorName();
                }
                writePostForm(req, out, state, summary, mid, level, next_heading_level, next_level_mod, last_child_invisible, quote_text, author_name );
            }
            
	    out.println( "</div>" ); // end of tree_siblings_...
	}
	
	if ( visible_children )
	    out.println( "</div>" );  // end of tree_...branch

	if ( level == state.root_level )
	    out.println( "</div>" );  // end of tree
	

    }

    
    public void printLinkToIndex( Request req, PrintWriter out )
    {
        String frames = this.getPreference( req, "preference.messaging.frames" );
        boolean oneframe = "one".equalsIgnoreCase( frames );
        String p = this.getPreference( req, "preference.messaging.jump_to_message" );
        boolean jump = "yes".equalsIgnoreCase( p ) || "true".equalsIgnoreCase( p );

        PrimaryKey id = null, base_id = null;
        int param_count = req.getTemplateParameterCount();
        if ( param_count == 1 || param_count == 2 )
        {
            try
            {
                id=new PrimaryKey( Integer.parseInt( (String)req.getTemplateParameter( param_count-1 ) ) );
                if ( param_count == 2 )
                    base_id=new PrimaryKey( Integer.parseInt( (String)req.getTemplateParameter( 0 ) ) );
            }
            catch ( NumberFormatException nfex )
                {
                    // don't put number in URL after all
                }
        }
        
        
        out.print( "<a href=\"" );
        out.print( req.absoluteURL() );
        if ( oneframe )
        {
            if ( base_id != null )
            {
                out.print( base_id.toString() );
                out.print( "/" );
            }
            out.print( "bs_template_newcontents.html" );
            
            
            out.print( "?" );
            if ( id != null )
            {
                out.print( "message_id=" );
                out.print( id.toString() );
                out.print( "&" );
            }
            out.print( "op=select_and_make_visible" );

            
            if ( jump && id != null )
            {
                out.print( "#" );
                out.print( id.toString() );
            }
            out.print( "\">" );
        }
        else
        {
            out.print( "bs_template_main.html\"" );
            if ( id != null )
            {
                out.print( " onclick=\"refresh('" );
                out.print( id.toString() );
                out.print( "')\"" );
            }
            out.print( ">" );
        }
        ResourceBundleHelper  resource_bundle_helper = 
            ResourceBundleHelper.getResourceBundleHelper( "org.bodington.messaging.strings", 
                                    req.getHeader( "Accept-Language" ) );
        out.print( resource_bundle_helper.getString( "link_to_index" ) );
        out.print( "</a>" );
    }
    
	/**
	 * Outputs the text of a specific message.
	 * 
	 * @param req The building HTTP request.
	 * @param out The output stream.
	 * @param mr_session A MessagingRoomSession object for the current user.
	 * @exception java.io.IOException Thrown if there is a problem outputting the HTML.
	 * @exception BuildingServerException Thrown is there is a problem deciding what to output.
	 * @exception java.rmi.RemoteException Thrown if there is a problem accessing the Session remotely.
	 */
	private void message( Request req, PrintWriter out,
							MessagingRoomSession mr_session )
		throws IOException, BuildingServerException, RemoteException
		{
		String text;
		StringTokenizer tok;
		//Message m;
		PrimaryKey id=null;
		String content;
                
                int param_count = req.getTemplateParameterCount();
		if ( param_count == 1 || param_count == 2 )
			{
			try
				{
				id=new PrimaryKey( Integer.parseInt( (String)req.getTemplateParameter( param_count-1 ) ) );
				}
			catch ( NumberFormatException nfex )
				{
				out.println( "<HR>Invalid URL (Number expected)</HR>" );
				return;
				}

			if ( !mr_session.containsMessage( id ) )
				{
				out.println( "<HR>Message not found in this room.</HR>" );
				return;
				}
			}

		if ( id==null )
			return;

		text = mr_session.getMessageText( id, "text/html" );
		if ( text!=null )
			out.print( text );
		
		return;
		}

	/**
	 * Outputs list of attached files.
	 * 
	 * @param req The building HTTP request.
	 * @param out The output stream.
	 * @param mr_session A MessagingRoomSession object for the current user.
	 * @exception java.io.IOException Thrown if there is a problem outputting the HTML.
	 * @exception BuildingServerException Thrown is there is a problem deciding what to output.
	 * @exception java.rmi.RemoteException Thrown if there is a problem accessing the Session remotely.
	 */
	private void attachments( Request req, PrintWriter out,
							MessagingRoomSession mr_session )
		throws IOException, BuildingServerException, RemoteException, ServletException
		{
		String text;
		StringTokenizer tok;
		//Message m;
		PrimaryKey id=null;
		String content;

                int param_count = req.getTemplateParameterCount();
		if ( param_count == 1 || param_count == 2 )
			{
			try
				{
				id=new PrimaryKey( Integer.parseInt( (String)req.getTemplateParameter( param_count-1 ) ) );
				}
			catch ( NumberFormatException nfex )
				{
				out.println( "<HR>Invalid URL (Number expected)</HR>" );
				return;
				}

			if ( !mr_session.containsMessage( id ) )
				{
				out.println( "<HR>Message not found in this room.</HR>" );
				return;
				}
			}

		if ( id==null )
			return;

		fileindex( req, out, "/" + id.toString() + "/", false, true, 0 );
		
		return;
		}


	/**
	 * Outputs the text of a specific message as a JavaScript variable.  This is
	 * used to hide the text of a message that can be trasfered into a TEXTAREA
	 * using JavaScript.
	 * 
	 * @param req The building HTTP request.
	 * @param out The output stream.
	 * @param insertname The name of the javascript variable.
	 * @param mr_session A MessagingRoomSession object for the current user.
	 * @exception java.io.IOException Thrown if there is a problem outputting the HTML.
	 * @exception BuildingServerException Thrown is there is a problem deciding what to output.
	 * @exception java.rmi.RemoteException Thrown if there is a problem accessing the Session remotely.
	 */
	private void hiddenmessage( Request req, PrintWriter out,
							String insertname,
							MessagingRoomSession mr_session )
		throws IOException, BuildingServerException, RemoteException
		{
		String text=null;
                String authorname=null;
		StringTokenizer tok;
		//Message m;
		PrimaryKey id=null;
		String part;
		MessageSummary summary=null;


                int param_count = req.getTemplateParameterCount();
		if ( param_count == 1 || param_count == 2 )
			{
			try
				{
				id=new PrimaryKey( Integer.parseInt( (String)req.getTemplateParameter( param_count-1 ) ) );
				}
			catch ( NumberFormatException nfex )
				{
				id=null;
				text = "Invalid URL (Number expected)";
				}

			if ( id!=null && !mr_session.containsMessage( id ) )
				{
				id=null;
				text = "Message not found in this room.";
				}
			}

		if ( id!=null )
			{
			text = mr_session.getMessageText( id, "text/plain" );
			summary = mr_session.getMessageSummary( id );
			}
			
		if ( text==null )
			text = "No referenced message.";
			

		if ( summary!=null && summary.getAuthorName()!=null )
                            authorname = summary.getAuthorName();
		out.println( "<SCRIPT LANGUAGE=JavaScript>" );
                hiddenmessage( out, insertname, text, authorname );
		out.println( "</SCRIPT>" );
        }            
                
	private void hiddenmessage( PrintWriter out, String variable, String text, String authorname )
		throws IOException
		{
		char c;
		int i, j;
                
                
                if ( authorname != null )
                {
			out.print( "var authorname=\"" );
			out.print( authorname );
			out.println( "\";" );
			}

                if ( variable == null || text == null )
                    return;
                
		out.print( "var " );
		out.print( variable );
		out.print( " = " );
			
		for ( i=0; i<text.length(); i+=50 )
			{
			out.print( "\"" );
			
			for ( j=i; j<text.length() && (j-i)<50; j++ )
				{
				c = text.charAt( j );
				switch ( c )
					{
					case '\r':
						if ( text.charAt( j+1 ) != '\n' )
							out.print( "\\n" );
						break;
					case '\n':
						out.print( "\\n" );
						break;
					case '\t':
						out.print( "\\t" );
						break;
					default:
						out.print( c );
						break;
					}
				}
			
			out.println( "\" + " );
			}
		out.println( "\"\"" );

		
		return;
		}





	/**
	 * Outputs a property.
	 * e.g. subject,  of a specific message.
	 * 
	 * @param req The building HTTP request.
	 * @param out The output stream.
	 * @param fieldname The name of the message property.
	 * @param mr_session A MessagingRoomSession object for the current user.
	 * @exception java.io.IOException Thrown if there is a problem outputting the HTML.
	 * @exception BuildingServerException Thrown is there is a problem deciding what to output.
	 * @exception java.rmi.RemoteException Thrown if there is a problem accessing the Session remotely.
	 */
	private void header( Request req, PrintWriter out,
							String fieldname,
							MessagingRoomSession mr_session )
		throws IOException, BuildingServerException, RemoteException
		{
		MessageSummary summary;
		StringTokenizer tok;
		//Message m;
		PrimaryKey id=null;
		String content;

                int param_count = req.getTemplateParameterCount();
		if ( param_count == 1 || param_count == 2 )
			{
			try
				{
				id=new PrimaryKey( Integer.parseInt( (String)req.getTemplateParameter( param_count-1 ) ) );
				}
			catch ( NumberFormatException nfex )
				{
				out.println( "<HR>Invalid URL (Number expected)</HR>" );
				return;
				}

			if ( !mr_session.containsMessage( id ) )
				{
				out.println( "<HR>Message not found in this room.</HR>" );
				return;
				}
			}

		if ( id==null )
			return;

		summary = mr_session.getMessageSummary( id );
		if ( summary==null )
			return;
		
		if ( fieldname.equalsIgnoreCase( "subject" ) )
			{
			out.print( summary.getSubject() );
			return;
			}
			
		if ( fieldname.equalsIgnoreCase( "author_name" ) )
			{
			String new_name=null;
			String old_name = summary.getAuthorName();
			User user = User.findUser( summary.getUserId() );
			if ( user!=null )
				new_name = user.getName();
				
			if ( new_name!=null && !new_name.equalsIgnoreCase( old_name ) )
				{
				out.print( summary.getAuthorName() );
				out.print( " (now known as " );
				out.print( new_name );
				out.print( ")" );
				}
			else
				out.print( summary.getAuthorName() );
			return;
			}
			
		if ( fieldname.equalsIgnoreCase( "created_time" ) )
			{
			DateFormatter.outputDate( out, summary.getCreatedTime(), 2 );
			return;
			}
			
                        /*
                         * WebLearn inserted code; A Corfield 02/12/2003.
                         */
                        if (fieldname.equalsIgnoreCase("history")) {
                          if (summary.getEditorName() != null && summary.getEditedTime() != null) {
                            out.print( "(revised " );
                            out.print( summary.getEditorName() );
                            out.print( ", " );
                            DateFormatter.outputDate(out, summary.getEditedTime(), 2);
                            out.print( ")" );
                          }
                          return;
                        }

			
		return;
		}



	/**
	 * Outputs a form for posting/editing a message.
	 * 
	 * @param req The building HTTP request.
	 * @param out The output stream.
	 * @param mr_session A MessagingRoomSession object for the current user.
	 * @param editform Whether an editing form or a posting form is required.
	 * @exception java.io.IOException Thrown if there is a problem outputting the HTML.
	 * @exception BuildingServerException Thrown is there is a problem deciding what to output.
	 * @exception java.rmi.RemoteException Thrown if there is a problem accessing the Session remotely.
	 */
	private void postform( Request req, PrintWriter out,
							MessagingRoomSession mr_session, boolean editform )
		throws IOException, BuildingServerException, RemoteException
		{
		StringTokenizer tok;
		MessageSummary m=null;
		MessageSummary replytom=null;
		PrimaryKey id=null;
		String content;
                
                String frames = this.getPreference( req, "preference.messaging.frames" );
                boolean one_frame = "one".equals( frames );

                int param_count = req.getTemplateParameterCount();
		if ( param_count == 1 || param_count == 2 )
			{
			try
				{
				id=new PrimaryKey( Integer.parseInt( (String)req.getTemplateParameter( param_count-1 ) ) );
				}
			catch ( NumberFormatException nfex )
				{
				out.println( "<HR>Invalid URL (Number expected)</HR>" );
				return;
				}

			if ( !mr_session.containsMessage( id ) )
				{
				out.println( "<HR>Message not found in this room.</HR>" );
				return;
				}
				
			m = mr_session.getMessageSummary( id );
			}
		//else
		//	m=null;

		// m==null indicates new thread m!=null indicates reply or edit

		if ( id==null )
			{
			if ( editform )
				{
				out.println( "<P>No message selected for editing.</P>" );
				return;
				}
			}

		if ( editform )
			out.println( "<FORM NAME=messageform ONSUBMIT=\"return doit()\" METHOD=POST ACTION=bs_template_confirmedit.html ENCTYPE=\"multipart/form-data\">" );
		else
			out.println( "<FORM NAME=messageform ONSUBMIT=\"return doit()\" METHOD=POST ACTION=bs_template_confirmpost.html ENCTYPE=\"multipart/form-data\">" );

		if ( id!=null )
			out.println( "<INPUT TYPE=HIDDEN NAME=inreplyto VALUE=" + id + ">" );


		if ( m!=null )
		{
                        // the new index page makes it easy to see where message is going to
                        // appear so the branch/follow option is only relevant for the
                        // old interface and will be phased out.
                        if ( editform )
                        {
                            if ( m.getReplyToMessageId() !=null )
                            {
                                replytom = mr_session.getMessageSummary( m.getReplyToMessageId() );
                                if ( replytom.getParentMessageId() == null )
                                    out.println( "<INPUT TYPE=\"HIDDEN\" NAME=\"branch\" VALUE=\"yes\">" );
                                else
                                {
                                    //branching option needs to be presented with corrent
                                    //selection
                                    boolean branched = m.getReplyToMessageId().equals( m.getParentMessageId() );
                                    out.print( "<INPUT TYPE=\"HIDDEN\" NAME=\"branch\" VALUE=\"" );
                                    out.print( branched?"yes":"no" );
                                    out.println( "\">" );
                                }
                            }
                        }
                        else
                        {
				if ( !one_frame )
				{
				if ( m.getParentMessageId() == null )
					{
					//when replying to top level message you have to branch off
					out.println( "<INPUT TYPE=HIDDEN NAME=branch VALUE=yes>" );
					}
				else
					{
					//if the selected message was not a top level message
					//you can follow it on or branch off.
					out.println( "<B>Follow On or Branch:</B><BR>" );
					out.println( "<TABLE><TR><TD VALIGN=TOP>" );
					out.println( "<INPUT TYPE=RADIO NAME=branch VALUE=no CHECKED>Follow<BR>" );
					out.println( "<INPUT TYPE=RADIO NAME=branch VALUE=yes>Branch" );
					out.println( "</TD><TD>" );
					out.println( "Normally select <I>Follow</I>. Select Branch if you want to start a new  " );
					out.println( "sub-topic that relates to something in the message you selected to reply to. " );
					out.println( "</TD></TR></TABLE>" );
					}
				}
                        }
		}


		out.print( "<B>Subject:</B><BR><INPUT NAME=subject " );
		if ( m!=null && m.getSubject()!=null )
			{
			out.print( "VALUE=\"" );
			//remove the quotes and new lines from subject
			tok = new StringTokenizer( m.getSubject(), "\"\n\r" );
			while ( tok.hasMoreElements() )
				out.print( tok.nextToken() );
			out.print( "\"" );
			}
		out.println( " SIZE=40><BR>" );

		out.println( "<B>Message Style:</B><BR>" );
		out.print( "<INPUT TYPE=RADIO NAME=style VALUE=\"0\" " );
		if ( editform && m!=null && m.getStyle()==Message.STYLE_PLAINTEXT )
			out.print( "CHECKED" );
		out.println( ">Plain Text<BR>" );
		out.print( "<INPUT TYPE=RADIO NAME=style VALUE=\"1\" " );
		if ( m==null || (editform && m!=null && m.getStyle()==Message.STYLE_SMARTTEXT) )
			out.print( "CHECKED" );
		out.println( ">Smart Text<BR>" );
		out.print( "<INPUT TYPE=RADIO NAME=style VALUE=\"2\" " );
		if ( editform && m!=null && m.getStyle()==Message.STYLE_HTML )
			out.print( "CHECKED" );
		out.println( ">HTML<BR>" );
		out.print( "<B>Message:</B><BR><SPAN CLASS=bs-textarea><TEXTAREA NAME=message COLS=75 ROWS=10 WRAP=SOFT>" );


		//put text of message into box if this is an editing form
		if ( m!=null && editform )
			{
			//null mime type to get text just as entered.
			String text = mr_session.getMessageText( id, null );
			if ( text!=null )
				out.print( text );
			}
			

		out.println( "</TEXTAREA></SPAN><BR>" );

		
		//put originator name and replyto message text
		//in hidden form fields if appropriate
		if ( !editform && m!=null )
			{
			out.print( "<INPUT TYPE=HIDDEN NAME=replyto_author VALUE=\"" );
			out.print( m.getAuthorName() );
			out.println( "\">" );
			}

		if ( editform )
			{
			out.println( "<B><INPUT TYPE=SUBMIT VALUE=\"Save\"></B>" );
			out.println( "<INPUT TYPE=BUTTON VALUE=\"Restore Original\" ONCLICK=\"messageform.message.value=hiddenmessage\">" );
			}
		else
			{
			if ( req.getResource().checkPermission( Permission.UPLOAD ) )
				out.println( "Attach file: <INPUT name=file TYPE=FILE><BR>" );
			out.println( "<B><INPUT TYPE=SUBMIT VALUE=\"Post Now\"></B>" );
			if ( m!=null )
				out.println( "<INPUT TYPE=BUTTON VALUE=\"Quote Message\" ONCLICK=\"quote()\">" );
			}
		out.println( "<INPUT TYPE=BUTTON VALUE=\"Cancel\" ONCLICK=\"history.back()\">" );

		out.println( "</FORM></P>" );
		}


	/**
	 * Posts a message to the room and outputs a confirmation or an error message.
	 * 
	 * @param req The building HTTP request.
	 * @param out The output stream.
	 * @param mr_session A MessagingRoomSession object for the current user.
	 * @exception java.io.IOException Thrown if there is a problem outputting the HTML.
	 * @exception BuildingServerException Thrown is there is a problem deciding what to output.
	 * @exception java.rmi.RemoteException Thrown if there is a problem accessing the Session remotely.
	 */
	private void post( Request req, PrintWriter out,
							MessagingRoomSession mr_session   )
		throws IOException, BuildingServerException, RemoteException
		{
                    post( req, out, mr_session, false );
                }
        
	/**
	 * Posts a message to the room and outputs a confirmation or an error message.
	 * 
	 * @param req The building HTTP request.
	 * @param out The output stream.
	 * @param mr_session A MessagingRoomSession object for the current user.
	 * @exception java.io.IOException Thrown if there is a problem outputting the HTML.
	 * @exception BuildingServerException Thrown is there is a problem deciding what to output.
	 * @exception java.rmi.RemoteException Thrown if there is a problem accessing the Session remotely.
	 */
	private void post( Request req, PrintWriter out,
			   MessagingRoomSession mr_session, boolean one_frame   )
		throws IOException, BuildingServerException, RemoteException
		{
		String subject, message, style, inreplyto, branch, file, file_name, mime_type;
		int istyle;
		boolean bbranch;
		PrimaryKey id=null;
                MessageSummary summary;

		inreplyto=req.getParameter( "inreplyto" );
		if ( inreplyto!=null && inreplyto.length()>0 )
			{
			try
				{
				id=new PrimaryKey( Integer.parseInt( inreplyto ) );
				}
			catch ( NumberFormatException nfex )
				{
				out.println( "<p>Invalid message ID (Number expected)</p>" );
                                if ( !one_frame )
                                {
                                    out.println( "<A HREF=void(1) ONCLICK=back()>Back to message.</A><BR>" );
                                    out.println( "<A HREF=bs_template_main.html>Cancel.</A>" );
                                }
				return;
				}

			if ( !mr_session.containsMessage( id ) )
				{
				out.println( "<p>Message not found in this room.</p>" );
                                if ( !one_frame )
                                {
				out.println( "<A HREF=void(1) ONCLICK=back()>Back to message.</A><BR>" );
				out.println( "<A HREF=bs_template_main.html>Cancel.</A>" );
                                }
				return;
				}
			}
		subject=req.getParameter( "subject" );
		if ( subject!=null )
			subject = subject.trim();
		if ( subject==null || subject.length()==0 )
			subject = "(no subject)";

		message=req.getParameter( "message" );
		if ( message!=null )
			message=message.trim();
		if ( message==null || message.length()==0 )
			{
			out.println( "<p>The message was NOT posted because you didn't enter any text.</p>" );
                                if ( !one_frame )
                                {
                                    out.println( "<A HREF=void(1) ONCLICK=back()>Back to message.</A><BR>" );
                                    out.println( "<A HREF=bs_template_main.html>Cancel.</A>" );
                                }
			return;
			}

		istyle = Message.STYLE_SMARTTEXT;
		style=req.getParameter( "style" );
		if ( style!=null )
			{
			if ( style.equals( "0" ) )
				istyle = Message.STYLE_PLAINTEXT;
			if ( style.equals( "2" ) )
				istyle = Message.STYLE_HTML;
			}


		branch = req.getParameter( "branch" );
		if ( branch==null )
			bbranch = true;
		else
			bbranch = branch.equalsIgnoreCase( "yes" );

		id = mr_session.postMessage( subject, message, istyle, id, bbranch );
                if ( !one_frame )
                {
                    summary = mr_session.getMessageSummary( id );
                    mr_session.openMessageBranch( summary.getParentMessageId() );
                    mr_session.selectMessage( id );
                }
	
		file = req.getParameter( "file" );
		file_name = req.getParameterFileName( "file" );
		try
			{
			if ( file!=null && file_name!=null )
				{
        		StringBuffer dest_filename=new StringBuffer();

				if ( file_name.indexOf( '/' )>=0 )
					{
					out.println( "<p>File names may not contain any slash characters.  File attachment failed.</p>" );
					return;
					}
			    dest_filename.append( id.toString() );
			    dest_filename.append( "/" );
				dest_filename.append( UploadedFile.nameToURL( file_name ) );

				mime_type=req.getServletContext().getMimeType( file_name );
				if ( mime_type==null )
				    mime_type="application/octet-stream";
				mr_session.getUploadedFileSession().transferFile( file,  dest_filename.toString(), mime_type );
				out.println( "<p>File attachment succeeded.</p>" );
				}
			}
        catch (BuildingServerUserException bsue)
        {
            out.println("<p>Failed to uploaded file</p>");
            out.println( bsue.getMessage() );
        }
		catch ( Exception ex )
			{
			logException( out, "MessagingRoomFacility", "post", 
			    "A technical problem occured.",
			    ex );
			}
		
		out.println( "<p>Message posted and added to message index.</p>" );
                if ( !one_frame )
                {
                    out.print( "<P>Return to <A HREF=\"" );
                    out.print( req.absoluteURL() );
                    out.print( "bs_template_main.html\" ONCLICK=\"refresh('" );
                    out.print( id.toString() );
                    out.println( "')\">index</A>.</P>" );
                }
	}

	/**
	 * Saves changes to a message and outputs a confirmation or error message.
	 * 
	 * @param req The building HTTP request.
	 * @param out The output stream.
	 * @param mr_session A MessagingRoomSession object for the current user.
	 * @exception java.io.IOException Thrown if there is a problem outputting the HTML.
	 * @exception BuildingServerException Thrown is there is a problem deciding what to output.
	 * @exception java.rmi.RemoteException Thrown if there is a problem accessing the Session remotely.
	 */
	private void savemessage( Request req, PrintWriter out,
							MessagingRoomSession mr_session   )
		throws IOException, BuildingServerException, RemoteException
		{
		String subject, message, style, inreplyto, branch;
		int istyle;
		boolean bbranch;
		PrimaryKey id=null;

		//not really a reply but the form is the same as the posting form
		inreplyto=req.getParameter( "inreplyto" );
		if ( inreplyto!=null && inreplyto.length()>0 )
			{
			try
				{
				id=new PrimaryKey( Integer.parseInt( inreplyto ) );
				}
			catch ( NumberFormatException nfex )
				{
				out.println( "<HR>Invalid message ID (Number expected)</HR>" );
				out.println( "<A HREF=void(1) ONCLICK=back()>Back to message.</A><BR>" );
                                this.printLinkToIndex( req, out );
				return;
				}

			if ( !mr_session.containsMessage( id ) )
				{
				out.println( "<HR>Message not found in this room.</HR>" );
				out.println( "<A HREF=void(1) ONCLICK=back()>Back to message.</A><BR>" );
                                this.printLinkToIndex( req, out );
				return;
				}
			}
		if ( id==null )
			{
			out.println( "<HR>Technical problem - message ID not specified.</HR>" );
			out.println( "<A HREF=void(1) ONCLICK=back()>Back to message.</A><BR>" );
                                this.printLinkToIndex( req, out );
			return;
			}
		if ( !mr_session.canEditMessage( id ) )
			{
			out.println( "<HR>Sorry, the message can't be edited because it has been posted and read by others.</HR>" );
			out.println( "<A HREF=void(1) ONCLICK=back()>Back to message.</A><BR>" );
                                this.printLinkToIndex( req, out );
			return;
			}
			
		subject=req.getParameter( "subject" );
		if ( subject!=null )
			subject = subject.trim();
		if ( subject==null || subject.length()==0 )
			subject = "(no subject)";

		message=req.getParameter( "message" );
		if ( message!=null )
			message=message.trim();
		if ( message==null || message.length()==0 )
			{
			out.println( "The message was NOT saved because you didn't enter any text." );
			out.println( "<A HREF=void(1) ONCLICK=back()>Back to message.</A><BR>" );
                                this.printLinkToIndex( req, out );
			return;
			}

		istyle = Message.STYLE_SMARTTEXT;
		style=req.getParameter( "style" );
		if ( style!=null )
			{
			if ( style.equals( "0" ) )
				istyle = Message.STYLE_PLAINTEXT;
			if ( style.equals( "2" ) )
				istyle = Message.STYLE_HTML;
			}


		branch = req.getParameter( "branch" );
		if ( branch==null )
			bbranch = true;
		else
			bbranch = branch.equalsIgnoreCase( "yes" );

		id = mr_session.saveMessage( subject, message, istyle, id, bbranch );
		
		out.println( "<P>Message saved.</P>" );
                out.print( "<p>" );
                this.printLinkToIndex( req, out );
                out.println( "</p>" );
		//out.print( "<P>Return to <A HREF=bs_template_main.html ONCLICK=\"refresh('" );
		//out.print( id.toString() );
		//out.println( "')\">index</A>.</P>" );
		}

	/**
	 * Lists readers of a particular message.
	 * 
	 * @param req The building HTTP request.
	 * @param out The output stream.
	 * @param style A string indicating which type of reader list is required.
	 * @param mr_session A MessagingRoomSession object for the current user.
	 * @exception java.io.IOException Thrown if there is a problem outputting the HTML.
	 * @exception BuildingServerException Thrown is there is a problem deciding what to output.
	 * @exception java.rmi.RemoteException Thrown if there is a problem accessing the Session remotely.
	 */
	private void readers( Request req, PrintWriter out, String style, 
							MessagingRoomSession mr_session   )
		throws IOException, BuildingServerException, RemoteException
		{
		PrimaryKey id=null;
		Vector list;
		
                int param_count = req.getTemplateParameterCount();
		if ( param_count == 1 || param_count == 2 )
			{
			try
				{
				id=new PrimaryKey( Integer.parseInt( (String)req.getTemplateParameter( param_count-1 ) ) );
				}
			catch ( NumberFormatException nfex )
				{
				out.println( "<HR>Invalid URL (Number expected)</HR>" );
				return;
				}

			if ( !mr_session.containsMessage( id ) )
				{
				out.println( "<HR>Message not found in this room.</HR>" );
				return;
				}
			}

		if ( id==null )
			{
			out.println( "<P>Can't list readers - no message specified.</P>" );
			return;
			}


		if ( style.equalsIgnoreCase( "nonreaders" ) )
			list = mr_session.getNonReaders( id );
		else
			list = mr_session.getReaders( id, style.equalsIgnoreCase( "date" ) );
			
		String reader;
		for ( int i=0; i<list.size(); i++ )
			{
			reader = (String)list.elementAt( i );
			out.print( reader );
			out.println( "<BR>" );
			}
		}


	/**
	 * Lists participants for the room.
	 * 
	 * @param req The building HTTP request.
	 * @param out The output stream.
	 * @param style A string indicating which type of participant list is required.
	 * @param mr_session A MessagingRoomSession object for the current user.
	 * @exception java.io.IOException Thrown if there is a problem outputting the HTML.
	 * @exception BuildingServerException Thrown is there is a problem deciding what to output.
	 * @exception java.rmi.RemoteException Thrown if there is a problem accessing the Session remotely.
	 */
	private void participants( Request req, PrintWriter out, String style, 
							MessagingRoomSession mr_session   )
		throws IOException, BuildingServerException, RemoteException
		{
		int i;
		int s = MessagingParticipantStyle.PARTICIPANTS_CAN_VIEW;
		
		if ( style.equalsIgnoreCase( "canpost" ) )
			s = MessagingParticipantStyle.PARTICIPANTS_CAN_POST;
			
		if ( style.equalsIgnoreCase( "readers" ) )
			s = MessagingParticipantStyle.PARTICIPANTS_READERS;
			
		if ( style.equalsIgnoreCase( "posters" ) )
			s = MessagingParticipantStyle.PARTICIPANTS_POSTERS;
			
		if ( style.equalsIgnoreCase( "nonreaders" ) )
			s = MessagingParticipantStyle.PARTICIPANTS_NON_READERS;
			
		if ( style.equalsIgnoreCase( "nonposters" ) )
			s = MessagingParticipantStyle.PARTICIPANTS_NON_POSTERS;
			
		Vector list = mr_session.getParticipants( s );
		String reader;
		boolean in_users=false;
		boolean in_groups=false;
		for ( i=0; i<list.size(); i++ )
			{
			reader = (String)list.elementAt( i );
			if ( reader.equals( "$group" ) )
				{
				if ( in_users )
					{
					out.println( "</TABLE>" );
					in_users=false;
					}
				if ( !in_groups )
					{
					out.println( "<TABLE CLASS=bs-table-opaque><TR>" );
					out.println( "<TD CLASS=bs-cell-special><B>Group</B></TD>" );
					out.println( "<TD CLASS=bs-cell-special><B>Description</B></TD></TR>" );
					in_groups=true;
					}
				out.println( "<TR><TD>" );
				out.println( (String)list.elementAt( ++i ) );
				out.println( "</TD><TD>" );
				out.println( (String)list.elementAt( ++i ) );
				out.println( "</TD></TR>" );
				}
			else
				{
				if ( in_groups )
					{
					out.println( "</TABLE>" );
					in_groups=false;
					}
				if ( !in_users )
					{
					out.println( "<TABLE CLASS=bs-table-opaque><TR>" );
					out.println( "<TD CLASS=bs-cell-special><B>Participants</B></TD></TR>" );
					in_users=true;
					}
				out.print( "<TR><TD>" );
				out.print( reader );
				out.println( "</TD></TR>" );
				}
			}
		if ( in_groups || in_users )
			out.println( "</TABLE>" );
		else
			out.println( "<P>No participants of this type were found.</P>" );
		}

	}


