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


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

import org.inria.bmajwatcher.client.services.BankEditingServiceAsync;
import org.inria.bmajwatcher.client.ui.styles.MainCss;
import org.inria.bmajwatcher.client.ui.styles.Resources;

import com.allen_sauer.gwt.dnd.client.PickupDragController;
import com.allen_sauer.gwt.dnd.client.drop.DropController;
import com.allen_sauer.gwt.dnd.client.drop.HorizontalPanelDropController;
import com.allen_sauer.gwt.dnd.client.drop.VerticalPanelDropController;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
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.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.user.client.Random;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.ComplexPanel;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
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.Panel;
import com.google.gwt.user.client.ui.PushButton;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.smartgwt.client.util.SC;

/**
 * Component for building, reordering, editing the post or pre processes.
 * 
 * @author rsabas
 *
 */
public class ProcessEditor extends VerticalPanel {

	public static final String PRE = "pre";
	public static final String POST = "post";
	public static final String REMOVE = "remove";
	
	private static final String PRE_PROCESS = "PRE_PROCESS";
	private static final String REMOVE_PROCESS = "REMOVE_PROCESS";
	
	private MainCss css = Resources.INSTANCE.mainCss();
	private PickupDragController dragBlock = null;
	private PickupDragController dragMeta = null;
	private PickupDragController dragProcess = null;
	private Map<Panel, DropController> registeredDropControllers;
	private Button newProcess;
	
	private AbsolutePanel container;
	private String type;
	private Map<String, String> processes;
	private Collection<String> availablePs;
	
	private static final int MAX_RAND = 1000000;
	private static final String PREFIX = "misc__";
	
	public ProcessEditor() {
		super();
		BankEditingServiceAsync.INSTANCE.getProcessList(new AsyncCallback<Collection<String>>() {
			
			@Override
			public void onSuccess(Collection<String> result) {
				availablePs = result;
			}
			
			@Override
			public void onFailure(Throwable caught) {
				SC.warn("Could not retrieve process list");
			}
		});
	}
	
	public void load(Map<String, String> content, String type) {
		clear();
		
		this.type = type;
		container = new AbsolutePanel();
		processes = new HashMap<String, String>();
		
		registeredDropControllers = new HashMap<Panel, DropController>();
		
		if (dragBlock != null)
			dragBlock.unregisterDropControllers();
		if (dragMeta != null)
			dragMeta.unregisterDropControllers();
		if (dragProcess != null)
			dragProcess.unregisterDropControllers();
		
		dragBlock = new PickupDragController(container, false);
		dragMeta = new PickupDragController(container, false);
		dragMeta.addDragHandler(new MetaDragHandler(this));
		dragProcess = new PickupDragController(container, false);
		dragProcess.addDragHandler(new ProcessDragHandler(this));
		
		
		final VerticalPanel parent = new VerticalPanel();
		parent.addStyleName(css.processEditorContainer());
		
		newProcess = new Button("New process");
		newProcess.addClickHandler(new ClickHandler() {
			
			@Override
			public void onClick(ClickEvent event) {
				String blockName = "B" + Random.nextInt(MAX_RAND);
				if (ProcessEditor.this.type.equals(PRE))
					blockName = PRE_PROCESS;
				else if (ProcessEditor.this.type.equals(REMOVE))
					blockName = REMOVE_PROCESS;
				parent.add(getBlockPanel(parent, blockName, true));
				newProcess.setVisible(false);
			}
		});
		
		generateBlocks(content, parent);
		
		container.add(newProcess);
		container.add(parent);
		add(container);
	}
	
