/**
 * app.js
 *
 * Javascript Application
 *
 * Fonctions:
 *   - WaitAffiche
 *   - EncodeSpecialChars
 *   - IsSameUrl
 *   - GetPanelRechObject
 *   - CheckButton
 *
 * Classes:
 *   - Palette
 *   - Histo
 *   - EcranRechCrit
 *   - SaisieChamp: stockage de critère de recherche
 */

//------------------------------------------------------------------------------
// Déclarations
//------------------------------------------------------------------------------

// Configuration de l'application
var gConfig = {
	mTest: 20, // Number of items
	mDepth: 5 // Depth of cascade animation
};

// Configuration de l'application: table des messages
gConfig.messages = {
	mInvalidCorpus: "Corpus non valide",
	mWindowRechWait: "Recherche en cours. Veuillez patienter...",
	mWindowIndexWait: "Calcul d'index en cours. Veuillez patienter...",
	mWindowConsultWait: "Calcul de liste en cours. Veuillez patienter...",
	mWindowRechToActivate: "Veuillez activer la fenêtre de recherche",
	mWindowRechNoCritRech: "Aucun critère de recherche ne correspond à cet index",
	mWindowRechNoFullText: "Recherche texte intégral désactivée",
	mWindowRechMaxCritRech: "Nombre de critères de recherche maximal atteint",
	mWindowRechOpeRechOr: "\"ou\"",
	mSajaxNoNotes: "Votre navigateur ne permet pas la sauvegarde des notes personnelles",
	mSajaxNoSignets: "Votre navigateur ne permet pas la sauvegarde des signets",
	mDomNoMenu: "Votre navigateur ne permet pas l'affichage des menus"
};

// Configuration de la recherche: nombre de lignes de critères de recherche max
gConfig.mWindowRech = {
	mNbRowMax: 10
};

// Entier max
var ULONG_MAX = Number.MAX_VALUE;

// enum: Types de palettes
var eTypPaletteNormal = 1;
var eTypPaletteClose = 2;
var eTypPaletteNoBar = 3;

// enum: Types d'actions possibles dans les historiques
var eHistoFirst = 1;
var eHistoSuiv = 2;
var eHistoPrec = 3;
var eHistoLast = 4;


//------------------------------------------------------------------------------
// Fonctions
//------------------------------------------------------------------------------

/**
 * Affichage de message dans le bandeau supérieur
 *
 * @param string Texte à afficher, ou vide ou rien pour effacer
 *
 * @return void
 */
function WaitAffiche(inStr /* = "" */)
{
	if (top.window_top == null) {
		return ;
	}
	
	var node = top.window_top.gWaitAffiche;
	if (node == null) {
		return ;
	}

	if (inStr == undefined || inStr == "") {
		node.style.display = "none";
		return ;
	}

	node.firstChild.nodeValue = inStr;
	node.style.display = "block";
};

/**
 * Remplace certains caractères avant encodage encodeURIComponent (PhpUtil)
 *
 * @param string Chaine à traiter
 *
 * @return string Chaine traitée
 */
function EncodeSpecialChars(inStr)
{
	var str = inStr;

	if (str.indexOf("†") != -1) {
		str = str.replace(/†/g, "&dagger;");
	}
	if (str.indexOf("Œ") != -1) {
		str = str.replace(/Œ/g, "&OElig;");
	}
	if (str.indexOf("œ") != -1) {
		str = str.replace(/œ/g, "&oelig;");
	}

	return str;
};

/**
 * Compare deux url, sans tenir compte de l'ordre des paramètres,
 * ni de la présence du host
 *
 * @param string url
 * @param string url
 *
 * @return bool true si url identiques, sinon false
 */
function IsSameUrl(inUrl1, inUrl2)
{
	// Ca simple
	if (inUrl1 == inUrl2) {
		return true;
	}

	// Supprime le path
	if (inUrl1.substring(0, 7) == "http://") {
		var i = inUrl1.indexOf("/", 7);
		if (i != -1) {
			inUrl1 = inUrl1.substr(i);
		}
	}
	if (inUrl2.substring(0, 7) == "http://") {
		var i = inUrl2.indexOf("/", 7);
		if (i != -1) {
			inUrl2 = inUrl2.substr(i);
		}
	}

	if (inUrl1.length != inUrl2.length) {
		return false;
	}

	// Compare le path
	var i1 = inUrl1.indexOf("?");
	var i2 = inUrl2.indexOf("?");
	if (i1 == -1 || i2 == -1) {
		return false;
	}
	if (inUrl1.substring(0, i1) != inUrl2.substring(0, i2)) {
		return false;
	}

	// Compare les querry après avoir trié les paramètres
	var q1 = inUrl1.substr(i1 + 1);
	var q2 = inUrl2.substr(i2 + 1);
	var tab1 = q1.split("&");
	var tab2 = q2.split("&");
	tab1.sort();
	tab2.sort();
	q1 = tab1.join("&");
	q2 = tab2.join("&");
	return q1 == q2;
};

