
// Définition de la classe 'CarSelectorChoices'
// NB: L'ordre des méthodes est important
CarSelectorChoices.Instances = new Array();

function CarSelectorChoices(id)
{
	// Outil pour récupérer une instance par son identifiant
	CarSelectorChoices.Instances[id] = this;

	// Constantes
	var EXPAND_DELAY = 300;			// Délai pour qu'une catégorie se déploie, en millisecondes
	var EXPAND_FRAMERATE = 20;		// Temps d'attente entre deux frames de l'animation
	var EXPAND_SPEED = 5;			// Incrémentation par frame en pourcents, tel que (100 % EXPAND_SPEED) == 0.

	// Champs privés
	var _timerId = null;
	var _history = new Array();
	var _callStack = new Array();

	var _animId;
	var _overGroupId;
	var _currentGroupId;
	var _previousGroupId;

	// Propriétés
	this.HtmlObject = document.getElementById(id);
	this.OnChoiceExpanded = new Array();
	this.OnChoiceOver = new Array();
	this.OnChoiceOut = new Array();
	this.OnChanged = new Array();

	// Méthodes privées

	var ClearTimer = function()
	{
		if (_timerId != null)
		{
			clearTimeout(_timerId);
			_timerId = null;
		}
	}

	var SetTimer = function(todo, delay)
	{
		ClearTimer();
		_timerId = setTimeout(todo, delay);
	}
	
	// Mise à jour du nombre de choix possibles
	var SetAvailableCount = function(groupCode, count)
	{
		var helpCount = document.getElementById(id + "Group__" + groupCode + "__HelpCount");
		if (helpCount != null)
			helpCount.innerHTML = count;
	}
	

	// Méthodes publiques

	this.IsAnimating = function()
	{
		return (_animId != null);
	}

	this.GetGroupByCode = function(groupCode)
	{
		if (this.HtmlObject != null)
		{
			var groups = this.HtmlObject.getElementsByTagName("dl");
			for (var i = 0; i < groups.length; i++)
			{	
				var group = groups[i];
				if (groupCode == group.id.split("__")[1])
					return group;
			}
		}
		return null;
	}	

	// Si le groupId est 'null' alors on prend le groupe en cours,
	// ce qui permet de rafraichir le style des groupes si
	// par exemple un des groupes a changé de hauteur
	this.UpdateGroupStyles = function(groupId, animate)
	{
		if (this.HtmlObject == null)
			return ;

		if (groupId == null)
			_overGroupId = groupId = _currentGroupId;

		if (_overGroupId == groupId)
		{
			_previousGroupId = _currentGroupId;
			_currentGroupId = groupId;

			// Met à jour les styles des groupes
			var previousInnerHeight = 0;
			var topStack = (document.body.className == "IE6") ? 0 : Tools_GetPosition(this.HtmlObject).Top;

			// ???
			if (document.body.className.indexOf("Safari") != -1)
				topStack += 3;
			
			Tools_ForEach(this.HtmlObject.getElementsByTagName("dl"), function(group)
			{
				group.style.top = topStack + "px";
				if (group.id == _previousGroupId)
					previousInnerHeight = group.offsetHeight - group.getElementsByTagName("dt")[0].offsetHeight;
				group.className = (group.id == _currentGroupId) ? "Over" : "Out";
				var titleHeight = group.getElementsByTagName("dt")[0].offsetHeight;
				topStack += titleHeight;
				if (group.id == _previousGroupId)
				{
					topStack += previousInnerHeight;
					group.style.height = titleHeight + previousInnerHeight + "px";
				}
			});

			// Démarre l'animation
			this.AnimateGroups(groupId, animate);
		}
		else ClearTimer();
	}

	this.ExpandGroup = function(groupIndex, animate)
	{
		var groups = this.HtmlObject.getElementsByTagName("dl");
		if (groups.length <= groupIndex)
			return ;

		var group = groups[groupIndex];
		_overGroupId = group.id;

		// On lance en asynchrone pour éviter un bug sur Firefox
		setTimeout(function() { CarSelectorChoices.Instances[id].UpdateGroupStyles(group.id, animate); }, 0);	
	}

	this.OnLoad = function()
	{
		$(this.HtmlObject).find("dl").attr("class", "Out");
		this.ExpandGroup(0, true);
	}

	this.MoveGroups = function(newHeight, oldHeight, groupId, animId, percent)
	{
		if (_animId != animId)
			return ;

		var currentList = null, groupTop = 0;
		var topStack = (document.body.className == "IE6") ? 0 : Tools_GetPosition(this.HtmlObject).Top;
		
		// ???
		if (document.body.className.indexOf("Safari") != -1)
			topStack += 3;		
		
		Tools_ForEach(this.HtmlObject.getElementsByTagName("dl"), function(group)
		{
			var titleBlock = group.getElementsByTagName("dt")[0];
			group.style.top = topStack + "px";
			list = group.getElementsByTagName("ul")[0];
			list.style.overflow = "hidden";
						
			var titleHeight = titleBlock.offsetHeight;
			var height = titleHeight;
			if (group.id == groupId)
			{
				currentList = list;
				height += (newHeight - titleHeight) * (percent * 0.01);
				groupTop = topStack;
			}
			else if (group.id == _previousGroupId)
				height += (oldHeight - titleHeight) * (1 - percent * 0.01);

			height = (height && height > 0) ? Math.round(height) : 0;
			group.style.height = height + "px";
			
			var listHeight = (height - titleHeight);
			listHeight = (listHeight < 0) ? 0 : listHeight;
			list.style.height = listHeight + "px";
			topStack += height;
		});

		if (percent == 100)
		{
			if (currentList != null)
				currentList.style.overflow = "auto";
			Tools_DispatchEvent(this.OnChoiceExpanded, this, { GroupId: groupId, GroupTop: groupTop });
			_animId = null;
		}
	}

	this.AnimateGroups = function(groupId, animate)
	{
		// On calcule d'abord la future hauteur du groupe de choix en cours
		// et on récupère la hauteur du précédent groupe de choix
		var oldHeight = 0, newHeight = this.HtmlObject.offsetHeight;
		Tools_ForEach(this.HtmlObject.getElementsByTagName("dl"), function(group)
		{
			if (group.id != groupId)
			{
				newHeight -= group.getElementsByTagName("dt")[0].offsetHeight;
				if (group.id == _previousGroupId)
					oldHeight = group.offsetHeight;
			}
		});
		
		// On anime les groupes de choix
		_animId = Tools_CreateGuid();
		if (animate)
		{
			var s = "CarSelectorChoices.Instances['{0}'].MoveGroups({1}, {2}, '{3}', '{4}'";
			s = s.format(id, newHeight, oldHeight, groupId, _animId) + ", {0});";
			var end = 100 / EXPAND_SPEED;
			for (var t = 0; t <= end; t++)
			{
				var percent = Tools_Smooth(t / end) * 100;
				setTimeout(s.format(percent), EXPAND_FRAMERATE * (t + 1));
			}
		}
		else if (newHeight > 0)
			this.MoveGroups(newHeight, oldHeight, groupId, _animId, 100);
	}

	this.HistoryGetOldest = function()
	{
		return (_history.length > 0) ? _history[0] : null;
	}

	// Ajoute un choix à l'historique des choix en faisant
	// attention à ce qu'il n'y ait pas de doublons
	this.HistoryAdd = function(groupCode)
	{
		this.HistoryRemove(groupCode);
		_history.push(groupCode);
	}

	this.HistoryRemove = function(groupCode)
	{
		var index = Tools_IndexOf(_history, groupCode);
		if (index != -1)
			_history.splice(index, 1);
	}

	// Nettoie l'historique des choix
	this.HistoryClear = function()
	{
		_history = new Array();
	}	

	// Supprime la sélection au sein d'un groupe de choix
	this.ResetGroupSelection = function(group, ignoreDisabled)
	{
		if (group == null)
			return ;

		// On réinitialise l'aide contexuelle		
		var groupCode = group.id.split("__")[1];
		var prefix = id + "Group__" + groupCode + "__";
		Tools_Show(prefix + "Help", true);
		Tools_Show(prefix + "Name", false);

		// On réactive tous les choix
		var choices = group.getElementsByTagName("li");
		for (var i = 0; i < choices.length; i++)
		{
			var choice = choices[i];
			if (!ignoreDisabled || choice.className.indexOf("Disabled") < 0)
				choice.className = "Enabled";
		}
		
		SetAvailableCount(groupCode, choices.length);
	}

	// Evénement déclenché lorsqu'on clique sur un des choix
	this.OnClick = function(item, induced)
	{
		if (item == null)
			return ;
		
		// Un clic utilisateur sur un choix induit ne fait rien du tout
		if (!induced && (item.className.indexOf("Induced") != -1))
			return ;
				
		// On récupère le groupe et la valeur du choix
		var idParts = item.id.split("__");
		var groupCode = idParts[1];

		var wasDisabled = (item.className.indexOf("Disabled") >= 0);	// L'élément était-il désactivé ?
		var wasSelected = (item.className.indexOf("Selected") >= 0);	// L'élément était-il sélectionné ?

		// Pour éviter tout blocage d'inclusions imbriquées (dead lock)
		// on garde en mémoire la call stack des sélections et on brise
		// la chaîne dès qu'un cycle est détecté.
		if (wasSelected == false)
		{
			if (Tools_Contains(_callStack, groupCode))
				return ;
			_callStack.push(groupCode);
		}

		// On désélectionne tous les élements du groupe
		var group = document.getElementById(id + "Group__" + groupCode);
		this.ResetGroupSelection(group, /* true */ false);

		var help = document.getElementById(id + "Group__" + groupCode + "__Help");
		var reminder = document.getElementById(id + "Group__" + groupCode + "__Name");

		if (wasSelected)
		{
			help.style.display = "block";
			reminder.style.display = "none";
			item.className = "Enabled";										// L'utilisateur désélectionne
			this.HistoryRemove(groupCode);
		}
		else
		{
			help.style.display = "none";
			reminder.innerHTML = (item.textContent ? item.textContent : item.innerText);
			reminder.style.display = "block";
			item.className = induced ? "Selected Induced" : "Selected";		// On sélectionne l'élément choisi
			this.HistoryAdd(groupCode);										// On met à jour l'historique des choix
		}

		var args = { GroupCode: groupCode, Induced: (induced ? true : false) };
		Tools_DispatchEvent(this.OnChanged, this, args);
		
		if (wasSelected == false)
			_callStack.pop();
	}

	this.OnGroupOver = function(group)
	{
		_overGroupId = group.id;
		if (_currentGroupId != group.id)
		{
			var that = this;
			SetTimer(function() { that.UpdateGroupStyles(group.id, true); }, EXPAND_DELAY);
		}
	}	

	this.OpenGroup = function(overGroupId)
	{		      
		_overGroupId = overGroupId;
		if (_currentGroupId != overGroupId)
		{
			var that = this;
			SetTimer(function() { that.UpdateGroupStyles(overGroupId, true); }, EXPAND_DELAY);
		}
	}
	
	this.OnGroupOut = function(group)
	{
		_overGroupId = null;
	}
	
	this.OnItemOut = function(item, description)
	{
		if (item.className.indexOf("Over") != -1)
			item.className = item.className.replace(" Over", "");
		Tools_DispatchEvent(this.OnChoiceOut, this, item);
	}
	
	this.OnItemOver = function(item, description)
	{
		if (_animId == null && item.className.indexOf("Over") < 0)
			item.className += " Over";
		var args = { Top: Tools_GetPosition(item).Top, Height: item.offsetHeight, Description: description };
		Tools_DispatchEvent(this.OnChoiceOver, this, args);
	}

	// Met à jour le style des choix en fonction des résultats
	// qu'ils engendrent et de leur sélection
	this.UpdateSelection = function(selection, changedGroupCode, induced, items)
	{
		if (this.HtmlObject == null)
			return ;

		var toClick = new Array();
				
		// D'abord on s'occupe des styles
		var groups = this.HtmlObject.getElementsByTagName("dl");
		for (var i = 0; i < groups.length; i++)
		{
			var group = groups[i];
			if (group.id.indexOf("Group__") < 0)
				continue ;
			var groupCode = group.id.split("__")[1];
			var enabledItems = new Array();
			
			var nodes = group.getElementsByTagName("li");
			for (var j = 0; j < nodes.length; j++)
			{
				var node = nodes[j];
				var parts = node.id.split("__");			
				var itemCode = parts[2];
				var tempSelection = Tools_Clone(selection);
				tempSelection[groupCode] = itemCode;

				var count = items.filter(function(item) { return Tools_IsSubsetOf(tempSelection, item) }).length;
				if (node.className.indexOf("Selected") < 0)
					node.className = (count == 0) ? "Disabled" : "Enabled";
				if (node.className.indexOf("Disabled") < 0)
					enabledItems.push(node);
			}

			// Vérifie les éléments induits
			if (enabledItems.length == 1)
			{
				if (enabledItems[0].className.indexOf("Selected") < 0)
					toClick.push(enabledItems[0]);
			}
			else if (enabledItems.length > 1)
			{
				// Les éléments qui étaient induits ne le sont
				// plus car il y a plusieurs choix possibles
				for (var j = 0; j < enabledItems.length; j++)
					if (enabledItems[j].className.indexOf("Induced") != -1)
					{
						toClick.push(enabledItems[j]);
						break ;
					}
			}
									
			SetAvailableCount(groupCode, enabledItems.length);
		}
		
		// Ensuite on s'occupe des choix induits
		for (var i = 0; i < toClick.length; i++)
			this.OnClick(toClick[i], true);
	}

	// Récupère la valeur de l'élement sélectionné pour chaque groupe
	this.GetSelection = function()
	{
		var selection = new Object();
				
		if (this.HtmlObject != null)
			Tools_ForEach(this.HtmlObject.getElementsByTagName("dl"), function(group)
			{
				var choices = group.getElementsByTagName("li");
				for (var j = 0; j < choices.length; j++)
				{
					var choice = choices[j];
					if (choice.className.indexOf("Selected") != -1)
					{
						var parts = choice.id.split("__");
						var groupCode = parts[1];
						var itemCode = parts[2];
						selection[groupCode] = itemCode;
						break ;
					}
				}
			});

		return selection;
	}

	// Supprime toutes les sélections en cours
	this.ResetAllSelection = function()
	{
		if (this.HtmlObject != null)
			Tools_ForEach(this.HtmlObject.getElementsByTagName("dl"), this.ResetGroupSelection);
		this.HistoryClear();
	}
		
	this.GetSelectedItem = function(groupCode)
	{
		var group = this.GetGroupByCode(groupCode);
		if (group != null)
		{
			var choices = group.getElementsByTagName("li");
			for (var j = 0; j < choices.length; j++)
			{
				var choice = choices[j];
				if (choice.className.indexOf("Selected") != -1)
					return choice;
			}
		}
		return null;
	}
	
	// Insère un message après le libellé du groupe de choix
	this.SetGroupMessage = function(groupCode, message)
	{
		var groupMessage = document.getElementById("{0}Group__{1}__Message".format(id, groupCode));
		if (groupMessage != null)
		{
			groupMessage.innerHTML = message;
			groupMessage.style.display = Tools_IsNullOrEmpty(message) ? "none" : "block";
		}
	}
}

