/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
package org.dspace.app.xmlui.aspect.administrative.group;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import org.dspace.app.xmlui.aspect.administrative.FlowGroupUtils;
import org.dspace.app.xmlui.cocoon.AbstractDSpaceTransformer;
import org.dspace.app.xmlui.wing.Message;
import org.dspace.app.xmlui.wing.WingException;
import org.dspace.app.xmlui.wing.element.Body;
import org.dspace.app.xmlui.wing.element.Cell;
import org.dspace.app.xmlui.wing.element.Division;
import org.dspace.app.xmlui.wing.element.Highlight;
import org.dspace.app.xmlui.wing.element.PageMeta;
import org.dspace.app.xmlui.wing.element.Para;
import org.dspace.app.xmlui.wing.element.Row;
import org.dspace.app.xmlui.wing.element.Table;
import org.dspace.app.xmlui.wing.element.Text;
import org.dspace.authorize.AuthorizeManager;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;

/**
 * Present the user with the group's current state. The user may select to 
 * change the group's name, OR search for new epeople / groups to add, OR select 
 * current group members for removal.
 * 
 * @author Alexey Maslov
 * @author Scott Phillips
 */
public class EditGroupForm extends AbstractDSpaceTransformer   
{
	/** Language Strings */
	private static final Message T_dspace_home =
		message("xmlui.general.dspace_home");

	private static final Message T_group_trail =
		message("xmlui.administrative.group.general.group_trail");
	
	private static final Message T_title = 
		message("xmlui.administrative.group.EditGroupForm.title");

	private static final Message T_trail = 
		message("xmlui.administrative.group.EditGroupForm.trail");
	
	private static final Message T_main_head = 
		message("xmlui.administrative.group.EditGroupForm.main_head");
	
	private static final Message T_collection_para = 
		message("xmlui.administrative.group.EditGroupForm.collection_para");

    private static final Message T_community_para = 
        message("xmlui.administrative.group.EditGroupForm.community_para");

	private static final Message T_label_name = 
		message("xmlui.administrative.group.EditGroupForm.label_name");
	
	private static final Message T_label_instructions = 
		message("xmlui.administrative.group.EditGroupForm.label_instructions");

	private static final Message T_label_search = 
		message("xmlui.administrative.group.EditGroupForm.label_search");

	private static final Message T_submit_search_people = 
		message("xmlui.administrative.group.EditGroupForm.submit_search_people");

	private static final Message T_submit_search_groups = 
		message("xmlui.administrative.group.EditGroupForm.submit_search_groups");

	private static final Message T_no_results =
		message("xmlui.administrative.group.EditGroupForm.no_results");
	
	private static final Message T_main_head_new =
		message("xmlui.administrative.group.EditGroupForm.main_head_new");
	
	private static final Message T_submit_clear =
		message("xmlui.administrative.group.EditGroupForm.submit_clear");
	
	private static final Message T_submit_save =
		message("xmlui.general.save");
	
	private static final Message T_submit_cancel =
		message("xmlui.general.cancel");
	
	private static final Message T_member =
		message("xmlui.administrative.group.EditGroupForm.member");
	
	private static final Message T_cycle =
		message("xmlui.administrative.group.EditGroupForm.cycle");
	
	private static final Message T_pending =
		message("xmlui.administrative.group.EditGroupForm.pending");
	
	private static final Message T_pending_warn =
		message("xmlui.administrative.group.EditGroupForm.pending_warn");
	
	private static final Message T_submit_add =
		message("xmlui.administrative.group.EditGroupForm.submit_add");
	
	private static final Message T_submit_remove =
		message("xmlui.administrative.group.EditGroupForm.submit_remove");
	
	
	// EPeople Search
	private static final Message T_epeople_column1 =
		message("xmlui.administrative.group.EditGroupForm.epeople_column1");
	
	private static final Message T_epeople_column2 =
		message("xmlui.administrative.group.EditGroupForm.epeople_column2");

	private static final Message T_epeople_column3 =
		message("xmlui.administrative.group.EditGroupForm.epeople_column3");

	private static final Message T_epeople_column4 =
		message("xmlui.administrative.group.EditGroupForm.epeople_column4");
	
	// Group Search
	private static final Message T_groups_column1 =
		message("xmlui.administrative.group.EditGroupForm.groups_column1");
	