/**
 * Récupération de l'objet panel de recherche de l'application
 *
 * @return objet PanelRech de la fenêtre de recherche active (simple ou avancée)
 *   ou messagebox utilisateur et retourne null si pas de fenêtre de recherche active
 */
function GetPanelRechObject()
{
	var windowRech = null;

	// Récupère la fenêtre de recherche affichée (simple ou avancée)
	windowRech = top.frame_right.window_right.panelrechavance;
	if (windowRech == null) {
		windowRech = top.frame_right.window_right;
	}
	if (windowRech == null || windowRech.gPanelRech == null) {
		// Aucune fenêtre de recherche active
		alert(gConfig.messages.mWindowRechToActivate);
		return null;
	}

	return windowRech.gPanelRech;
};


/**
 * Affichage d'un état (coché / décoché) pour un bouton de l'application
 *
 * @param string Nom du bouton
 * @param bool true si bouton coché sinon false
 * @param string hint facultatif
 *
 * @throws exception si erreur
 *
 * @return void
 */
function CheckButton(inButtonName, inCheck, inHint /* = null */)
{
	// Initialise le nom de base d'image à charger
	var imgFileName = inButtonName;
	if (inCheck) {
		imgFileName += "_check";
	}

	// Récupère les noeuds ancre et image
	var imgNode = FindNode(inButtonName);
	var anchorNode = FindNode("id_check_anchor_" + inButtonName);

	imgNode.setAttribute("src", "./img/button/" + imgFileName + ".gif");
	anchorNode.onmouseover = new Function("document." + inButtonName
		+ ".src='./img/button/" + imgFileName + "_over.gif';");
	anchorNode.onmouseout  = new Function("document." + inButtonName
		+ ".src='./img/button/" + imgFileName + ".gif';");

	// Hint facultatif
	if (inHint != undefined) {
		imgNode.alt = inHint;
		imgNode.title = inHint;
	}
};


/**
 * Désérialisation d'une chaine contenant un tableau de critères de recherche
 *
 * @param string Chaine à désérialiser
 *
 * @return Array Résultat = Tableau d'objets SaisieChamp
 */
function CritRechFromString(inStr)
{
	// Vire les retours à la ligne Windows éventuels
	inStr = inStr.replace(/\r/g, "");

	res = new Array();
	var tab = inStr.split("\n");

	var count = tab.length;

	// Supprime la dernière ligne si vide
	if (count && tab[count - 1] == "") {
		tab.pop();
		count--;
	}

	var str = "";
	var sc = null;
	for (var i = 0; i < count; i++) {
		str = tab[i];
		// Anomalie si lignes vides
		if (str == "") {
			var error = "Désérialisation \"CritRech\" incorrecte: \"" + inStr + "\"";
			throw new Error(error);
		}
		sc = new SaisieChamp(); // Création d'un nouveau sinon références
		sc.SaisieChampFromString(str);
		res.push(sc);
	}

	return res;
};

/**
 * Sérialisation d'un tableau de critères de recherche
 *
 * @param Array Tableau d'objets SaisieChamp à sérialiser
 *
 * @return string Chaine résultat à retourner
 */
function CritRechToString(inCt)
{
	var res = "";
	var str = "";
	var sc = null;
	var count = inCt.length;
	for (var i = 0; i < count; i++) {
		sc = inCt[i];
		if (i) {
			res += "\n";
		}
		str = sc.SaisieChampToString();
		res += str;
	}

	return res;
};


//------------------------------------------------------------------------------
// Classe Palette
//------------------------------------------------------------------------------

/**
 * Classe palette générique
 *
 * Constructeur
 *
 * @param string Id du noeud parent de la palette
 * @param bool Affichage du contenu
 * @param int Type de palette
 *   - eTypPaletteNormal
 *   - eTypPaletteClose
 *   - eTypPaletteNoBar
 */
