package org.inria.bmajwatcher.client.ui.users;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.inria.bmajwatcher.client.services.UserManagerServiceAsync;
import org.inria.bmajwatcher.client.ui.bank_editor.MySuggestOracle;
import org.inria.bmajwatcher.client.ui.styles.MainCss;
import org.inria.bmajwatcher.client.ui.styles.Resources;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PasswordTextBox;
import com.google.gwt.user.client.ui.PushButton;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.SuggestOracle;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.smartgwt.client.data.Record;
import com.smartgwt.client.types.TreeModelType;
import com.smartgwt.client.util.BooleanCallback;
import com.smartgwt.client.util.SC;
import com.smartgwt.client.widgets.grid.events.CellContextClickEvent;
import com.smartgwt.client.widgets.grid.events.CellContextClickHandler;
import com.smartgwt.client.widgets.grid.events.SelectionChangedHandler;
import com.smartgwt.client.widgets.grid.events.SelectionEvent;
import com.smartgwt.client.widgets.layout.Layout;
import com.smartgwt.client.widgets.menu.Menu;
import com.smartgwt.client.widgets.menu.MenuItem;
import com.smartgwt.client.widgets.menu.MenuItemSeparator;
import com.smartgwt.client.widgets.menu.events.MenuItemClickEvent;
import com.smartgwt.client.widgets.tree.Tree;
import com.smartgwt.client.widgets.tree.TreeGrid;
import com.smartgwt.client.widgets.tree.TreeGridField;
import com.smartgwt.client.widgets.tree.TreeNode;
import com.smartgwt.client.widgets.tree.events.FolderDropEvent;
import com.smartgwt.client.widgets.tree.events.FolderDropHandler;

public class UserManagerDialog extends DialogBox {

	boolean admin = false;
	
	private MainCss css = Resources.INSTANCE.mainCss();
	private VerticalPanel rightPanel = new VerticalPanel();
	private Map<String, Map<String, String>> users = new HashMap<String, Map<String,String>>();
	private Set<String> groups = new HashSet<String>();
	private SuggestOracle groupSuggest = null;
	private VerticalPanel leftPanel = new VerticalPanel();
	private TreeGrid treeGrid;
	
	private String currentUser = "";
	
	private int addGroup = 0;
	private int delGroup = 0;
	
	private UserManagerServiceAsync service = UserManagerServiceAsync.INSTANCE;
	
	public static class UserTreeNode extends TreeNode {
		public UserTreeNode(String name, String id, String parentId, String type) {
			setAttribute("name", name);
			setAttribute("id", id);
			setAttribute("parentId", parentId);
			setAttribute("type", type);
			
			if (type.equals("user"))
				setIcon("user-edit.png");
			else if (type.equals("group")) {
				setIsFolder(true);
				setCanDrag(false);
				setCanAcceptDrop(true);
			}
			
			if (id.equals("root")) {
				setCanAcceptDrop(false);
			}
		}
	}
	
	
	public UserManagerDialog(final String login) {
		
		super(false, false);
		
		currentUser = login;
		service.getUsers(new AsyncCallback<Map<String,Map<String,String>>>() {
				
			@Override
			public void onSuccess(Map<String, Map<String, String>> result) {
				users = result;
				
				service.getAdminLogin(new AsyncCallback<String>() {
					
					@Override
					public void onSuccess(String result) {
						admin = login.equals(result);
						init(login);
					}
					
					@Override
					public void onFailure(Throwable caught) {
						SC.warn("RPC failure");
					}
				});
				
				service.getGroups(new AsyncCallback<List<String>>() {
					
					@Override
					public void onSuccess(List<String> result) {
						groups.clear();
						// Add group nodes
						for (String group : result) {
							groups.add(group);
						}
					}
					
					@Override
					public void onFailure(Throwable caught) {
						SC.warn("RPC failure");
					}
				});
			}
			
			@Override
			public void onFailure(Throwable caught) {
				SC.warn("RPC failure");
			}
		});
	}
	