	private static final Message T_groups_column2 =
		message("xmlui.administrative.group.EditGroupForm.groups_column2");

	private static final Message T_groups_column3 =
		message("xmlui.administrative.group.EditGroupForm.groups_column3");

	private static final Message T_groups_column4 =
		message("xmlui.administrative.group.EditGroupForm.groups_column4");

	private static final Message T_groups_column5 =
		message("xmlui.administrative.group.EditGroupForm.groups_column5");
	
	private static final Message T_groups_collection_link = 
		message("xmlui.administrative.group.ManageGroupsMain.groups_collection_link");
	
	// Members
	private static final Message T_members_head =
		message("xmlui.administrative.group.EditGroupForm.members_head");
	
	private static final Message T_members_column1 =
		message("xmlui.administrative.group.EditGroupForm.members_column1");
	
	private static final Message T_members_column2 =
		message("xmlui.administrative.group.EditGroupForm.members_column2");

	private static final Message T_members_column3 =
		message("xmlui.administrative.group.EditGroupForm.members_column3");

	private static final Message T_members_column4 =
		message("xmlui.administrative.group.EditGroupForm.members_column4");

	private static final Message T_members_group_name =
		message("xmlui.administrative.group.EditGroupForm.members_group_name");	
	
	private static final Message T_members_pending = 
		message("xmlui.administrative.group.EditGroupForm.members_pending");
	
	private static final Message T_members_none =
		message("xmlui.administrative.group.EditGroupForm.members_none");
	
	
	// How many results to show on a page.
	private static final int RESULTS_PER_PAGE = 5;
	
