/**
 * Ecran/fenêtre d'affichage du temps de course du joueur,
 * et des meilleurs temps pour le circuit.
 *
 * S'affiche une fois la course finie, en superposition par rapport à la visu du jeu.
 *
 * Elle est découpée en deux phases, avec une boucle principale pour chaque phase :
 *  - affichage des meilleurs temps : boucle displayLoop()
 *  - choix de la suite : boucle choiceLoop()
 */
 
 
 /**
  * Création de la fenêtre d'affichage des meilleurs temps.
  *
  *  - application : l'application (RacingGameApp) à qui rendre la main à l'issue
  *  - container : l'objet DIV qui contiendra les éléments DHTML créés
  *  - fader : le fader sur le container (état In, i.e. affichage normal, en entrée)
  *  - imageFx : l'outil pour effectuer les opérations graphiques (texte détouré)
  */
function TimingWindow(application, container, fader, imageFx)
{
	this.application = application;
	this.container = container;
	this.fader = fader;
	this.imageFx = imageFx;
	
	this.width = 430;
	this.height = 190;
	this.transitionLineCount = 19; // nombre de lignes de persiennes
	this.transitionLineHeight = this.height / this.transitionLineCount;
	this.bestTimesCount = 6;
	
	this.respawnLayerCount = 10;
	

	this.bestTimesShown = false;
}