	/**
	 * Generates the blocks from the given properties.
	 * 
	 * @param content
	 * @param parent
	 */
	private void generateBlocks(Map<String, String> content, VerticalPanel parent) {
		List<String> validBlocks = new ArrayList<String>();
		String prefix = ".";
		
		Set<String> keys = content.keySet();
		if (!this.type.equals(POST)) { // pre process or remove process
			
			String tmp = PRE_PROCESS + ".db." + type + ".process";
			if (this.type.equals(REMOVE))
				tmp = REMOVE_PROCESS + ".db." + type + ".process";
			
			if (keys.contains(tmp)) {
				content.put("db." + type + ".process", content.get(tmp));
				validBlocks.add("");
				prefix = "";
				content.remove(tmp);
			} else if (keys.contains("db." + type + ".process")) {
				validBlocks.add("");
				prefix = "";
			}
		} else {
			if (keys.contains("BLOCKS")) {
				String[] split = content.get("BLOCKS").split(",");
				for (String s : split)
					validBlocks.add(s);
			} else if (keys.contains("db." + type + ".process")) {
				validBlocks.add("");
				prefix = "";
			}
		}
		
		int added = 0;
		if (validBlocks.size() > 0) {
			
			newProcess.setVisible(false);
			for (String block : validBlocks) {
				
				if (content.containsKey(block + prefix + "db." + type + ".process")) { // Can be either pre or post
					
					String[] metas = content.get(block + prefix + "db." + type + ".process").split(",");
					
					if (block.isEmpty()) {
						if (this.type.equals(PRE))
							block = PRE_PROCESS;
						else if (this.type.equals(REMOVE))
							block = REMOVE_PROCESS;
						else
							block = "B" + Random.nextInt(MAX_RAND);
					}
					ComplexPanel blockPnl = getBlockPanel(parent, block, false);
					parent.add(blockPnl);
					HorizontalPanel blockContent = (HorizontalPanel) ((ComplexPanel) blockPnl.getWidget(0)).getWidget(1);
					
					for (String meta : metas) {
						if (meta.isEmpty())
							break;
						ComplexPanel metaPnl = getMetaPanel(blockContent, meta, false);
						blockContent.add(metaPnl);
						VerticalPanel metaContent = (VerticalPanel) ((ComplexPanel) metaPnl.getWidget(0)).getWidget(1);
						
						String[] processes = content.get(meta).split(",");
						for (String process : processes) {
							this.processes.put(PREFIX + process + ".name", content.get(process + ".name"));
							this.processes.put(PREFIX + process + ".exe", content.get(process + ".exe"));
							this.processes.put(PREFIX + process + ".args", content.get(process + ".args"));
							this.processes.put(PREFIX + process + ".type", content.get(process + ".type"));
							this.processes.put(PREFIX + process + ".desc", content.get(process + ".desc"));
							this.processes.put(PREFIX + process + ".cluster", content.get(process + ".cluster"));
							
							ComplexPanel processPnl = getProcessPanel(metaContent, process);
							metaContent.add(processPnl);
						}
					}
					added++;
				}
			}
		}
		if (added == 0) {
			newProcess.setVisible(true);
		}
	}
	
	
	/**
	 * Generates a title panel for meta and block elements.
	 * It contains an editable link that contains the block/meta name.
	 * 
	 * @param type
	 * @param name
	 * @return
	 */
	private HorizontalPanel getTitlePanel(String type, String name) {
		final HorizontalPanel hp = new HorizontalPanel();
		hp.addStyleName(css.editorPanelTitle());
		Label lbl = new Label(type + " :"); // Meta or Block
		lbl.addStyleName(css.processEditorLabel());
		final Anchor link = new Anchor(name);
		link.setName(type);
		final TextBox tb = new TextBox();
		tb.addStyleName(css.processEditorTitleTxt());
		tb.setVisible(false);
		
		
		if (this.type.equals(POST) || (this.type.equals(PRE) || this.type.equals(REMOVE)) && type.equals("Meta")) {
			/*
			 * When the link is clicked, it is hidden and a textbox is shown
			 * to allow the modification of the link value.
			 */
			link.addClickHandler(new ClickHandler() {
				
				@Override
				public void onClick(ClickEvent event) {
					link.setVisible(false);
					tb.setVisible(true);
					tb.setText(link.getText());
					tb.setFocus(true);
				}
			});
		}
		
		/*
		 * When enter or escape key are hit, the textbox is hidden
		 * and link is made visible with the new entered value if enter
		 * was hit or the previous value if escape was hit.
		 */
		tb.addKeyPressHandler(new KeyPressHandler() {
			
			@Override
			public void onKeyPress(KeyPressEvent event) {
				if (event.getCharCode() == KeyCodes.KEY_ENTER) {
					if (!tb.getText().trim().isEmpty()) {
						link.setText(tb.getText());
						tb.setVisible(false);
						link.setVisible(true);
					}
				} else if (event.getCharCode() == KeyCodes.KEY_ESCAPE) {
					tb.setVisible(false);
					link.setVisible(true);
				}
			}
		});
		/*
		 * Focus lost handler.
		 */
		tb.addBlurHandler(new BlurHandler() {
			
			@Override
			public void onBlur(BlurEvent event) {
				tb.setVisible(false);
				link.setVisible(true);
			}
		});
		
		hp.add(lbl);
		hp.add(link);
		hp.add(tb);
		hp.setCellWidth(lbl, "60px");
		
		return hp;
	}
	