	/** The maximum size of a collection name allowed */
	private static final int MAX_COLLECTION_NAME = 25;
	
	
	public void addPageMeta(PageMeta pageMeta) throws WingException
    {
        pageMeta.addMetadata("title").addContent(T_title);
        pageMeta.addTrailLink(contextPath + "/", T_dspace_home);
        pageMeta.addTrailLink(contextPath + "/admin/groups",T_group_trail);
        pageMeta.addTrail().addContent(T_trail);
    }
	
	
	public void addBody(Body body) throws WingException, SQLException 
	{		
		// Find the group in question
		int groupID = parameters.getParameterAsInteger("groupID",-1);
		String currentName = decodeFromURL(parameters.getParameter("groupName",null));
		if (currentName == null || currentName.length() == 0)
        {
            currentName = FlowGroupUtils.getName(context, groupID);
        }
		
		Group group = null;
		if (groupID >= 0)
        {
            group = Group.find(context, groupID);
        }

		// Find the collection or community if applicable
		Collection collection = null;
		Community community = null;
		if (group != null)
		{
			int collectionID = FlowGroupUtils.getCollectionId(group.getName());
			if (collectionID > -1)
            {
                collection = Collection.find(context, collectionID);
            }
			else
			{
			    int communityID = FlowGroupUtils.getCommunityId(group.getName());
			    if (communityID > -1)
                {
                    community = Community.find(context, communityID);
                }
		    }
		}
		
		// Get list of member groups
		String memberGroupIDsString = parameters.getParameter("memberGroupIDs",null);
		List<Integer> memberGroupIDs = new ArrayList<Integer>();
		if (memberGroupIDsString != null)
		{
			for (String id : memberGroupIDsString.split(","))
			{
				if (id.length() > 0)
                {
                    memberGroupIDs.add(Integer.valueOf(id));
                }
			}
		}
	    
		// Get list of member epeople
		String memberEPeopleIDsString = parameters.getParameter("memberEPeopleIDs",null);
		List<Integer> memberEPeopleIDs = new ArrayList<Integer>();
		if (memberEPeopleIDsString != null)
		{
			for (String id : memberEPeopleIDsString.split(","))
            {
				if (id.length() > 0)
                {
					memberEPeopleIDs.add(Integer.valueOf(id));
                }
            }
		}
		
		// Get highlight parameters
		int highlightEPersonID = parameters.getParameterAsInteger("highlightEPersonID",-1);
		int highlightGroupID = parameters.getParameterAsInteger("highlightGroupID",-1);
		
		// Get search parameters
	    String query = decodeFromURL(parameters.getParameter("query",null));
	    int page     = parameters.getParameterAsInteger("page",0);
	    String type  = parameters.getParameter("type",null);
	    
	    // Get any errors
	    String errorString = parameters.getParameter("errors",null);
		List<String> errors = new ArrayList<String>();
		if (errorString != null)
        {
			for (String error : errorString.split(","))
            {
				errors.add(error);
            }
        }
	    
	    //DIVISION: group-edit
	    Division main = body.addInteractiveDivision("group-edit",contextPath+"/admin/groups",Division.METHOD_POST,"primary administrative groups");
	    if (group == null)
        {
            main.setHead(T_main_head_new);
        }
	    else
        {
            main.setHead(T_main_head.parameterize(group.getName(), groupID));
        }
	 
	    
	    if(collection != null)
	    {
	    	Para para = main.addPara();
	    	para.addContent(T_collection_para);
	    	para.addXref(contextPath+"/handle/"+collection.getHandle(), collection.getMetadata("name"));
	    }
	    else if(community != null)
        {
            Para para = main.addPara();
            para.addContent(T_community_para);
            para.addXref(contextPath+"/handle/"+community.getHandle(), community.getMetadata("name"));
        }
	   
	    // DIVISION: group-actions
	    Division actions = main.addDivision("group-edit-actions");
	    Para groupName = actions.addPara();
        groupName.addContent(T_label_name);
        Text groupText = groupName.addText("group_name");
        groupText.setValue(currentName);
        if(collection != null || community != null)
        {
        	// If this group is associated with a collection or community then it is special,
        	// thus they shouldn't be able to update it.
        	groupText.setDisabled();
        	groupText.setHelp(T_label_instructions);
        }
        else if (errors.contains("group_name") || errors.contains("group_name_duplicate"))
        {
            groupText.addError("");
        }
        

        Para searchBoxes = actions.addPara();
        searchBoxes.addContent(T_label_search);
        Text queryField = searchBoxes.addText("query");
        queryField.setValue(query);
        queryField.setSize(15);
        searchBoxes.addButton("submit_search_epeople").setValue(T_submit_search_people);
        searchBoxes.addButton("submit_search_groups").setValue(T_submit_search_groups);
		
        
        if (query != null)
        {
	        if ("eperson".equals(type))
	        {
	        	searchBoxes.addButton("submit_clear").setValue(T_submit_clear);
	        	addEPeopleSearch(main,query,page,group,memberEPeopleIDs);
	        }
	        else if ("group".equals(type))
	        {
	        	searchBoxes.addButton("submit_clear").setValue(T_submit_clear);
        		addGroupSearch(main,group,query,page,group,memberGroupIDs);
	        }
        }
                     
        boolean changes = false;
        if (group != null)
        {
            changes = addMemberList(main, group, memberGroupIDs, memberEPeopleIDs, highlightEPersonID, highlightGroupID);
        }
        
               
    	Para buttons = main.addPara();
    	buttons.addButton("submit_save").setValue(T_submit_save);
    	buttons.addButton("submit_cancel").setValue(T_submit_cancel);
       
    	if (changes)
        {
            main.addPara().addHighlight("warn").addContent(T_pending_warn);
        }
    	
        
        main.addHidden("administrative-continue").setValue(knot.getId());
	}
	
	
	