TimingWindow.prototype =  {


	/**
	 * Initialization / remise à zéro des paramètres 
	 *  - road : le circuit juste joué
	 *  - raceTime : le temps de course à afficher (en 1/100e de seconde)
	 */
	initialize : function(road, raceResult) {
		this.userAcknowledged = true;
		this.timer = 0;
		this.raceResult = raceResult;
		this.raceTime = raceResult.raceTime;
		this.menuActive = false;
		
		this.road = road;
		this.bestTimesShown = false;
	},
	
	/**
	 * Renvoie l'option sélectionnée dans le menu, ou zéro s'il n'a pas été créé
	 */
	getSelectedOption : function() {
		if (this.menuDriver) {
			return this.menuDriver.getSelectedOption();
		}
		return 0;
	},
	
	/**
	 * Régle l'affichage des options du menu final en fonction de si le joueur
	 * a déverrouillé le circuit suivant ou non.
	 *  - qualified : true si le joueur vient de qualifier (conditionne l'affichage d'un message et l'option "course suivante")
	 * 
	 * A n'appeler qu'après un launchScreen() (le menuDriver doit avoir été initialisé)
	 */
	setQualified : function(qualified) {
		this.menuDriver.setOptionAvailable(0, qualified);
		this.menuDriver.setSelectedOption(qualified ? 0 : 1);
	},
 
	/**
	 * Création et initialisation des éléments d'affichage (DHTML)
	 * Branchement des gestionnaires d'événements clavier
	 *
	 * Prérequis : fader In en entrée (visu normale), on n'y touche pas
	 *
	 * - road : le circuit juste joué
	 * - raceResult : la description du résultat de course
	 */
	launchScreen : function(road, raceResult) {
	
		// panel parent des div "persiennes", pendant l'affichage de celles-ci 
		this.transitionMaster = document.createElement('div');
		this.transitionMaster.setAttribute("class", "timing_master");
		this.container.appendChild(this.transitionMaster);

		// panel parent de l'affichage des temps, une fois les persiennes rassemblées
		// en deux niveaux : le premier a une opacité à 1 et contient les images des voitures,
		// le second a une opacité à 0.5 et contient tout le reste. On procède ainsi car le rendu des
		// voitures avec 50% de transparence n'était pas esthétique
		this.opaqueMaster = document.createElement('div');
		this.opaqueMaster.setAttribute("class", "timing_master");
		this.container.appendChild(this.opaqueMaster);
		this.permanentMaster = document.createElement('div');
		this.permanentMaster.setAttribute("class", "timing_display");
		this.opaqueMaster.appendChild(this.permanentMaster);

		this.lapRecordMessage = this.imageFx.createOutlinedTextPanel(this.permanentMaster, "timing_new_record", "loader_text", "white");
		this.illustrationLegend = this.imageFx.createOutlinedTextPanel(this.permanentMaster, "timing_legend", "loader_text", "white");
		
			
		this.illustration = document.createElement('img');
		this.illustration.setAttribute("class", "timing_illustration");
		this.opaqueMaster.appendChild(this.illustration);
		
		// animation en attente de la réponse Ajax
		this.hourglassAnimation = document.createElement('div');
		this.hourglassAnimation.setAttribute("class", "timing_hourglass");
		this.hourglassAnimation.appendChild(document.createTextNode(''));
		this.permanentMaster.appendChild(this.hourglassAnimation);

		// lignes de persiennes
		this.transitionLines = new Array(this.transitionLineCount);
		for (var line = 0 ; line < this.transitionLineCount ; ++line)
		{
			var currentLine = document.createElement('div');
			currentLine.setAttribute("class", "timing_transition_line");
			currentLine.style.top=(line*this.transitionLineHeight)+"px";
			this.transitionMaster.appendChild(currentLine);
			this.transitionLines[line] = currentLine;
		}
		
		// panel parent des "lames de verre", pendant l'affichage de celles-ci
		this.respawnMaster = document.createElement('div');
		this.respawnMaster.setAttribute("class", "timing_master");
		this.container.appendChild(this.respawnMaster);

		// "lames de verre"
		this.respawnLayers = new Array(this.respawnLayerCount);
		for (var index = 0 ; index < this.respawnLayerCount ; ++index)
		{
			var currentLayer = document.createElement('div');
			currentLayer.setAttribute("class", "timing_respawn_block");
			this.respawnMaster.appendChild(currentLayer);
			this.respawnLayers[index] = currentLayer;
		}
		
		this.menuDriver = new MenuDriver(this.respawnMaster, this.fader, this.imageFx, 3);
		this.menuDriver.initialize();
		this.menuDriver.setOptionText(0, _("TW001"));
		this.menuDriver.setOptionText(1, _("TW002"));
		this.menuDriver.setOptionText(2, _("TW003"));
		this.menuDriver.getControl(0).style.top = "25px";
		this.menuDriver.getControl(1).style.top = "85px";
		this.menuDriver.getControl(2).style.top = "145px";
		this.menuDriver.setEscapeOption(2);


		var control = this;
		document.onkeydown = function(event) { return control.onKeyDown(event); }
		document.onkeyup = function(event) { control.onKeyUp(event); }
		
		this.initialize(road, raceResult);
		
		this.displayLoop();
	},
 
	/**
	 * Boucle principale de gestion des événements et d'affichage, pour la première phase
	 * d'affichage des temps.
	 * Affiche progressivement (effet de persiennes) le fond semi-opaque
	 * puis met à jour avec les meilleurs temps.
	 */
	displayLoop : function() {
	
		var transitionCheckpoint = 2*this.transitionLineCount + this.transitionLineHeight;
		// transition initiale : on déploie le fond semi-opaque en persiennes
		if (this.timer < transitionCheckpoint)
		{
			for (var line = 0 ; line < this.transitionLineCount ; ++line)
			{
				
				var currentLineHeight = this.timer - 2*line;
				if (currentLineHeight>0 && currentLineHeight<= this.transitionLineHeight) {
					var currentLineStyle = this.transitionLines[line].style;
					currentLineStyle.height = currentLineHeight+"px";
					currentLineStyle.visibility = "visible";
				}
			}
		}
		else if (this.timer == transitionCheckpoint)
		{
			this.permanentMaster.style.opacity = 0.5;
			this.permanentMaster.style.backgroundColor = "black";
			this.transitionMaster.style.display = "none";

			var timeString = this.formatRaceTime(this.raceTime);
			this.permanentMaster.appendChild(document.createTextNode(timeString));
		}
		else if (!this.road.lapRecordUpdateCompleted) {
			// en attente de la réponse Ajax
			var line = "_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _";
			var car = "'o-o-";
			var message = line.substr(1, this.timer%30)+car+line;
			message = message.substr(1, 30);
			this.hourglassAnimation.firstChild.data = message;
		}
		else if (this.road.lapRecordUpdateCompleted && !this.bestTimesShown)
		{
			this.hourglassAnimation.firstChild.data = '';
			if (this.userAcknowledged) {
				this.illustration.style.visibility="hidden";
				if (this.raceResult.messages.length > 0) {
					// Tant qu'il y a des messages, on les affiche un par un, en attendant une réponse 
					// utilisateur à chaque fois
					this.imageFx.outlinedWrite(this.lapRecordMessage, this.raceResult.messages[0].text);
					if (this.raceResult.messages[0].image != "") {
						this.illustration.setAttribute("src", this.raceResult.messages[0].image);
						this.illustration.style.visibility="visible";
					} 
					this.imageFx.outlinedWrite(this.illustrationLegend, this.raceResult.messages[0].legend);
					this.raceResult.messages.shift();
					
					// on informe l'application si un circuit a été débloqué
					// todo : à refactorer dans un endroit plus approprié (pas l'affichage, mais c'est le
					// premier qui ait connaissance de l'application dans la chaîne de traitement
					// depuis le retour de la requête Ajax)
					if (this.raceResult.unlockedTrack>0) {
						this.application.trackUnlocked(this.raceResult.unlockedTrack);
					}
				} else {
					this.imageFx.outlinedWrite(this.lapRecordMessage, '');
					this.imageFx.outlinedWrite(this.illustrationLegend, '');
					this.displayBest();
					this.bestTimesShown = true;
				}
				this.userAcknowledged = false;
			}

		}
		
		++this.timer;
	
		
		if (this.userAcknowledged && this.bestTimesShown && this.timer > transitionCheckpoint) {
			this.setQualified(this.raceResult.unlockedTrack > 0);
			this.timer = 0;
			this.userAcknowledged = false;
			setTimeout("timingWindowChoiceLoop(lotus.timingWindow)", 40);
		} else {
			setTimeout("timingWindowDisplayLoop(lotus.timingWindow)", 40);
		}
	},
	
	/**
	 * Boucle principale de gestion des événements et d'affichage, pour le menu de choix
	 * utilisateur (suite, recommencer ..) en fin de course.
	 * Fait disparaître l'affichge précédent, le remplace par le nouveau
	 * et met à jour en fonction de la sélection utilisateur
	 */
	choiceLoop : function() {
		this.menuActive = true;
		var vanishingPoint = 25, respawnPoint=vanishingPoint+35, completionPoint=respawnPoint+10;
		if (this.timer < vanishingPoint)
		{	// 1re phase, on fait disparaître le panel avec un effet de zoom 
			// qui le rend de plus en plus transparent, jusqu'à disparaître complètement
			var margin = 25 - this.timer*this.timer;
			var opacity = 1-0.04*this.timer;
			this.opaqueMaster.style.opacity = opacity;
			this.opaqueMaster.style.left = margin+"px";
			this.opaqueMaster.style.top = margin+"px";
			this.opaqueMaster.style.right = margin+"px";
			this.opaqueMaster.style.bottom = margin+"px";
			if (margin<0) {
				var clip = (-margin)+"px";
				var clipBottom = (240-margin)+"px";
				var clipRight = (480-margin)+"px";
				this.opaqueMaster.style.clip = "rect("+clip+", "+clipRight+", "+clipBottom+", "+clip+")";
			}
		} else if (this.timer == vanishingPoint) {
			// fin de la 1re phase : on efface tous les éléments qui y ont servi
			this.opaqueMaster.style.display = "none";
		} else if (this.timer < respawnPoint) {
			// 2e phase : le panel revient sous la forme d'une série de "lames de verre" semi-transparents
			// qui viennent se mettre en place
			var motionEnd = 20;
			for (var index = 0 ; index < this.respawnLayerCount ; ++index) {
				var localTimer = this.timer - vanishingPoint - index;
				localTimer = (localTimer > motionEnd ? motionEnd : localTimer);
				if (localTimer > 0) {
					var c = Math.cos(1.57*localTimer/motionEnd);
					var x = Math.round(30*c*c);
					x = (x>25 ? 25 :x);
					var y = Math.round(90*c);
					var layerStyle = this.respawnLayers[index].style;
					layerStyle.visibility = "visible";
					layerStyle.left = -x+"px";
					layerStyle.right = x+"px";
					layerStyle.width = (2*x+430)+"px";
					layerStyle.top = y+"px";
					layerStyle.bottom = y+"px";
				}
			}
		} else if (this.timer <= completionPoint) {
			// 3e phase : le texte des options apparaît par dessus le panel
			var opacity = 0.1*(this.timer-respawnPoint);
			this.menuDriver.getControl(0).style.opacity = opacity;
			this.menuDriver.getControl(1).style.opacity = opacity;
			this.menuDriver.getControl(2).style.opacity = opacity;
		} 
		
		// à partir de la 3e phase, l'utilisateur peut choisir une option avec les flèches
		if (this.timer > respawnPoint) {
			this.menuDriver.repaint(this.timer - respawnPoint);
		}
		
		++this.timer;
	
		var keepOn = true;
		if (this.menuDriver.userAcknowledged) {
			document.onkeydown = null;
			document.onkeyup = null;
			if (!this.fader.fading) { // le rideau est à l'arrêt
				if (this.fader.faded) { // soit il est déja tiré
					this.terminateScreen();
					keepOn = false;
				} else { // soit on est au tout début et on doit le tirer
					this.fader.fadeOut();
				}
			}
		} 
		if (keepOn) {
			setTimeout("timingWindowChoiceLoop(lotus.timingWindow)", 40);
		}
	},

	
	/**
	 *	Ferme et détruit les éléments DHTML de l'écran
	 */
	terminateScreen : function() {
		document.onkeydown = null;
		document.onkeyup = null;
		for (var line = 0 ; line < this.transitionLineCount ; ++line)	{
			this.transitionMaster.removeChild(this.transitionLines[line]);
			this.transitionLines[line] = null;
		}
		for (var index = 0 ; index < this.respawnLayerCount ; ++index)	{
			this.respawnMaster.removeChild(this.respawnLayers[index]);
			this.respawnLayers[index] = null;
		}
		this.menuDriver.terminate();
		this.permanentMaster.removeChild(this.hourglassAnimation);
		this.permanentMaster.removeChild(this.illustrationLegend);
		this.permanentMaster.removeChild(this.lapRecordMessage);
		this.opaqueMaster.removeChild(this.permanentMaster);
		this.opaqueMaster.removeChild(this.illustration);
		this.container.removeChild(this.opaqueMaster);
		this.container.removeChild(this.respawnMaster);
		this.container.removeChild(this.transitionMaster);
		this.application.afterTimingWindow();
	},

	/**
	 * Affiche les meilleurs temps de la course
	 */
	displayBest : function() {
		for (var index=0; index<=this.road.lapRecordCount; ++index) {
			var playerLine = (index == this.road.lapRecordCount);

			var lineTop = (13+index*14+(playerLine?2:0))+"%";

			// numéro dans le classement : 1 à 5
			if (!playerLine) {
				var rankPanel = document.createElement('div');
				rankPanel.appendChild(document.createTextNode(1+index));
				rankPanel.style.left = "5%";
				rankPanel.style.top = lineTop;
				this.permanentMaster.appendChild(rankPanel);
			}
			
			// nom du joueur
			var nameContents = (playerLine ? this.road.playerLapRecord : this.road.lapRecords[index]).name;
/*			if (nameContents.length > 20) {
				nameContents = nameContents.substr(, 0, 17)+"...";
			} else */ if (nameContents == "") {
				nameContents = "--------------------";
			}
			var namePanel = document.createElement('div');
			namePanel.appendChild(document.createTextNode(nameContents));
			namePanel.style.left = "10%";
			namePanel.style.top = lineTop;
			this.permanentMaster.appendChild(namePanel);
			
			// voiture avec laquelle a été fait le record
			var raceTime = (playerLine ? this.road.playerLapRecord : this.road.lapRecords[index]).time;
			if (raceTime > 0) {
				var carIndex = (playerLine ? this.road.playerLapRecord : this.road.lapRecords[index]).car;
				var carImage = document.createElement('div');
				carImage.style.backgroundImage = "url(cars/allCars24.png)";
				carImage.style.backgroundPosition = "0px "+(-24*(carIndex-1))+"px";
				carImage.style.left = "60%";
				carImage.style.top = lineTop;
				carImage.style.width = "60px";
				carImage.style.height = "24px";
				this.opaqueMaster.appendChild(carImage);
			}
			
			// temps mis
			
			var timeString = this.formatRaceTime(raceTime);
			var timePanel = document.createElement('div');
			timePanel.appendChild(document.createTextNode(timeString));
			timePanel.style.left = "80%";
			timePanel.style.top = lineTop;
			this.permanentMaster.appendChild(timePanel);
		}

	},
	
	/**
	 * Formate un temps en 1/100e de seconde vers une chaîne "mm:ss:cc",
	 * en rajoutant si nécessaire un zéro devant les secondes ou 1/100e de seconde
	 * pour garder un format à deux chiffres.
	 */
	formatRaceTime : function(inTime) {
		inTime = parseInt(inTime);
		var timeString = "--:--:--";
		if (inTime > 0) {
			var cs = inTime%100;
			var seconds = ((inTime-cs)/100)%60;
			var minutes = Math.floor(inTime/6000);
			timeString = (minutes<10?"0":"")+minutes+":"+(seconds<10?"0":"")+seconds+":"+(cs<10?"0":"")+cs;
		}
		return timeString;
	},
	
	
	/**
	 * Handler des événements onkeydown (touche clavier pressée)
	 * Choix du mode s'il s'agit d'une flèche verticale
	 * Renvoie true si l'événement doit continuer à être propagé (i.e. non traité ici), false sinon
	 */
	onKeyDown : function(event) {
		if (this.menuActive) {
			return this.menuDriver.onKeyDown(event);
		} 
		var handled = this.keyControl(event, true);
		return !handled;
	},
	
	/**
	 * Handler des événements onkeyup (touche clavier relâchée)
	 */
	onKeyUp : function(event) {
		if (this.menuActive) {
			this.menuDriver.onKeyUp(event);
		} else {
			this.keyControl(event, false);
		}
	},
	
	/**
	 * Handler délégué pour les événements clavier.
	 * Enregistre les appuis et les relâchements des flèches
	 * et de la touche Escape.
	 * Renvoie true si l'événement est géré, false sinon
	 */
	keyControl : function(event, value)
	{
		var handled = true;
		var key = 0;
		if (window.event) { // IE
			key = window.event.keyCode;
		} else { // FF, Opera,...
			key = event.which;
		}

		switch (key) {
			case 27 : // escape
				this.userAcknowledged |= value;
				break;
			case 13 : // entrée
			case 32 : // espace
				this.userAcknowledged |= value;
				break;
			default :
				handled = false;
		}
		return handled;
	}	
	
}

function timingWindowDisplayLoop(screen)
{
	screen.displayLoop();
}

function timingWindowChoiceLoop(screen)
{
	screen.choiceLoop();
}