	/**
	 * Builds a panel that contains a block.
	 * Blocks are added vertically (sequential)
	 * 
	 * @param parent
	 * @param blockName
	 * @param autoAdd whether to generated empty children
	 * @return
	 */
	private ComplexPanel getBlockPanel(final VerticalPanel parent, String blockName, boolean autoAdd) {
		final HorizontalPanel main = new HorizontalPanel();
		main.addStyleName(css.processEditorPanel());
		
		final HorizontalPanel content = new HorizontalPanel();
		
		VerticalPanel vp = new VerticalPanel();
		HorizontalPanel title = getTitlePanel("Block", blockName);
		vp.add(title);
		vp.add(content);
		
		vp.addStyleName(css.blockPanel());
		VerticalPanel buttons = new VerticalPanel();
		
		
		
		dragBlock.makeDraggable(main, (Label) title.getWidget(0));
		if (!registeredDropControllers.containsKey(parent)) {
			VerticalPanelDropController drop = new VerticalPanelDropController(parent);
			dragBlock.registerDropController(drop);
			registeredDropControllers.put(parent, drop);
		}
		
		PushButton remove = new PushButton(new Image("images/list-remove.png"));
		remove.addStyleName(css.processEditorButton());
		remove.addClickHandler(new ClickHandler() {
			
			@Override
			public void onClick(ClickEvent event) {
				for (int i = 0; i < content.getWidgetCount(); i++) {
					ComplexPanel processParent = (ComplexPanel) ((ComplexPanel) ((ComplexPanel) content.getWidget(i)).getWidget(0)).getWidget(1);
					dragProcess.unregisterDropController(registeredDropControllers.get(processParent));
					registeredDropControllers.remove(processParent);
				}
				dragMeta.unregisterDropController(registeredDropControllers.get(content));
				
				if (((ComplexPanel) main.getParent()).getWidgetCount() == 1)
					newProcess.setVisible(true);
				main.removeFromParent();
			}
		});
		if (this.type.equals(POST)) {
			PushButton add = new PushButton(new Image("images/list-add.png"));
			add.addStyleName(css.processEditorButton());
			add.addClickHandler(new ClickHandler() { // Add a new block
				
				@Override
				public void onClick(ClickEvent event) {
					parent.add(getBlockPanel(parent, "B" + Random.nextInt(MAX_RAND), true));
				}
			});
			
			buttons.add(add);
		}
		buttons.add(remove);
		
//		main.add(content);
		main.add(vp);
		main.add(buttons);
		
		if (autoAdd)
			content.add(getMetaPanel(content, "M" + Random.nextInt(MAX_RAND), true));
		return main;
	}
	