	/**
	 * Search for epeople to add to this group.
	 */
	private void addEPeopleSearch(Division div, String query, int page, Group group, List<Integer> memberEPeopleIDs) throws SQLException, WingException
	{
		int resultCount = EPerson.searchResultCount(context, query);
        EPerson[] epeople = EPerson.search(context, query, page*RESULTS_PER_PAGE, RESULTS_PER_PAGE);
		
		Division results = div.addDivision("results");
		
		if (resultCount > RESULTS_PER_PAGE) 
		{
        	// If there are enough results then paginate the results
        	String baseURL = contextPath +"/admin/groups?administrative-continue="+knot.getId();
        	int firstIndex = page*RESULTS_PER_PAGE+1; 
        	int lastIndex = page*RESULTS_PER_PAGE + epeople.length;
       
        	String nextURL = null, prevURL = null;
        	if (page < (resultCount / RESULTS_PER_PAGE))
            {
                nextURL = baseURL + "&page=" + (page + 1);
            }
        	if (page > 0)
            {
                prevURL = baseURL + "&page=" + (page - 1);
            }
        	
			results.setSimplePagination(resultCount,firstIndex,lastIndex,prevURL, nextURL);
		}
		
		/* Set up a table with search results (if there are any). */
		Table table = results.addTable("group-edit-search-eperson",epeople.length + 1, 1);
		Row header = table.addRow(Row.ROLE_HEADER);
		header.addCell().addContent(T_epeople_column1);
		header.addCell().addContent(T_epeople_column2);
		header.addCell().addContent(T_epeople_column3);
		header.addCell().addContent(T_epeople_column4);

		for (EPerson person : epeople)
		{
			String epersonID = String.valueOf(person.getID());
			String fullName = person.getFullName();
			String email = person.getEmail();
			String url = contextPath+"/admin/epeople?administrative-continue="+knot.getId()+"&submit_edit_eperson&epersonID="+epersonID;
			
			
			
			Row personData = table.addRow();

			personData.addCell().addContent(person.getID());
			personData.addCell().addXref(url, fullName);
			personData.addCell().addXref(url, email);
			
			// check if they are allready a member of the group
			if (memberEPeopleIDs.contains(person.getID()))
			{
				// Check if they really members or just pending members
				if (group != null && group.isMember(person))
                {
                    personData.addCellContent(T_member);
                }
				else
                {
                    personData.addCell().addHighlight("warn").addContent(T_pending);
                }
			}
			else
			{
				personData.addCell().addButton("submit_add_eperson_"+epersonID).setValue(T_submit_add);
			}
		}

		if (epeople.length <= 0) {
			table.addRow().addCell(1, 4).addContent(T_no_results);
		}
	}
	
	
	
	
	