function Palette(inNodeId, inShowContent /*= true*/, inTypPalette /*= eTypPaletteNormal*/)
{
	this.mId = inNodeId;
	this.mPaletteNode = FindNode(inNodeId);

	// Mémorise le type de palette (minimisable, etc...)
	if (inTypPalette == null) {
		inTypPalette = eTypPaletteNormal;
	}
	this.mTypPalette = inTypPalette;

	// Barre de commande de la palette
	this.mPaletteBar = null;
	if (this.mTypPalette != eTypPaletteNoBar) {
		this.mPaletteBar = FindChildNode(this.mPaletteNode, "palette_bar");
	}

	// Panel de contenu de la palette
	this.mPaletteContent = FindChildNode(this.mPaletteNode, "palette_content");

	if (this.mTypPalette == eTypPaletteNormal) {
		this.mPaletteBarButton = FindChildNode(this.mPaletteNode, "palette_bar_button_anchor");
		this.mPaletteBarButtonImg = FindChildNode(this.mPaletteNode, "palette_bar_button_img");
		// Reporte l'évènement href du titre, dans l'évènement href du bouton
		var paletteBarTitle = FindChildNode(this.mPaletteNode, "palette_bar_title");
		var paletteBarTitleHref = paletteBarTitle.getAttribute("href");
		this.mPaletteBarButton.setAttribute("href", paletteBarTitleHref);
	}
	else if (this.mTypPalette == eTypPaletteClose) {
		// Rien à faire
	}
	else if (this.mTypPalette == eTypPaletteNoBar) {
		// Rien à faire
	}

	// Fonction de callback à appeler à chaque modif ShowHide
	this.mShowHideCallback = null;

	// Affiche ou masque le contenu de la palette
	if (inShowContent == null) {
		inShowContent = false;
	}
	this.mShowContent = !inShowContent;
	this.ShowContent(inShowContent, false);
};

/**
 * Indique la fonction de callback à appeler à chaque modif ShowHide
 *
 * @param function Fonction à appeler
 *
 * @return int Hauteur
 */
Palette.prototype.SetShowHideCallbackFunc = function(inCallbackFunc)
{
	this.mShowHideCallback = inCallbackFunc;
};

/**
 * Affiche ou masque le contenu de la palette
 *
 * @param bool Contenu à afficher ?
 * @param bool Mise à jour de la hauteur du panel d'affichage des palettes
 *
 * @return void
 */
Palette.prototype.ShowContent = function(inShowContent, inUpdateHeight /* = true */)
{
	// Quitte si rien à faire
	if (this.mShowContent == inShowContent) {
		return;
	}

	if (this.mTypPalette == eTypPaletteNormal) {
		// Change les images du bouton de la barre de titre
		var imgFileName = this.mShowContent ? "palette_right" : "palette_down";
		var imgHtmlName = this.mPaletteBarButtonImg.getAttribute("name");
		this.mPaletteBarButton.onmouseover = new Function("document." + imgHtmlName + ".src='./img/button/" + imgFileName + "_over.gif';");
		this.mPaletteBarButton.onmouseout  = new Function("document." + imgHtmlName + ".src='./img/button/" + imgFileName + ".gif';");
		this.mPaletteBarButtonImg.setAttribute("src", "./img/button/" + imgFileName + ".gif");
		// Affiche ou masque la palette
		this.mPaletteContent.style.display = inShowContent ? "block" : "none";
	}
	else if (this.mTypPalette == eTypPaletteClose) {
		// Affiche ou masque la palette
		this.mPaletteBar.style.display = inShowContent ? "block" : "none";
		this.mPaletteContent.style.display = inShowContent ? "block" : "none";
	}
	else if (this.mTypPalette == eTypPaletteNoBar) {
		// Rien à faire
	}

	// Mise à jour de la hauteur du panel d'affichage des palettes
	// sauf à la création de la palette
	if (inUpdateHeight != false) {
		if (typeof(gPanelPaletteRech) == "object") {
			gPanelPaletteRech.UpdatePanelPaletteHeight();
		}
		else {
			UpdatePanelPaletteHeight();
		}
		// Appelle la fonction de callback si définie
		if (this.mShowHideCallback != null) {
			this.mShowHideCallback(this.mId, inShowContent);
		}
	}

	// Mémorise la position affichée ou masquée
	this.mShowContent = inShowContent;
};

/**
 * Bascule l'affichage affiché / masqué de l'ensemble de la palette
 *
 * @return void
 */
Palette.prototype.ShowAll = function(inShow)
{
	this.mPaletteNode.style.display = inShow ? "block" : "none";
};

/**
 * Retourne la hauteur totale de la palette,
 * en tenant compte de son état affiché ou masqué
 *
 * @return int Hauteur
 */
Palette.prototype.GetPaletteHeight = function()
{
	return this.mPaletteNode.offsetHeight;
};

/**
 * Bascule l'affichage affiché / masqué du contenu de la palette
 *
 * @return void
 */
Palette.prototype.ShowHide = function()
{
	this.ShowContent(!this.mShowContent);
};


//------------------------------------------------------------------------------
// Classe Histo
//------------------------------------------------------------------------------

/**
 * Classe historique générique
 *
 * Constructeur
 *
 * @param bool Suppression de tout ce qui est postérieur à la position
 * courante avant d'effectuer un ajout ?
 *
 * @return void
 */
function Histo(inDelAppend)
{
	this.mDelAppend = inDelAppend;

	this.mList = new Array();
	this.mLast = ULONG_MAX;
	this.mWantCurrent = false;
	this.mDelAppend = false;
};