	/**
	 * Builds a panel that contains a metaprocess.
	 * Metaprocesses are added horizontally (parallel)
	 * 
	 * @param parent
	 * @param metaName
	 * @param autoAdd whether to generated empty children
	 * @return
	 */
	private ComplexPanel getMetaPanel(final HorizontalPanel parent, String metaName, boolean autoAdd) {
		final HorizontalPanel main = new HorizontalPanel();
		main.addStyleName(css.processEditorPanel());
		
		final VerticalPanel content = new VerticalPanel();
		VerticalPanel vp = new VerticalPanel();
		HorizontalPanel title = getTitlePanel("Meta", metaName);
		vp.add(title);
		vp.add(content);
		vp.addStyleName(css.metaPanel());
		
		
		dragMeta.makeDraggable(main, (Label) title.getWidget(0));
		if (!registeredDropControllers.containsKey(parent)) {
			HorizontalPanelDropController drop = new HorizontalPanelDropController(parent);
			dragMeta.registerDropController(drop);
			registeredDropControllers.put(parent, drop);
		}
		
		VerticalPanel buttons = new VerticalPanel();
		
		PushButton add = new PushButton(new Image("images/list-add.png"));
		add.addClickHandler(new ClickHandler() { // Add a metaprocess
			
			@Override
			public void onClick(ClickEvent event) {
				// Recalculate parent because it can have changed after a drag.
				HorizontalPanel newParent = (HorizontalPanel) main.getParent();
				newParent.add(getMetaPanel(newParent, "M" + Random.nextInt(MAX_RAND), true));
			}
		});
		add.addStyleName(css.processEditorButton());
		
		PushButton remove = new PushButton(new Image("images/list-remove.png"));
		remove.addClickHandler(new ClickHandler() {
			
			@Override
			public void onClick(ClickEvent event) {
				HorizontalPanel newParent = (HorizontalPanel) main.getParent();
				dragProcess.unregisterDropController(registeredDropControllers.get(content));
				registeredDropControllers.remove(content);

				main.removeFromParent();
				cleanMetaPanel(newParent);
			}
		});
		remove.addStyleName(css.processEditorButton());
		buttons.add(add);
		buttons.add(remove);
		
		main.add(vp);
		main.add(buttons);
		
		if (autoAdd)
			content.add(getProcessPanel(content, "P" + Random.nextInt(MAX_RAND)));
		
		return main;
	}
	
	
	/**
	 * Builds the panel that contains a process.
	 * Processes are added vertically (sequential).
	 * 
	 * @param parent
	 * @return
	 */
	private ComplexPanel getProcessPanel(final VerticalPanel parent, final String keyName) {
		
		final HorizontalPanel main = new HorizontalPanel();
		main.addStyleName(css.processEditorPanel());
		
		VerticalPanel content = new VerticalPanel();
		Label title = new Label("Process");
		title.addStyleName(css.processEditorLabel());
		VerticalPanel vp = new VerticalPanel();
		HorizontalPanel hp = new HorizontalPanel();
		hp.addStyleName(css.editorPanelTitle());
		hp.add(title);
		vp.add(hp);
		vp.add(content);
		vp.addStyleName(css.processPanel());
		
		final Anchor lbl = new Anchor(keyName);
		lbl.setName("Process");
		lbl.addClickHandler(new ClickHandler() {
			
			@Override
			public void onClick(ClickEvent event) {
				showProcessDetail(lbl);
			}
		});
		content.add(lbl);
		content.setCellHeight(lbl, "35px");
		content.setCellVerticalAlignment(lbl, ALIGN_MIDDLE);
		
		dragProcess.makeDraggable(main, title);
		if (!registeredDropControllers.containsKey(parent)) {
			VerticalPanelDropController drop = new VerticalPanelDropController(parent);
			dragProcess.registerDropController(drop);
			registeredDropControllers.put(parent, drop);
		}
		
		VerticalPanel buttons = new VerticalPanel();
		
		PushButton add = new PushButton(new Image("images/list-add.png"));
		add.addClickHandler(new ClickHandler() {
			
			@Override
			public void onClick(ClickEvent event) {
				// Recalculate parent because it can have changed after a drag.
				VerticalPanel newParent = (VerticalPanel) main.getParent();
				newParent.add(getProcessPanel(newParent, "P" + Random.nextInt(MAX_RAND)));
			}
		});
		add.addStyleName(css.processEditorButton());
		
		PushButton remove = new PushButton(new Image("images/list-remove.png"));
		remove.addClickHandler(new ClickHandler() {
			
			@Override
			public void onClick(ClickEvent event) {
				VerticalPanel newParent = (VerticalPanel) main.getParent();
				main.removeFromParent();
				cleanProcessPanel(newParent);
			}
		});
		remove.addStyleName(css.processEditorButton());
		buttons.add(add);
		buttons.add(remove);
		
		main.add(vp);
		main.add(buttons);
		
		return main;
	}
	
