package cz.cuni.amis.pogamut.ut2004.server.commands.dialog;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.DialogBegin;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.DialogEnd;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.DialogItem;
import cz.cuni.amis.pogamut.ut2004.server.commands.PlayerCommunication;

/**
 * This class represents a dialog in UT. One dialog should be displayed only
 * once, if you want to show same looking again, use the same main panel.
 * 
 * @author Radim Vansa <radim.vansa@matfyz.cz>
 * 
 */
public class Dialog {
	private static int dialogCount = 0;

	/**
	 * Removes backslashes from the string, transforming two following
	 * backslashes into one and removing all other.
	 * 
	 * @param unclean
	 *            String to be cleaned.
	 * @return
	 */
	public final static String cleanEscaped(String unclean) {
		// String.replaceAll also doesn't like backslashes
		StringBuilder sb = new StringBuilder();
		boolean escaped = false;
		char c;
		for (int i = 0; i < unclean.length(); ++i) {
			c = unclean.charAt(i);
			if (c == '\\') {
				if (escaped) {
					escaped = false;
					sb.append('\\');
				} else {
					escaped = true;
				}
			} else {
				escaped = false;
				sb.append(c);
			}
		}
		return sb.toString();
	}

	/**
	 * Splits specified string into list according to the delimiter. The
	 * delimiter can be escaped by backslash and then is treated as normal
	 * character.
	 * 
	 * @param s
	 *            The splitted string
	 * @param delim
	 *            Delimiter
	 * @param max
	 *            Maximum number of elements in the list - after reaching of
	 *            this number is the rest inserted into list without reading of
	 *            the content
	 * @return
	 */
	public final static List<String> splitEscaped(String s, char delim, int max) {
		// we cannot use the regular split because of the backslashes
		List<String> list = new LinkedList<String>();
		if (s.isEmpty()) {
			return list;
		}
		int prev = -1, i = -1;
		while ((i = s.indexOf(delim, i + 1)) != -1) {
			int bs = 0;
			for (int j = i - 1; j >= 0 && s.charAt(j) == '\\'; --j) {
				++bs;
			}
			if (bs % 2 == 0) {
				list.add(s.substring(prev + 1, i));
				prev = i;
			}
			if (list.size() >= max - 1) {
				break;
			}
		}
		list.add(s.substring(prev + 1));
		return list;
	}

	/** called when the DialogCommand returns */
	protected List<IDialogListener> listeners = new LinkedList<IDialogListener>();

	/** data results from the dialog - read only */
	protected Map<String, String> data;

	/** whether the dialog is currently displayed */
	protected boolean displayed = false;
	/** whether the dialog was used */
	protected boolean finished = false;

	/** unique id of the dialog */
	private String id;
	/** main panel of the dialog */
	protected DialogPanel mainPanel;

	/** player who should interact with the dialog */
	protected String targetPlayer;

	protected PlayerCommunication playerCommunication;

	/** constructor with generated id */
	public Dialog() {
		++dialogCount;
		id = "d" + dialogCount;
	}

	/** constructor with specified id */
	public Dialog(String id) {
		++dialogCount;
		this.id = id;
	}

	/**
	 * Creates begin message corresponding to this dialog
	 * 
	 * @param playerName
	 * @return
	 */
	public DialogBegin createBeginCommand() {
		DialogBegin begin = new DialogBegin();
		begin.setPlayer(targetPlayer);
		begin.setId(id);
		return begin;
	}

	/**
	 * Creates end message corresponding to this dialog
	 * 
	 * @return
	 */
	public DialogEnd createEndCommand() {
		DialogEnd end = new DialogEnd();
		end.setPlayer(targetPlayer);
		end.setId(id);
		return end;
	}

	/**
	 * Creates a list of DialogItem commands
	 * 
	 * @return
	 */
	public List<DialogItem> createItemCommands() {
		if (mainPanel != null) {
			return mainPanel.createItemCommands();
		} else {
			throw new IllegalStateException("Main panel not set!");
		}
	}

	public DialogComponent findComponent(String id) {
		return mainPanel.findComponent(id);
	}

	/**
	 * Returns an identifier of this dialog
	 * 
	 * @return id
	 */
	public String getId() {
		return id;
	}

	public DialogPanel getMainPanel() {
		return mainPanel;
	}

	public String getTargetPlayer() {
		return targetPlayer;
	}

	public boolean isFinished() {
		return finished;
	}

	/**
	 * Calls callback in a way specified by the command
	 * 
	 * @param command
	 * @param command
	 * @return True if the dialog is no longer needed.
	 */
	public boolean processCommand(String sourceId, String command) {
		DialogComponent component = findComponent(sourceId);
		if (command.equalsIgnoreCase("SUBMIT")) {
			if (component instanceof IHasAction) {
				((IHasAction) component).dialogSubmitted(this, data);
			}
			for (IDialogListener listener: listeners) {
				listener.dialogSubmitted(this, data);
			}
			finished = true;
			return true;
		} else if (command.equalsIgnoreCase("CANCEL")) {
			if (component instanceof IHasAction) {
				((IHasAction) component).dialogCancelled(this, data);
			}
			for (IDialogListener listener: listeners) {
				listener.dialogCancelled(this, data);
			}
			finished = true;
			return true;
		} else {
			// if one command says CLOSE, then close
			if (component instanceof IHasAction) {
				((IHasAction) component).dialogCommand(this, command, data);
			}
			for (IDialogListener listener: listeners) {
				listener.dialogCommand(this, command, data);
			}
			return false;
		}
	}

	public void addListener(IDialogListener listener) {
		listeners.add(listener);
	}

	/**
	 * Sets data from DialogCommand info message
	 * 
	 * @param data
	 */
	public void setData(String data) {
		// cannot use regular split because of the escaping
		List<String> pairs = splitEscaped(data, '&', Integer.MAX_VALUE);
		Map<String, String> map = new HashMap<String, String>();
		for (String pair : pairs) {
			List<String> pl = splitEscaped(pair, '=', 2);
			map.put(cleanEscaped(pl.get(0)), cleanEscaped(pl.get(1)));
		}
		this.data = Collections.unmodifiableMap(map);
	}

	/**
	 * Sets the main panel
	 * 
	 * @param panel
	 */
	public void setMainPanel(DialogPanel panel) {
		mainPanel = panel;
	}

	public void setTargetPlayer(String targetPlayer) {
		this.targetPlayer = targetPlayer;
	}

	public boolean isDisplayed() {
		return displayed;
	}

	public void setDisplayed() {
		displayed = true;
	}

	public void setPlayerCommunication(PlayerCommunication playerCommunication) {
		this.playerCommunication = playerCommunication;		
	}

	public PlayerCommunication getPlayerCommunication() {
		return playerCommunication;
	}
}