	private void init(String login) {
		setHTML("<b>User management</b>");
		addStyleName(css.bankEditor());
		
		HorizontalPanel content = new HorizontalPanel();
		VerticalPanel main = new VerticalPanel();

		HorizontalPanel rightContainer = new HorizontalPanel();
		rightContainer.addStyleName(css.userInfoMainPanel());
		rightContainer.add(rightPanel);
		
		if (admin) {
			generateUserTree();
			updateTreeData();
			Label treeTitle = new Label("Users");
			
			Image refreshTree = new Image("images/arrow-refresh.png");
			refreshTree.setTitle("Refresh user list");
			refreshTree.addStyleName(css.userTreeRefresh());
			refreshTree.addClickHandler(new ClickHandler() {
				
				@Override
				public void onClick(ClickEvent event) {
					updateTreeData();
				}
			});
			HorizontalPanel hpTitle = new HorizontalPanel();
			hpTitle.addStyleName(css.userTreeTitle());
			hpTitle.add(refreshTree);
			hpTitle.add(treeTitle);
			
			leftPanel.add(hpTitle);
			Layout treeContainer = new Layout();
			treeContainer.addChild(treeGrid);
			leftPanel.add(treeContainer);
			leftPanel.addStyleName(css.userInfoTreePanel());
			
			PushButton addGroup = new PushButton(new Image("images/group-add.png"), new ClickHandler() {
				
				@Override
				public void onClick(ClickEvent event) {
					showGroupPanel(null);
				}
			});
			PushButton addUser = new PushButton(new Image("images/user-add.png"), new ClickHandler() {
				
				@Override
				public void onClick(ClickEvent event) {
					showUserPanel(null, null);
				}
			});
			addGroup.setTitle("Create new group");
			addUser.setTitle("Create new user");
			
			HorizontalPanel hpBtn = new HorizontalPanel();
			hpBtn.add(addGroup);
			hpBtn.add(addUser);
			hpBtn.setSpacing(3);
			
			final TextBox tbSearch = new TextBox();
			tbSearch.setText("Search for user...");
			tbSearch.addStyleName(css.userInfoTextBox());
			tbSearch.addStyleName(css.searchBox());
			HorizontalPanel hpSearch = new HorizontalPanel();
			hpSearch.add(tbSearch);
			tbSearch.addKeyPressHandler(new KeyPressHandler() {
				@Override
				public void onKeyPress(KeyPressEvent event) {
					if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
						if (users.containsKey(tbSearch.getText().trim())) {
							showUserPanel(tbSearch.getText().trim(), null);
						}
					}
				}
			});
			tbSearch.addFocusHandler(new FocusHandler() {
				
				@Override
				public void onFocus(FocusEvent event) {
					tbSearch.selectAll();
				}
			});
			
			leftPanel.add(hpSearch);
			leftPanel.add(hpBtn);
			
			content.add(leftPanel);
			content.add(rightContainer);
			
		}
		
		content.add(rightContainer);
		showUserPanel(login, null);
		
		Button close = new Button("Close", new ClickHandler() {
			
			@Override
			public void onClick(ClickEvent event) {
				hide();
			}
		});

		main.add(content);
		main.add(close);
		main.setCellHorizontalAlignment(close, HasHorizontalAlignment.ALIGN_RIGHT);