	/**
	 * Generates a dialog box with editable properties
	 * for a process.
	 * 
	 */
	private void showProcessDetail(final Anchor anc) {
		
		final String keyName = anc.getText();
		String name = "";
		String exe = "";
		String args = "";
		String desc = "";
		String type = "";
		boolean cluster = false;
		
		if (processes.get(PREFIX + keyName + ".name") != null)
			name = processes.get(PREFIX + keyName + ".name");
		if (processes.get(PREFIX + keyName + ".exe") != null)
			exe = processes.get(PREFIX + keyName + ".exe");
		if (processes.get(PREFIX + keyName + ".args") != null)
			args = processes.get(PREFIX + keyName + ".args");
		if (processes.get(PREFIX + keyName + ".desc") != null)
			desc = processes.get(PREFIX + keyName + ".desc");
		if (processes.get(PREFIX + keyName + ".type") != null)
			type = processes.get(PREFIX + keyName + ".type");
		if (processes.get(PREFIX + keyName + ".cluster") != null)
			cluster = processes.get(PREFIX + keyName + ".cluster").equals("true");

		
		final DialogBox dialog = new DialogBox(false, true);
		dialog.addStyleName(css.processDetail());
		VerticalPanel main = new VerticalPanel();
		VerticalPanel content = new VerticalPanel();
		HorizontalPanel buttons = new HorizontalPanel();
		
		main.add(content);
		main.add(buttons);
		
		final TextBox txtKey = new TextBox();
		txtKey.setValue(keyName);
		txtKey.addStyleName(css.processParameter());
		Label lblKey = new Label("Key name :");
		lblKey.setWidth("150px");
		
		final TextBox txtName = new TextBox();
		txtName.setValue(name);
		txtName.addStyleName(css.processParameter());
		Label lblName = new Label("Name :");
		lblName.setWidth("150px");
		
//		final TextBox txtExe = new TextBox();
//		txtExe.setValue(exe);
//		txtExe.addStyleName(css.processParameter());
		Label lblExe = new Label("Executable :");
		lblExe.setWidth("150px");
		
		final TextBox txtArgs = new TextBox();
		txtArgs.setValue(args);
		txtArgs.addStyleName(css.processParameter());
		Label lblArgs = new Label("Arguments :");
		lblArgs.setWidth("150px");
		
		final TextArea txtDesc = new TextArea();
		txtDesc.setText(desc);
		txtDesc.addStyleName(css.processParameter());
		Label lblDesc = new Label("Description :");
		lblDesc.setWidth("150px");
		
		final TextBox txtType = new TextBox();
		txtType.setValue(type);
		txtType.addStyleName(css.processParameter());
		Label lblType = new Label("Type :");
		lblType.setWidth("150px");
		
		Label lblCluster = new Label("Execute on cluster :");
		lblCluster.setWidth("150px");
		final CheckBox cbCluster = new CheckBox();
		cbCluster.setValue(cluster);
		
		HorizontalPanel hpKey = new HorizontalPanel();
		hpKey.add(lblKey);
		hpKey.add(txtKey);
		
		HorizontalPanel hpName = new HorizontalPanel();
		hpName.add(lblName);
		hpName.add(txtName);
		
		HorizontalPanel hpExe = new HorizontalPanel();
		hpExe.add(lblExe);
		
		MySuggestOracle oracle = new MySuggestOracle(availablePs);
		final SuggestBox suggExe = new SuggestBox(oracle);
		suggExe.setText(exe);
		suggExe.addStyleName(css.suggestBox());
		suggExe.addKeyUpHandler(new KeyUpHandler() {
			
			@Override
			public void onKeyUp(KeyUpEvent event) {
				if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
					String value = suggExe.getText();
					if (value.contains("."))
						txtName.setText(value.substring(0, value.lastIndexOf('.')));
					else
						txtName.setText(value);
					
					BankEditingServiceAsync.INSTANCE.getProcessDescription(value, new AsyncCallback<String>() {
						
						@Override
						public void onSuccess(String result) {
							txtDesc.setText(result);
						}
						
						@Override
						public void onFailure(Throwable caught) {
							SC.warn("Could not retrieve process description");
						}
					});
				}
			}
		});

		hpExe.add(suggExe);
		Image hint = new Image("images/help-about.png");
		hint.addStyleName(css.helpIcon());
		hint.setTitle("The complete process list can be obtained with '*' character");
		hpExe.add(hint);
		hpExe.setCellVerticalAlignment(hint, HasVerticalAlignment.ALIGN_MIDDLE);
		
		
		
		HorizontalPanel hpArgs = new HorizontalPanel();
		hpArgs.add(lblArgs);
		hpArgs.add(txtArgs);
		
		HorizontalPanel hpDesc = new HorizontalPanel();
		hpDesc.add(lblDesc);
		hpDesc.add(txtDesc);
		
		HorizontalPanel hpType = new HorizontalPanel();
		hpType.add(lblType);
		hpType.add(txtType);
		