/**
 * Raz de l'historique
 *
 * @return void
 */
Histo.prototype.Clear = function()
{
	this.mWantCurrent = false;
	this.mList = new Array();
	this.mLast = ULONG_MAX;
};

/**
 * Positionne pour que le prochain lu soit l'item courant
 *
 * @return void
 */
Histo.prototype.WantCurrent = function()
{
	this.mWantCurrent = true;
};

/**
 * Ajout d'un item dans l'historique
 *
 * @param string Item à ajouter
 *
 * @return void
 */
Histo.prototype.Append = function(inStr)
{
	this.mWantCurrent = false;

	var current = this.GetCurrent();
	if (current == inStr) {
		return;
	}

	// Suppression de tout ce qui est postérieur à la position courante
	// avant d'effectuer un ajout ?
	if (this.mDelAppend) {
		var count = this.mList.length;
		for (var i = count - 1; i != -1 && i > this.mLast; i--) {
			this.mList.pop();
		}
	}

	var count = this.mList.length;
	if (count == 0 || !(inStr == this.mList[count - 1])) {
		this.mList.push(inStr);
	}

	// Suppression du premier élément si taille max histo atteinte
	if (count == 100) {
		this.mList.shift();
	}

	this.mLast = this.mList.length - 1;
};

/*
 * Récupération de l'item courant
 *
 * @return l'item à récupérer ou null rien trouvé
 */
Histo.prototype.GetCurrent = function()
{
	var count = this.mList.length;
	if (count > 0 && this.mLast < count) {
		;	// Rien faire, c'est bon
	}
	else {
		// Beep
		return null;
	}

	return this.mList[this.mLast];
};

/*
 * Récupération du premier item
 *
 * @return l'item à récupérer ou null rien trouvé
 */
Histo.prototype.GetFirst = function()
{
	this.mWantCurrent = false;

	if (this.mList.length) {
		this.mLast = 0;
	}
	else {
		// Beep
		return null;
	}

	return this.mList[this.mLast];
};

/*
 * Récupération de l'item précédent
 *
 * @return l'item à récupérer ou null rien trouvé
 */
Histo.prototype.GetPrev = function()
{
	if (this.mWantCurrent) {
		this.mWantCurrent = false;
		return this.GetCurrent();
	}

	var count = this.mList.length;
	if (count && this.mLast > 0 && this.mLast < count) {
		this.mLast--;
	}
	else {
		// Beep
		return null;
	}

	return this.mList[this.mLast];
};

/*
 * Récupération de l'item suivant
 *
 * @return l'item à récupérer ou null rien trouvé
 */
Histo.prototype.GetNext = function()
{
	if (this.mWantCurrent) {
		this.mWantCurrent = false;
		return this.GetCurrent();
	}

	var count = this.mList.length;
	if (count && this.mLast < count - 1) {
		this.mLast++;
	}
	else {
		// Beep
		return null;
	}

	return this.mList[this.mLast];
};


//------------------------------------------------------------------------------
// Classe EcranRechCrit
//------------------------------------------------------------------------------

/**
 * Classe informations sur un critère de recherche
 *
 * Constructeur
 *
 * @param string Type de critère
 * @param string Types de tri
 * @param int Identifiant de critère
 * @param string Libellé du critère
 */
function EcranRechCrit(inTypCrit, inSortList, inIdCrit, inTexte)
{
	this.mTypCrit = inTypCrit;
	this.mSortList = inSortList;
	this.mIdCrit = inIdCrit;
	this.mTexte = inTexte;
};


//------------------------------------------------------------------------------
// Classe SaisieChamp
//------------------------------------------------------------------------------

/**
 * Classe de stockage de critère de recherche
 * Exemple: saisie utilisateur dans un champ de recherche
 *
 * Constructeur
 *
 * @param int Id du champ de recherche
 * @param string Saisie utilisateur
 */
function SaisieChamp(inNumChamp, inTexte)
{
	this.mNumChamp = inNumChamp;
	this.mMsSaisie = inTexte;
};

/**
 * Sérialisation
 *
 * @return string Chaine résultat de conversion
 */
SaisieChamp.prototype.SaisieChampToString = function()
{
	var str = this.mNumChamp + "\t" + this.mMsSaisie;

	return str;
};

/**
 * Désérialisation
 *
 * @param string Chaine sérialisée contenant un champ de saisie
 *
 * @throws Exception Si erreur
 *
 * @return void
 */
SaisieChamp.prototype.SaisieChampFromString = function(inStr)
{
	var tab = inStr.split("\t");
	if (tab.length != 2) {
		var error = "Désérialisation \"SaisieChamp\" incorrecte: \"" + inStr + "\"";
		throw new Error(error);
	}
	this.mNumChamp = Number(tab[0]);
	this.mMsSaisie = tab[1];
};