	/**
	 * Search for groups to add to this group.
	 */
	private void addGroupSearch(Division div, Group sourceGroup, String query, int page, Group parent, List<Integer> memberGroupIDs) throws WingException, SQLException
	{
		int resultCount = Group.searchResultCount(context, query);
        Group[] groups = Group.search(context, query, page*RESULTS_PER_PAGE, RESULTS_PER_PAGE);
		
		Division results = div.addDivision("results");
		
		if (resultCount > RESULTS_PER_PAGE) 
		{
        	// If there are enough results then paginate the results
        	String baseURL = contextPath +"/admin/groups?administrative-continue="+knot.getId();
        	int firstIndex = page*RESULTS_PER_PAGE+1; 
        	int lastIndex = page*RESULTS_PER_PAGE + groups.length;
       
        	String nextURL = null, prevURL = null;
        	if (page < (resultCount / RESULTS_PER_PAGE))
            {
                nextURL = baseURL + "&page=" + (page + 1);
            }
        	if (page > 0)
            {
                prevURL = baseURL + "&page=" + (page - 1);
            }
        	
			results.setSimplePagination(resultCount,firstIndex,lastIndex,prevURL, nextURL);
		}
		
        Table table = results.addTable("roup-edit-search-group",groups.length + 1, 1);
        Row header = table.addRow(Row.ROLE_HEADER);
        header.addCell().addContent(T_groups_column1);
        header.addCell().addContent(T_groups_column2);
        header.addCell().addContent(T_groups_column3);
        header.addCell().addContent(T_groups_column4);
        header.addCell().addContent(T_groups_column5);  
        
        for (Group group : groups)
        {
        	String groupID = String.valueOf(group.getID());
        	String name = group.getName();
        	String url = contextPath+"/admin/groups?administrative-continue="+knot.getId()+"&submit_edit_group&groupID="+groupID;
        	int memberCount = group.getMembers().length + group.getMemberGroups().length;
        	
        	Row row = table.addRow();
	        	
        	row.addCell().addContent(groupID);
        	if (AuthorizeManager.isAdmin(context))
        		// Only administrators can edit other groups.
            {
                row.addCell().addXref(url, name);
            }
        	else
            {
                row.addCell().addContent(name);
            }
        	
        	
        	
        	row.addCell().addContent(memberCount == 0 ? "-" : String.valueOf(memberCount));
        	
        	Cell cell = row.addCell();
        	if (FlowGroupUtils.getCollectionId(group.getName()) > -1)
        	{
        		Collection collection = Collection.find(context, FlowGroupUtils.getCollectionId(group.getName()) );
        		if (collection != null)
        		{
	        		String collectionName = collection.getMetadata("name");
	        		
	        		if (collectionName == null)
                    {
                        collectionName = "";
                    }
	        		else if (collectionName.length() > MAX_COLLECTION_NAME)
                    {
                        collectionName = collectionName.substring(0, MAX_COLLECTION_NAME - 3) + "...";
                    }
	        		
	        		cell.addContent(collectionName+" ");
	        		
	        		Highlight highlight = cell.addHighlight("fade");
	        		highlight.addContent("[");
	        		highlight.addXref(contextPath+"/handle/"+collection.getHandle(), T_groups_collection_link);
	        		highlight.addContent("]");
        		}
        	}
        	
        	
        	// Check if the group is allready a member or would create a cycle.
        	if (memberGroupIDs.contains(group.getID()))
        	{
        		// Check if they really members or just pending members
				if (parent != null && parent.isMember(group))
                {
                    row.addCellContent(T_member);
                }
				else
                {
                    row.addCell().addHighlight("warn").addContent(T_pending);
                }
        	}
        	else if (isDescendant(sourceGroup, group, memberGroupIDs)) 
        	{
        		row.addCellContent(T_cycle);
	        }
	        else 
	        {
	        	row.addCell().addButton("submit_add_group_"+groupID).setValue(T_submit_add);
	        }
            
        }
        if (groups.length <= 0) {
			table.addRow().addCell(1, 4).addContent(T_no_results);
		}
	}
	
	
	
	
	
	
	/**
	 * Method to extensively check whether the first group has the second group as a distant
	 * parent. This is used to avoid creating cycles like A->B, B->C, C->D, D->A which leads 
	 * all the groups involved to essentially include themselves.  
	 */
	private boolean isDescendant(Group descendant, Group ancestor, List<Integer> memberGroupIDs) throws SQLException
	{
		Queue<Group> toVisit = new LinkedList<Group>();
		Group currentGroup;
		
		toVisit.offer(ancestor);
		
		// Initialize by adding a list of our current list of group members.
		for (Integer groupid : memberGroupIDs)
		{
			Group member = Group.find(context,groupid);
			toVisit.offer(member);
		}
		
		while (!toVisit.isEmpty()) {
			// 1. Grab a group from the queue
			currentGroup = toVisit.poll();
			
			// 2. See if it's the descendant we're looking for
			if (currentGroup.equals(descendant)) {
                return true;
            }
			
			// 3. If not, add that group's children to the queue
			for (Group nextBatch : currentGroup.getMemberGroups()) {
				toVisit.offer(nextBatch);
			}
		}
		return false;
	}
	
	
	
	
	
	
	/**
	 * Add a table with all the current group's members to the specified division.
	 * @throws SQLException 
	 */
	private boolean addMemberList(Division div, Group parent, List<Integer> memberGroupIDs, List<Integer> memberEPeopleIDs, int highlightEPersonID, int highlightGroupID) throws WingException, SQLException
	{
		// Flag to remember if there are any pending changes.
        boolean changes = false;
        
        Division members = div.addDivision("group-edit-members");
        members.setHead(T_members_head);
        
        Table table = members.addTable("group-edit-members-table",memberGroupIDs.size() + memberEPeopleIDs.size() + 1, 4);
        
        Row header = table.addRow(Row.ROLE_HEADER);
        header.addCell().addContent(T_members_column1);
        header.addCell().addContent(T_members_column2);
        header.addCell().addContent(T_members_column3);
        header.addCell().addContent(T_members_column4);
        
        // get all group members, pend or actual
        @SuppressWarnings("unchecked") // the cast is correct
        List<Integer> allMemberGroupIDs = new ArrayList<Integer>(memberGroupIDs);
        for (Group group : parent.getMemberGroups())
        {
        	if (!allMemberGroupIDs.contains(group.getID()))
            {
                allMemberGroupIDs.add(group.getID());
            }
        }
        // Sort them to a consistent ordering
        Collections.sort(allMemberGroupIDs);
        
        // Loop through all group ids and display them.
        for (Integer groupID : allMemberGroupIDs)
        {
        	Group group = Group.find(context,groupID);
        	boolean highlight = (group.getID() == highlightGroupID);
        	boolean pendingAddition = !parent.isMember(group);
        	boolean pendingRemoval = !memberGroupIDs.contains(groupID);
        	addMemberRow(table, group, highlight,pendingAddition,pendingRemoval);   
        	
        	if (pendingAddition || pendingRemoval)
            {
                changes = true;
            }
        }
        
        
        // get all members, pend or actual
        @SuppressWarnings("unchecked") // the cast is correct
        List<Integer> allMemberEPeopleIDs = new ArrayList<Integer>(memberEPeopleIDs);
        for (EPerson eperson : parent.getMembers())
        {
        	if (!allMemberEPeopleIDs.contains(eperson.getID()))
            {
                allMemberEPeopleIDs.add(eperson.getID());
            }
        }
        // Sort them to a consistent ordering
        Collections.sort(allMemberEPeopleIDs);
        
        for (Integer epersonID : allMemberEPeopleIDs)
        {
        	EPerson eperson = EPerson.find(context, epersonID);
        	boolean highlight = (eperson.getID() == highlightEPersonID);
        	boolean pendingAddition = !parent.isMember(eperson);
        	boolean pendingRemoval = !memberEPeopleIDs.contains(epersonID);
        	addMemberRow(table,eperson,highlight,pendingAddition,pendingRemoval);
        	
        	if (pendingAddition || pendingRemoval)
            {
                changes = true;
            }
        }
    
        if (allMemberGroupIDs.size() <= 0 && allMemberEPeopleIDs.size() <= 0)
        {
        	table.addRow().addCell(1, 4).addContent(T_members_none);
        }
        
        return changes;
	}
	
	
	/**
	 * Add a singe member row for groups.
	 * 
	 * @param table The table to add the row too.
	 * @param group The group being displayed in this row.
	 * @param highlight Should the row be highlighted.
	 * @param pendingAddition Is this group pending additition
	 * @param pendingRemoval Is this group pending removal
	 */
	private void addMemberRow(Table table,Group group, boolean highlight, boolean pendingAddition, boolean pendingRemoval) throws WingException, SQLException
	{
    	String name = group.getName();
    	String url = contextPath+"/admin/groups?administrative-continue="+knot.getId()+"&submit_edit_group&groupID="+group.getID();
   
    	Row groupData = table.addRow(null,null,highlight ? "highlight" : null);
    	
    	groupData.addCell().addHighlight("bold").addContent(group.getID());
    	
    	// Mark if this member is pending or not.
    	Cell nameCell = groupData.addCell();
    	if (AuthorizeManager.isAdmin(context))
        {
            nameCell.addHighlight("bold").addXref(url, T_members_group_name.parameterize(name));
        }
    	else
        {
            nameCell.addHighlight("bold").addContent(T_members_group_name.parameterize(name));
        }
    	
    	if (pendingAddition)
    	{
    		nameCell.addContent(" ");
    		nameCell.addHighlight("warn").addContent(T_members_pending);
    	}
    	
    	groupData.addCell().addContent("-");
    	
    	if (pendingRemoval)
        {
            groupData.addCell().addHighlight("warn").addContent(T_pending);
        }
    	else
        {
            groupData.addCell().addButton("submit_remove_group_" + group.getID()).setValue(T_submit_remove);
        }
	}
	