		HorizontalPanel hpCluster = new HorizontalPanel();
		hpCluster.add(lblCluster);
		hpCluster.add(cbCluster);
		
		
		Button ok = new Button("Ok");
		ok.addClickHandler(new ClickHandler() {
			
			@Override
			public void onClick(ClickEvent event) {
				boolean ok = true;
				String keyText = txtKey.getText();
				// Check existence and unicity of key
				if (keyText.trim().isEmpty() || (processes.containsKey("__" + keyText + ".name") && !keyName.equals(keyText))) {
					ok = false;
					txtKey.addStyleName(css.bankEditorErrorWidget());
				} else
					txtKey.removeStyleName(css.bankEditorErrorWidget());
				
				if (txtName.getText().trim().isEmpty()) {
					ok = false;
					txtName.addStyleName(css.bankEditorErrorWidget());
				} else
					txtName.removeStyleName(css.bankEditorErrorWidget());
				
				if (suggExe.getText().trim().isEmpty()) {
					ok = false;
					suggExe.addStyleName(css.bankEditorErrorWidget());
				} else
					suggExe.removeStyleName(css.bankEditorErrorWidget());
				
				if (ok) {
					String newKeyName = keyName;
					if (!keyName.equals(keyText)) {
						newKeyName = keyText;
						anc.setText(newKeyName);
						processes.remove(PREFIX + keyName + ".name");
						processes.remove(PREFIX + keyName + ".exe");
						processes.remove(PREFIX + keyName + ".args");
						processes.remove(PREFIX + keyName + ".desc");
						processes.remove(PREFIX + keyName + ".type");
						processes.remove(PREFIX + keyName + ".cluster");
					}
					processes.put(PREFIX + newKeyName + ".name", txtName.getText());
					processes.put(PREFIX + newKeyName + ".exe", suggExe.getText());
					processes.put(PREFIX + newKeyName + ".args", txtArgs.getText());
					processes.put(PREFIX + newKeyName + ".desc", txtDesc.getText());
					processes.put(PREFIX + newKeyName + ".type", txtType.getText());
					processes.put(PREFIX + newKeyName + ".cluster", String.valueOf(cbCluster.getValue()));
					
					dialog.hide();
				}
			}
		});
		Button cancel = new Button("Cancel");
		cancel.addClickHandler(new ClickHandler() {
			
			@Override
			public void onClick(ClickEvent event) {
				dialog.hide();
			}
		});
		buttons.add(ok);
		buttons.add(cancel);
		buttons.setSpacing(5);
		main.setCellHorizontalAlignment(buttons, HasHorizontalAlignment.ALIGN_CENTER);
		
		content.add(hpKey);
		content.add(hpExe);
		content.add(hpName);
		content.add(hpArgs);
		content.add(hpDesc);
		content.add(hpType);
		content.add(hpCluster);
		
		dialog.setWidget(main);
		dialog.setHTML("<b>Process edition</b>");
		