		add(main);
	}
	
	/**
	 * Show panel with user info.
	 * If login is not null, try to retrieve info about user and display it.
	 * 
	 * Same form is used for adding a new user and modifying an existing user.
	 * 
	 * @param login if null, create new user on saving
	 * @param group parameter used when creating a new user. Defines group user should be added
	 * 			to. This value is retrieved from the user tree. If null, empty value is added.
	 */
	private void showUserPanel(final String login, String _group) {
		if (rightPanel.getParent() != null) {
			rightPanel.getParent().setVisible(true);
		}
		rightPanel.clear();
		
		String authType = "";
		String[] groups = null;
		if (login != null) {
			authType = users.get(login).get("auth_type");
			groups = users.get(login).get("groups").split("_:_");
		}
		
		// Title
		Label title = new Label("USER INFO");
		title.addStyleName(css.userInfoTitle());
		rightPanel.add(title);
		
		VerticalPanel subContent = new VerticalPanel();
		subContent.addStyleName(css.userInfoSubPanel());
		rightPanel.add(subContent);
		
		// User type
		if (authType.equals("local") || login == null) {
			HTML html = new HTML("<span style=\"color:#363C8C\"><b>Local user</b></span>");
			html.addStyleName(css.userInfoSection());
			subContent.add(html);
		} else {
			HTML html = new HTML("<span style=\"color:#363C8C\"><b>LDAP user</b></span>");
			html.addStyleName(css.userInfoSection());
			subContent.add(html);
		}
		
		// Login
		final TextBox tbLogin = new TextBox();
		tbLogin.addStyleName(css.userInfoTextBox());
		
		if (login != null) {
			HTML lblLogin = new HTML("Login : <span style=\"color:#363C8C\"><b>" + login + "</b></span>");
			lblLogin.addStyleName(css.userInfoSection());
			subContent.add(lblLogin);
		} else {
			Label lblLogin = new Label("Login :");
			lblLogin.addStyleName(css.userInfoSection());
			subContent.add(lblLogin);
			tbLogin.setText(login);
			subContent.add(tbLogin);
		}
		
		
		final PasswordTextBox tbPassword = new PasswordTextBox();		
		tbPassword.addStyleName(css.userInfoTextBox());
		final PasswordTextBox confirmPassword = new PasswordTextBox();
		confirmPassword.addStyleName(css.userInfoTextBox());
		if (!admin || (admin && currentUser.equals(login))) {
			if (!authType.equals("ldap")) {
				// Password
				Label lblPasswd = new Label("Password :");
				lblPasswd.addStyleName(css.userInfoSection());
				subContent.add(lblPasswd);
				subContent.add(tbPassword);
				// Confirm passwd
				subContent.add(new Label("Confirm"));
				subContent.add(confirmPassword);
			}
		} else if (login != null && !authType.equals("ldap")) {
			Anchor resetPasswd = new Anchor("Reset password");
			resetPasswd.addClickHandler(new ClickHandler() {
				
				@Override
				public void onClick(ClickEvent event) {
					service.setUserPassword(login, null, new AsyncCallback<Boolean>() {
						
						@Override
						public void onSuccess(Boolean result) {
							if (!result)
								SC.warn("Could not set new password");
							else
								updateTreeData();
						}
						
						@Override
						public void onFailure(Throwable caught) {
							SC.warn("RPC failure");
						}
					});
				}
			});
			subContent.add(resetPasswd);
		}
		
		// Mail address
		Label lblEmail = new Label("E-mail :");
		lblEmail.addStyleName(css.userInfoSection());
		subContent.add(lblEmail);
		final TextBox tbMail = new TextBox();
		tbMail.addStyleName(css.userInfoTextBox());
		final TextBox tbKey = new TextBox();
		tbKey.addStyleName(css.userInfoTextBox());
		subContent.add(tbMail);
		if (login != null) {
			tbMail.setText(users.get(login).get("mail"));
			tbKey.setText(users.get(login).get("key"));
			
			// User key
			Label lblKey=  new Label("Key for rest querying :");
			lblKey.addStyleName(css.userInfoSection());
			subContent.add(lblKey);
			
			tbKey.setReadOnly(true);
			Anchor resetKey = new Anchor("Reset key");		
			resetKey.addClickHandler(new ClickHandler() {
				
				@Override
				public void onClick(ClickEvent event) {
					service.resetUID(login, new AsyncCallback<Boolean>() {
						
						@Override
						public void onSuccess(Boolean result) {
							if (!result) {
								SC.warn("Could not reset key");
							} else {
								service.getUsers(new AsyncCallback<Map<String,Map<String,String>>>() {
									
									@Override
									public void onSuccess(Map<String, Map<String, String>> result) {
										users = result;
										tbKey.setText(users.get(login).get("key"));
									}
									
									@Override
									public void onFailure(Throwable caught) {
										SC.warn("RPC failure");
									}
								});
							}
						}
						
						@Override
						public void onFailure(Throwable caught) {
							SC.warn("RPC failure");
						}
					});
				}
			});
			subContent.add(tbKey);
			subContent.add(resetKey);
		}
		
		// Groups
		Label lblGroups = new Label("Groups :");
		lblGroups.addStyleName(css.userInfoSection());
		subContent.add(lblGroups);
		final List<SuggestBox> tbGroups = new ArrayList<SuggestBox>();
		
		
		VerticalPanel vpGroups = new VerticalPanel();
		
		if (groups != null) { // Load group list
			for (String group : groups)
				addGroupInput(vpGroups, group, tbGroups);
		} else if (_group != null) { // Add preselected group
			addGroupInput(vpGroups, _group, tbGroups);
		} else { // No select group
			addGroupInput(vpGroups, "", tbGroups);
		}
		
		subContent.add(vpGroups);
		
		HorizontalPanel hpBtn = new HorizontalPanel();
		hpBtn.setSpacing(5);
		hpBtn.addStyleName(css.userInfoSection());
		subContent.add(hpBtn);
		subContent.setCellHorizontalAlignment(hpBtn, HasHorizontalAlignment.ALIGN_CENTER);
		
		// Validate
		hpBtn.add(new Button("Save", new ClickHandler() {
			
			@Override
			public void onClick(ClickEvent event) {
				
				if (admin) {
					Set<String> grps = new HashSet<String>();
					for (SuggestBox sb : tbGroups) {
						if (!sb.getText().trim().isEmpty())
							grps.add(sb.getText().trim());
					}
					
					if (login == null) {
						/*
						 * Add new user
						 */
						if (!users.containsKey(tbLogin.getText().trim())) {
							service.addUser(tbLogin.getText(), grps, "local", tbMail.getText(), new AsyncCallback<Boolean>() {
								
								@Override
								public void onSuccess(Boolean result) {
									if (!result) {
										SC.warn("Could not create user");
									} else {
										updateTreeData();
									}
								}
								
								@Override
								public void onFailure(Throwable caught) {
									SC.warn("RPC failure");
								}
							});
						} else {
							SC.warn("Login already exists");
						}
					} else {
						/*
						 * Update user
						 */
						final List<String> grpsToRemove = new ArrayList<String>();
						final List<String> grpsCopy = new ArrayList<String>(grps.size());
						grpsCopy.addAll(grps);
						String[] previousGroups = users.get(login).get("groups").split("_:_");
						for (String grp : previousGroups) {
							if (grpsCopy.contains(grp)) {
								grpsCopy.remove(grp); // Remaining groups are new groups
							} else {
								grpsToRemove.add(grp); // Groups to remove
							}
						}
						
						// Add user to groups
						addGroup = 0;
						for (final String gp : grpsCopy) {
							service.addUserToGroup(login, gp, new AsyncCallback<Boolean>() {
								
								@Override
								public void onSuccess(Boolean result) {
									if (!result)
										SC.warn("Could not add user to group : " + gp);
									else if (++addGroup == grpsCopy.size()) // Update data on last group
										updateTreeData();
								}
								
								@Override
								public void onFailure(Throwable caught) {
									SC.warn("RPC failure");
								}
							});
						}
						// Remove user from groups
						delGroup = 0;
						for (final String gp : grpsToRemove) {
							service.removeUserFromGroup(login, gp, new AsyncCallback<Boolean>() {
								
								@Override
								public void onSuccess(Boolean result) {
									if (!result)
										SC.warn("Could not remove user from group : " + gp);
									else if (++delGroup == grpsToRemove.size()) // Update data on last remove
										updateTreeData();
								}
								
								@Override
								public void onFailure(Throwable caught) {
									SC.warn("RPC failure");
								}
							});
						}
					}
				}
				
				if (login != null) {
					service.setUserMail(login, tbMail.getText(), new AsyncCallback<Boolean>() {
						
						@Override
						public void onSuccess(Boolean result) {
							if (!result)
								SC.warn("Could not set new mail");
							else {
								users.get(login).put("mail", tbMail.getText());
							}
						}
						
						@Override
						public void onFailure(Throwable caught) {
							SC.warn("RPC failure");
						}
					});
				}
				
				if (!admin || (admin && currentUser.equals(login))) { // NOT ADMIN
					// Change password
					if (users.get(login).get("auth_type").equals("local")) { // Update password if not ldap
						String newPassword = null; // Null for admin as it needs to be generated
						
						if (tbPassword.getText().trim().equals(confirmPassword.getText().trim()) && 
								!tbPassword.getText().trim().isEmpty()) {
							newPassword = tbPassword.getText().trim();
							service.setUserPassword(login, newPassword, new AsyncCallback<Boolean>() {
								
								@Override
								public void onSuccess(Boolean result) {
									if (!result)
										SC.warn("Could not set new password");
									else {
										tbPassword.setText("");
										confirmPassword.setText("");
									}
								}
								
								@Override
								public void onFailure(Throwable caught) {
									SC.warn("RPC failure");
								}
							});
						} else if (!tbPassword.getText().trim().isEmpty()) {
							SC.warn("Passwords do not match");
						}
					}
				}
				
			}
		}));
		
		if (admin && login != null) {
			// Delete user
			hpBtn.add(new Button("Delete", new ClickHandler() {
				
				@Override
				public void onClick(ClickEvent event) {
					SC.confirm("Delete user '" + login + "' ?", new BooleanCallback() {
						
						@Override
						public void execute(Boolean value) {
							if (value) {
								service.deleteUser(login, new AsyncCallback<Boolean>() {
									
									@Override
									public void onSuccess(Boolean result) {
										if (!result) {
											SC.warn("Could not delete user");
										} else {
											updateTreeData();
										}
									}
									
									@Override
									public void onFailure(Throwable caught) {
										SC.warn("RPC failure");
									}
								});
							}
						}
					});
				}
			}));
		}
	}
	
	/**
	 * Generates a group input with a suggest box and an 'add' or 'del' button
	 * and add it the the given panel.
	 * 
	 * @param vp panel inputs are added to
	 * @param group default group name to set textbox text with
	 * @param tbGroups list of the suggestboxes that hold the groups values
	 */
	private void addGroupInput(final VerticalPanel vp, String group, final List<SuggestBox> tbGroups) {
		
		if (!admin) {
			TextBox tb = new TextBox();
			tb.addStyleName(css.userInfoTextBox());
			tb.setText(group);
			tb.setEnabled(false);
			vp.add(tb);
		} else {
			final HorizontalPanel hp = new HorizontalPanel();
			vp.add(hp);
			if (groupSuggest == null) {
				groupSuggest = new MySuggestOracle(groups);
			}
			final SuggestBox sb = new SuggestBox(groupSuggest);
			sb.addStyleName(css.userInfoTextBox());
			sb.setText(group);
			hp.add(sb);
			tbGroups.add(sb);
			
			PushButton pbAdd = new PushButton(new Image("images/list-add.png"), new ClickHandler() {
				
				@Override
				public void onClick(ClickEvent event) {
					addGroupInput(vp, "", tbGroups);
				}
			});
			pbAdd.addStyleName(css.userInfoGroupButton());
			
			PushButton pbDel = new PushButton(new Image("images/list-remove.png"), new ClickHandler() {
				
				@Override
				public void onClick(ClickEvent event) {
					tbGroups.remove(sb);
					vp.remove(hp);
					
					if (vp.getWidget(0) instanceof HorizontalPanel) { // Enables first Del button if more than one line
						if (((HorizontalPanel) vp.getWidget(0)).getWidget(2) instanceof PushButton)
							((PushButton) ((HorizontalPanel) vp.getWidget(0)).getWidget(2)).setEnabled(vp.getWidgetCount() > 1);
					}
				}
			});
			pbDel.addStyleName(css.userInfoGroupButton());
			
			hp.add(pbAdd);
			hp.add(pbDel);
			
			
			if (vp.getWidget(0) instanceof HorizontalPanel) { // Enables first Del button if more than one line
				if (((HorizontalPanel) vp.getWidget(0)).getWidget(2) instanceof PushButton)
					((PushButton) ((HorizontalPanel) vp.getWidget(0)).getWidget(2)).setEnabled(vp.getWidgetCount() > 1);
			}
		}
	}
	
	
	/**
	 * Generates panel that shows group name.
	 * 
	 * @param group if null, new group to create, otherwise, update
	 */
	private void showGroupPanel(final String group) {
		rightPanel.getParent().setVisible(true);
		rightPanel.clear();
		
		// Panel title
		Label title = new Label("GROUP INFO");
		title.addStyleName(css.userInfoTitle());
		rightPanel.add(title);
		
		VerticalPanel subContent = new VerticalPanel();
		subContent.addStyleName(css.userInfoSubPanel());
		rightPanel.add(subContent);
		
		// Group name
		Label grpName = new Label("Group name :");
		grpName.addStyleName(css.userInfoSection());
		subContent.add(grpName);
		
		final TextBox tbGroup = new TextBox();
		tbGroup.addStyleName(css.userInfoSection());
		tbGroup.addStyleName(css.userInfoTextBox());
		if (group != null)
			tbGroup.setText(group);
		subContent.add(tbGroup);
		HorizontalPanel hpBtn = new HorizontalPanel();
		hpBtn.addStyleName(css.userInfoSection());
		hpBtn.setSpacing(5);
		subContent.add(hpBtn);
		subContent.setCellHorizontalAlignment(hpBtn, HasHorizontalAlignment.ALIGN_CENTER);
		
		hpBtn.add(new Button("Save", new ClickHandler() {
			
			@Override
			public void onClick(ClickEvent event) {
				final String gr = tbGroup.getText().trim();
				if (group == null && !gr.isEmpty() && !groups.contains(gr)) {
					service.addGroup(gr, new AsyncCallback<Boolean>() {
						
						@Override
						public void onSuccess(Boolean result) {
							if (!result) {
								SC.warn("Could not add group to db");
							} else {
								updateTreeData();
							}
						}
						
						@Override
						public void onFailure(Throwable caught) {
							SC.warn("RPC failure");
						}
					});
				} else if (group != null && !gr.isEmpty()) {
					if (!groups.contains(gr)) {
						service.updateGroup(group, gr, new AsyncCallback<Boolean>() {
							
							@Override
							public void onSuccess(Boolean result) {
								if (result) {
									groups.remove(group);
									groups.add(gr);
									updateTreeData();
								} else {
									SC.say("Could not update group");
								}
							}
							
							@Override
							public void onFailure(Throwable caught) {
								SC.warn("RPC failure");
							}
						});
					} else {
						SC.warn("Group name already exists");
					}
				}
			}
		}));
		
		hpBtn.add(new Button("Delete", new ClickHandler() {
			
			@Override
			public void onClick(ClickEvent event) {
				deleteGroup(group);
			}
		}));
	}
	
	/**
	 * Contrary to updateTreeData method, updates only data.
	 * It is assumed that the tree already has the structure mapping the data.
	 * This method is meant for updating users map after a drag n drop.
	 */
	private void updateDataOnly() {
		service.getUsers(new AsyncCallback<Map<String,Map<String,String>>>() {
			
			@Override
			public void onSuccess(Map<String, Map<String, String>> result) {
				users = result;
				updateGroups();
			}
			
			@Override
			public void onFailure(Throwable caught) {
				SC.warn("RPC failure");
			}
		});
	}
	
	private void updateGroups() {
		service.getGroups(new AsyncCallback<List<String>>() {
			
			@Override
			public void onSuccess(List<String> result) {
				groups.clear();
				// Add group nodes
				for (String group : result) {
					groups.add(group);
				}
			}
			
			@Override
			public void onFailure(Throwable caught) {
				SC.warn("RPC failure");
			}
		});
	}

	/**
	 * Updates data and redraws tree
	 */
	private void updateTreeData() {
		final Tree tree = new Tree();
        tree.setModelType(TreeModelType.PARENT);
        tree.setNameProperty("name");
        tree.setIdField("id");
        tree.setParentIdField("parentId");
        tree.setShowRoot(false);
        
		// Get users
		service.getUsers(new AsyncCallback<Map<String, Map<String,String>>>() {
			
			@Override
			public void onSuccess(Map<String, Map<String, String>> result) {
				users = result;
				int id = 2;
				final List<TreeNode> data = new ArrayList<TreeNode>();
				for (Map<String, String> user : result.values()) {
					String[] grps = user.get("groups").split("_:_");
					for (String group : grps) { // User can't exist without a group
						data.add(new UserTreeNode(user.get("login"), String.valueOf(id++), group, "user"));
					}
				}
				
				// Get groups
				service.getGroups(new AsyncCallback<List<String>>() {
					
					@Override
					public void onSuccess(List<String> result) {
						groups.clear();
						// Add group nodes
						for (String group : result) {
							groups.add(group);
							data.add(new UserTreeNode(group, group, "root", "group"));
						}
						
						// Root node
						TreeNode root = new TreeNode();
						root.setAttribute("id", "root");
						root.setAttribute("name", "root");
						tree.setRoot(root);
						
						tree.setData((TreeNode[]) data.toArray(new TreeNode[data.size()]));
						treeGrid.setData(tree);
						tree.openAll();
					}
					
					@Override
					public void onFailure(Throwable caught) {
						SC.warn("RPC failure");
					}
				});
			}
			
			@Override
			public void onFailure(Throwable caught) {
				SC.warn("RPC call failure !");
			}
		});
	}

	public void generateUserTree() {

        treeGrid = new TreeGrid();
        treeGrid.setWidth(200);
        treeGrid.setHeight(300);
        treeGrid.setShowHeader(false);
        treeGrid.setCanReparentNodes(true);
        treeGrid.setCanReorderRecords(false);
        treeGrid.addCellContextClickHandler(new CellContextClickHandler() {
			
			@Override
			public void onCellContextClick(CellContextClickEvent event) {
				event.cancel();
				showPopupMenu(event.getRecord());
			}
		});
        treeGrid.addSelectionChangedHandler(new SelectionChangedHandler() {
			
			@Override
			public void onSelectionChanged(SelectionEvent event) {
				Record record = event.getRecord();
				if (record != null) {
					String name = record.getAttribute("name");
					if (record.getAttribute("type").equals("user") && users.containsKey(name)) { // User was not deleted)
						showUserPanel(name, null);
					} else if (record.getAttribute("type").equals("group") && groups.contains(name)) {
						showGroupPanel(name);
					}
				}
			}
		});
        treeGrid.addFolderDropHandler(new FolderDropHandler() {
			
			@Override
			public void onFolderDrop(FolderDropEvent event) {
				if (event.getFolder().getAttribute("id").equals("root")) {
					event.cancel(); // No drop under root
				} else {
					final Record[] drag = treeGrid.getDragData();
					addGroup = 0;
					for (Record rec : drag) {
						addGroup++;
						final String userId = rec.getAttribute("name");
						final String oldGroup = rec.getAttribute("parentId");
						final String newGroup = event.getFolder().getAttribute("name");
						
						// Add user to new group
						service.addUserToGroup(userId, newGroup, new AsyncCallback<Boolean>() {
							
							@Override
							public void onSuccess(Boolean result) {
								if (!result) {
									SC.warn("Could not add " + userId + " to " + newGroup);
								} else {
									// Remove user from old group
									service.removeUserFromGroup(userId, oldGroup, new AsyncCallback<Boolean>() {
										
										@Override
										public void onSuccess(Boolean result) {
											if (!result) {
												SC.warn("Could not remove " + userId + " from " + oldGroup);
											} else if (addGroup == drag.length) {
												updateDataOnly();
											}
										}
										
										@Override
										public void onFailure(Throwable caught) {
											SC.warn("RPC failure");
										}
									});
								}
							}
							
							@Override
							public void onFailure(Throwable caught) {
								SC.warn("RPC failure");
							}
						});
					}
				}
			}
		});
        
        TreeGridField field = new TreeGridField("name", "name");
        field.setCanSort(false);
        
        treeGrid.setFields(field);
	}
	
	private void showPopupMenu(final Record record) {
		
		boolean isUser = record.getAttribute("type").equals("user");
		
		final String parentGroup =  isUser ? record.getAttribute("parentId") : record.getAttribute("name");
		
		Menu menu = new Menu();
		
		MenuItem itemRemoveGroup = new MenuItem("Remove group", "edit-delete.png");
		itemRemoveGroup.addClickHandler(new com.smartgwt.client.widgets.menu.events.ClickHandler() {
			
			@Override
			public void onClick(MenuItemClickEvent event) {
				deleteGroup(parentGroup);
			}
		});
		
		MenuItem itemRemoveFromGroup = new MenuItem("Remove user from group", "edit-delete.png");
		itemRemoveFromGroup.addClickHandler(new com.smartgwt.client.widgets.menu.events.ClickHandler() {
			
			@Override
			public void onClick(MenuItemClickEvent event) {
				service.removeUserFromGroup(record.getAttribute("name"), parentGroup, new AsyncCallback<Boolean>() {
					
					@Override
					public void onSuccess(Boolean result) {
						if (result) {
							updateTreeData();
						} else {
							SC.warn("Could not remove user from group");
						}
					}
					
					@Override
					public void onFailure(Throwable caught) {
						SC.warn("RPC failure");
					}
				});
			}
		});
		
		MenuItem itemAddUser = new MenuItem("Add user", "user-add.png");
		itemAddUser.addClickHandler(new com.smartgwt.client.widgets.menu.events.ClickHandler() {
			
			@Override
			public void onClick(MenuItemClickEvent event) {
				showUserPanel(null, parentGroup);
			}
		});
		
		MenuItem itemAddGroup = new MenuItem("Add group", "group-add.png");
		itemAddGroup.addClickHandler(new com.smartgwt.client.widgets.menu.events.ClickHandler() {
			
			@Override
			public void onClick(MenuItemClickEvent event) {
				showGroupPanel(null);
			}
		});
		
		if (isUser)
			menu.setItems(itemRemoveFromGroup, new MenuItemSeparator(), itemAddGroup, itemAddUser);
		else
			menu.setItems(itemRemoveGroup, new MenuItemSeparator(), itemAddGroup, itemAddUser);
		
		
		menu.showContextMenu();
	}
	
	private void deleteGroup(final String group) {
		SC.confirm("Delete group '" + group + "' ?", new BooleanCallback() {
			
			@Override
			public void execute(Boolean value) {
				if (value) {
					service.deleteGroup(group, new AsyncCallback<Boolean>() {
						
						@Override
						public void onSuccess(Boolean result) {
							if (result) {
								updateTreeData();
							} else {
								SC.warn("Could not remove group");
							}
						}
						
						@Override
						public void onFailure(Throwable caught) {
							SC.warn("RPC failure");
						}
					});
				}
			}
		});
	}
}