	/**
	 * Add a single member row for epeople.
	 * 
	 * @param table The table to add a row too.
	 * @param eperson The eperson being displayed
	 * @param highlight Should this eperson be highlighted?
	 * @param pendingAddition Is this eperson pending addition?
	 * @param pendingRemoval Is this eperson pending removal?
	 */
	private void addMemberRow(Table table, EPerson eperson, boolean highlight, boolean pendingAddition, boolean pendingRemoval) throws WingException, SQLException
	{
    	String fullName = eperson.getFullName();
    	String email = eperson.getEmail();
    	String url = contextPath+"/admin/epeople?administrative-continue="+knot.getId()+"&submit_edit_eperson&epersonID="+eperson.getID();
		
    	
    	Row personData = table.addRow(null,null,highlight ? "highlight" : null);
    	
    	personData.addCell().addContent(eperson.getID());
    	
		Cell nameCell = personData.addCell();
		nameCell.addXref(url, fullName);
		if (pendingAddition)
		{
			nameCell.addContent(" ");
    		nameCell.addHighlight("warn").addContent(T_members_pending);
		}
		
		personData.addCell().addXref(url, email);
		
		if (pendingRemoval)
        {
            personData.addCell().addHighlight("warn").addContent(T_pending);
        }
		else
        {
            personData.addCell().addButton("submit_remove_eperson_" + eperson.getID()).setValue(T_submit_remove);
        }
	}
}