		dialog.center();
		
		
	}
	
	/**
	 * Browse the given process panel parents to delete
	 * orphans after a drag operation.
	 * 
	 * @param parent
	 */
	protected void cleanProcessPanel(ComplexPanel parent) {
		if (parent.getWidgetCount() == 0) { // If the meta is empty
			ComplexPanel main = (ComplexPanel) parent.getParent().getParent();
			ComplexPanel greatParent = (ComplexPanel) main.getParent(); // BLOCK
			
			dragProcess.unregisterDropController(registeredDropControllers.get(parent));
			registeredDropControllers.remove(parent);
			main.removeFromParent();
			
			if (greatParent.getWidgetCount() == 0) { // if the block is empty
				dragMeta.unregisterDropController(registeredDropControllers.get(greatParent));
				registeredDropControllers.remove(greatParent);
				ComplexPanel cp = (ComplexPanel) greatParent.getParent().getParent().getParent();
				greatParent.getParent().getParent().removeFromParent();
				
				if (cp.getWidgetCount() == 0)
					newProcess.setVisible(true);
			}
		}
	}
	
	/**
	 * Browse the given meta panel parents to delete
	 * orphans after a drag operation.
	 * 
	 * @param parent
	 */
	protected void cleanMetaPanel(ComplexPanel parent) {
		if (parent.getWidgetCount() == 0) {
			ComplexPanel block = (ComplexPanel) parent.getParent().getParent();
			dragMeta.unregisterDropController(registeredDropControllers.get(parent));
			registeredDropControllers.remove(parent);
			ComplexPanel blockParent = (ComplexPanel) block.getParent();
			block.removeFromParent();
			
			if (blockParent.getWidgetCount() == 0)
				newProcess.setVisible(true);
			
			
		}
	}
	
	public Map<String, String> getProcessProperties() {
		
		Map<String, String> res = new HashMap<String, String>();
		List<String> metas = new ArrayList<String>();
		List<String> processes = new ArrayList<String>();
		
		StringBuilder sb = new StringBuilder();
		getProcessProperties(container,sb);
		if (sb.length() == 0)
			return res;
		
		String str = sb.deleteCharAt(sb.lastIndexOf(",")).toString();
		String[] procs = str.split(",");
		String currentBlock = "";
		String currentMeta = "";
		

		StringBuilder blockLine = new StringBuilder("BLOCKS=");
		StringBuilder metaLine = new StringBuilder();
		StringBuilder processLine = new StringBuilder();
		
		for (String s : procs) {
			String[] split = s.split(":");
			if (split[0].equals("BLOCK")) {
				if (metaLine.length() != 0)
					metas.add(metaLine.substring(0, metaLine.length() - 1));
				currentBlock = split[1];
				blockLine.append(currentBlock + ",");
				metaLine = new StringBuilder();
			} else if (split[0].equals("META")) {
				if (metaLine.length() == 0)
					metaLine = new StringBuilder(currentBlock + ".db." + this.type + ".process=");
				if (processLine.length() != 0)
					processes.add(processLine.substring(0, processLine.length() - 1));
				currentMeta = split[1];
				metaLine.append(currentMeta + ",");
				processLine = new StringBuilder();
			} else if (split[0].equals("PROC")) {
				if (processLine.length() == 0)
					processLine = new StringBuilder(currentMeta + "=");
				processLine.append(split[1] + ",");
			}
		}
		metas.add(metaLine.deleteCharAt(metaLine.lastIndexOf(",")).toString());
		processes.add(processLine.deleteCharAt(processLine.lastIndexOf(",")).toString());
		blockLine.deleteCharAt(blockLine.lastIndexOf(","));
		
		List<String> addedProcesses = new ArrayList<String>(); // contains processes key names
		
		if (processLine.length() > 0) {
			String[] split = blockLine.toString().split("=");
			if (this.type.equals(POST))
				res.put(PREFIX + split[0], split[1]);
			for (String s : metas) {
				split = s.split("=");
				res.put(PREFIX + split[0], split[1]);
			}
			for (String s : processes) {
				split = s.split("=");
				res.put(PREFIX + split[0], split[1]);
				
				if (split[1].contains(",")) {
					for (String pp : split[1].split(",")) {
						addedProcesses.add(pp);
					}
				} else {
					addedProcesses.add(split[1]);
				}
			}
		}
		
		// Cleaning removed processes
		List<String> keys = new ArrayList<String>();
		for (String pcs : this.processes.keySet()) {
			if (!startsWithAny(pcs, addedProcesses))
				keys.add(pcs);
		}
		for (String key : keys)
			this.processes.remove(key);
		
		
		res.putAll(this.processes);
		return res;
	}
	
	private boolean startsWithAny(String ref, List<String> toCompare) {
		for (String s : toCompare) {
			if (ref.startsWith(PREFIX + s))
				return true;
		}
		
		return false;
	}
	
	/**
	 * Browse the elements and builds the related properties.
	 * 
	 * @return
	 */
	private void getProcessProperties(Widget widget, StringBuilder sb) {
		if (widget instanceof Anchor) {
			Anchor anc = (Anchor) widget;
			if (anc.getName().equals("Block")) {
				sb.append("BLOCK:" + anc.getText() + ",");
			} else if (anc.getName().equals("Meta")) {
				sb.append("META:" + anc.getText() + ",");
			} else if (anc.getName().equals("Process")) {
				sb.append("PROC:" + anc.getText() + ",");
			}
		} else if (widget instanceof ComplexPanel) {
			ComplexPanel pnl = (ComplexPanel) widget;
			for (int i = 0; i < pnl.getWidgetCount(); i++) {
				getProcessProperties(pnl.getWidget(i), sb);
			}
		}
	}
	
}
