// Connection library: connect.js   beta version 0.5.1  11 january 2000
//
// Copyright (C) 2000 Jean-Hugues Rety and Robert Kendall
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published 
// by the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public 
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
//
// THIS IS NOT AN OFFICIAL RELEASE BUT A CURRENT WORKING VERSION !!
//
// This library provides JavaScript tools for 
// hypertext literary writing in HTML.
//
// Important: this version is a prototype, functionalities may me 
// modified in future versions.
//
// Documentation: http://www.wordcircuits.com/connect
// e-mail: connection@wordcircuits.com
//

//
// Parameters.
//

// The variable _histCookieName_ below specifies the names of the 
// cookies (a long history may be splitted into several cookies) 
// that will contain the historical list of visited nodes.
// In this version, the history can occupy up to 9 cookies.
// Each history recorded in a cookie can be 1950 char lond.
// For an average of 8 char long filenames, this gives a litle bit
// more that 200 nodes per cookie, i.e. a maximum of 
// approximately 1800 or 2000 nodes in the history.


if (typeof(histCookieName) == "undefined")
 var histCookieName = "Hist";

// color of links (anchors):
// (if null, then uses the color defined by the navigator or the A.link style)
if (typeof(linkColorDefault) == "undefined")
 var linkColorDefault = null;

// What follows is used to bracket links:
if (typeof(linkPrefixDefault) == "undefined")
 var linkPrefixDefault = "";
if (typeof(linkSuffixDefault) == "undefined")
 var linkSuffixDefault = "";

//color of text written with h_Write: 
if (typeof(hotTextColorDefault) == "undefined")
  var hotTextColorDefault = document.linkColor;
// var hotTextColorDefault = document.fgColor;
//var hotTextColorDefault = document.vlinkColor;

// popup's management

if (typeof(popupTextDefault) == "undefined")
  var popupTextDefault = null;

// shall titles be poped up?
if (typeof(titlePopup) == "undefined")
  var titlePopup = false;

// default color of popup Text
if (typeof(popupTextColorDefault) == "undefined")
  var popupTextColorDefault = document.fgColor;

// default attributes for pop-up text
if (typeof(popupAttDefault) == "undefined")
  var popupAttDefault = "bgcolor='#FFFFCC'";

// status bar management

// default attributes for status Text
if (typeof(statusTextDefault) == "undefined")
  var statusTextDefault = null;

// shall titles be displayed in the status bar?
if (typeof(titleStatus) == "undefined")
  var titleStatus = false;

// Dead links management:

// color of dead links:

if (typeof(deadLinkColor) == "undefined")
  var deadLinkColor = document.fgColor;
//var deadLinkColor = document.linkColor;
//var deadLinkColor = document.vlinkColor;

if (typeof(deadLinkPopup) == "undefined")
  var deadLinkPopup = false;

if (typeof(deadLinkStatus) == "undefined")
  var deadLinkStatus = false;

// appearance of dead links.
// (only used when no popup or status text is to be displayed with the dead link)!!
if (typeof(deadLinkStyle) == "undefined")
 var deadLinkStyle = "text"; // appears as normal text
//var deadLinkStyle = "link"; // appears as links


//
// Initializations
//

// window where outputs are to be displayed:
var outputWin = window;

// the history !!
var h_nbCookies = Number(h_cookieVal (histCookieName + 'Nb'));
if (h_nbCookies <1 || h_nbCookies >9)
  h_nbCookies = 1;

var h_hist = histValue ();

h_currHist = h_cookieVal (histCookieName + h_nbCookies.toString() );

var h_histEmpty = (h_hist == "" || h_hist == "*");

// If noHistUpdate=true the history is not updated with the current node.
if (typeof(noHistUpdate) == "undefined")
  addHist();
else
  if (noHistUpdate != true)
    addHist();

// Variables below are below permit to modify parameters "on the fly".
var popupText = popupTextDefault;

var statusText = statusTextDefault;

var popupAtt = popupAttDefault;

var popupTextColor = popupTextColorDefault;

var linkClass = "link";

var linkColor = linkColorDefault;

var linkPrefix = linkPrefixDefault;

var linkSuffix = linkSuffixDefault;

var hotTextColor = hotTextColorDefault;

// The array titleArray may contain "titles" to be displayed with nodes.
if (typeof(titleArray) == "undefined")
    titleArray = new Array ();

if (typeof(popupAttArray) == "undefined")
    popupAttArray = new Array ();


// Internal variable
var h_popnb = 1;


// Internal cookie manipulation functions.


function h_cookieVal (cname) 
// Returns the value of the cookie _cname_.
{

  function h_cookieVal2 (offset) 
  {
  var end = document.cookie.indexOf (";", offset);
  if (end == -1)
    end = document.cookie.length;
  return unescape(document.cookie.substring(offset, end));
  }

var prefix = cname + "=";
var prefix_len = prefix.length;
var cookie_len = document.cookie.length;
var i = 0;
while (i < cookie_len ) 
  {
  var j = i + prefix_len;
  if (document.cookie.substring(i, j) == prefix)
    return h_cookieVal2 (j);
  i = document.cookie.indexOf(" ", i) + 1;
  if (i == 0) break; 
  }
return "";
}

function h_setCookie (cname, value) 
// Sets the cookie _cname_ with value _value_.
{
var arg = h_setCookie.arguments;
var arg_len = h_setCookie.arguments.length;
var expires = (arg_len > 2) ? arg[2] : null;
var path = (arg_len > 3) ? arg[3] : null;
var domain = (arg_len > 4) ? arg[4] : null;
var secure = (arg_len > 5) ? arg[5] : false;
document.cookie = cname + "=" + escape (value) +
   ((expires == null) ? "" : ("; expires=" + expires.toGMTString())) +
   ((path == null) ? "" : ("; path=" + path)) +
   ((domain == null) ? "" : ("; domain=" + domain)) +
   ((secure == true) ? "; secure" : "");
}

function h_deleteCookie (cname) 
// Deletes the cookie _cname_.
{
var expires = new Date ();
expires.setTime (expires.getTime() - 1000000000);  
document.cookie = cname + "=" + "CookieDeleted" + "; expires=" + expires.toGMTString();
}


// Other internal functions


function h_thisFile ()
// Returns the file name of the current window location
{
var url = window.location.href;
var i = url.lastIndexOf ('/');
return (url.substring (i +1, url.length));
}

function h_show(object) 
// Sets the visibility attribute to visible
{ 
if (outputWin.document.layers && outputWin.document.layers[object]) 
  outputWin.document.layers[object].visibility = 'visible'; 
else if (outputWin.document.all) 
  {
  outputWin.document.all[object].style.visibility = 'visible'; 
  outputWin.document.all[object].style.zIndex = 100;
  }
}

function h_hide(object) 
// Sets the visibility attribute to hidden
{ 
if (outputWin.document.layers && outputWin.document.layers[object]) 
  outputWin.document.layers[object].visibility = 'hidden'; 
else if (outputWin.document.all) 
  outputWin.document.all[object].style.visibility = 'hidden'; 
}

function h_member (ident, str)
// Returns true if _ident_ belongs to the string _str_.
{
if (str.indexOf(ident) != -1) return (true);
else return(false);
}

function h_makeArg (args, fromInd)
// Returns an array that contains a list of individual arguments.
{
if ( (fromInd == args.length -1) && (typeof (args[fromInd].sort) == "function"))
  return (args[fromInd]);
var ind=0;
var argArray = new Array ();
for (var i=fromInd ; i<args.length ; i++)
  {
  if (typeof (args[i].sort) == "function") 
    for (var j=0 ; j<args[i].length ; j++)
      argArray[ind++]=args[i][j];
  else argArray[ind++]=args[i];
  }
return (argArray);
}

// Functions for identifiers manipulation


function makeIdent (lfile)
// Produces the identifier to be used in the history.
{
var i = lfile.lastIndexOf ('/');
var file = lfile.substring (i +1, lfile.length);
var dot = file.lastIndexOf ('.');
if (dot == -1) return (file + '?');
var suffix = file.substring (dot +1, file.length);
var prefix = file.substring (0, dot);
if (suffix == "html") return (prefix);
if (suffix == "htm")  return (prefix + '/');
return (file + '?');
}

function makeFileName (ident)
// Returns the file name corresponding to _ident_.
{
var end = ident.length -1;
var type_ext = ident.charAt (end);
if (type_ext == "?") return (ident.substring(0, end));
if (type_ext == "/")  return (ident.substring(0, end) + ".htm");
return (ident + ".html");
}


// Functions for history manipulation


function setCurrHist (value)
// Sets the history to value _value_.
{
var cname = histCookieName + h_nbCookies.toString();
var exp_date = new Date (); 
exp_date.setTime(exp_date.getTime() + 31536000000); 
   //(+ 24 * 60 * 60 * 1000 * 365) = 1 year after
h_setCookie (cname, value, exp_date);  
return false;
}

function histValue ()
// Returns the value of the history.
{
var hist = "*";
for (var i = 1; i <= h_nbCookies; i++)
  hist = hist + h_cookieVal ( histCookieName + i.toString() );
return hist;
}

function histArray ()
// Returns an array containing the history (array of file names).
{
h = new Array ();
var ind = 0;
var i = 0;
var j = h_hist.indexOf ('*', i+1);
while (j != -1)
  {
  h[ind] = makeFileName ( h_hist.substring( i+1, j));
  ind++;
  i = j;
  j = h_hist.indexOf ('*', i+1);
  }
return (h);
}


// End user functions for text or node (or whatever) specification.
// These functions can also be used inside conditions.


function select (mode,a1,a2)
{
if (mode == "random") 
  {var arg = h_makeArg( select.arguments, 1);  
  return (random (arg));
  }
if (mode == "oldest")
  {var arg = h_makeArg( select.arguments, 1);
   return (oldest(arg));
   }
if (mode == "filteredRandom")
  {var arg = h_makeArg( select.arguments, 3);
   return (filteredRandom (a1, a2, arg));
  }
if (mode == "filteredFirst")
  {var arg = h_makeArg( select.arguments, 3);
   return (filteredFirst (a1, a2, arg));
  }
if (mode == "filteredOldest")
  {var arg = h_makeArg( select.arguments, 3);
   return (filteredOldest (a1, a2, arg));
  }
if (mode == "conditionalRandom")
  return (conditionalRandom (select.arguments));
if (mode == "conditionalFirst")
  return (conditionalFirst (select.arguments));
}


function random (items)
// Randomly selects an item in _items_. 
{
var arg = h_makeArg(random.arguments,0);
var arg_len = arg.length;
var select = Math.floor (Math.random () * (arg_len) );
if (select == arg_len) select = arg_len - 1;
return (arg[select]);
}

function oldest (nodes)
// Returns the oldest node in _nodes_
{
var arg = h_makeArg(oldest.arguments, 0);
var arg_len = arg.length;
var oldest_val = nbVisits();
var oldest_node = null;
for (var i=0 ; i<arg_len ; i++)   
  {
  var ident = makeIdent(arg[i]);
  var j = h_hist.lastIndexOf('*' + ident + '*')
    if (j < oldest_val)
      {
      oldest_val = j;
      oldest_node = arg[i];
      } 
  }
return (oldest_node);
}


function filteredRandom (condFunct, otherwiseItem, items)
// condFunct is a JavaScript function.
// Randomly selects an item _itemi_  such that _condFunct(itemi)_ holds.
// If no condition is satisfied then returns _otherwiseItem_.
{
var arg = h_makeArg(filteredRandom.arguments, 2);
var arg_len = arg.length;
nv = new Array (arg_len);
var nbnv = 0;
for (var i = 0 ; i < arg_len ; i++)
  if (condFunct(arg[i])) 
    nv[nbnv++] = i;
if (nbnv ==0 ) return (otherwiseItem);
else
  {
  var select = Math.ceil (Math.random () * (nbnv) );
  if (select == 0) select = 1;
  return (arg[ nv[select -1] ]);
  }
}


function filteredFirst (condFunct, otherwiseItem, items)
// condFunct is a JavaScript function.
// Returns the first item _itemi_ such that _condFunct(itemi)_ holds.
// If no condition is satisfied then returns _otherwiseItem_.
{
var arg = h_makeArg( filteredFirst.arguments, 2);
var arg_len = arg.length;
for (var i=0 ; i<arg_len ; i++)
  if ( condFunct (arg[i]) )
    return (arg[i]);
return (otherwiseItem);
}

function filteredOldest (condFunct, otherwiseNode, nodes)
// condFunct is a JavaScript function.
// Returns the oldest (wrt its last visit) node that satisfies condFunct.
// (By default: non visited nodes are oldest than all other nodes.)
// If no node satisfies condFunct, then returns otherwiseNode.
{
var arg = h_makeArg (filteredOldest.arguments, 2);
var arg_len = arg.length;
nv = new Array (arg_len);
var nbnv = 0;
for (var i = 0 ; i < arg_len ; i++)
  if (condFunct(arg[i])) 
    nv[nbnv++] = i;
if (nbnv ==0 ) return (otherwiseNode);
else
  {
  var oldest_val = nbVisits();
  var oldest_node = null;
  for (var i=0 ; i<nbnv ; i++)   
   {
   var ident = makeIdent(arg[nv[i]]);
   var j = h_hist.lastIndexOf('*' + ident + '*')
     if (j < oldest_val)
       {
       oldest_val = j;
       oldest_node = nv[i];
       }
   }
   return (arg[oldest_node]);
  }
}


function conditionalRandom (otherwiseItem, itemsAndConds)
// Randomly selects an item _itemi_  such that _condi_ holds.
// If no condition is satisfied then returns _otherwiseItem_.
{
if (typeof(otherwiseItem) == "object")
  {var arg = otherwiseItem; var ind = 2}
else
  {var arg = conditionalRandom.arguments; var ind = 1}
var arg_len = arg.length;
nv = new Array (arg_len - 1);
var nbnv = 0;
for (var i = ind ; i < arg_len ; i=i+2)
  if (arg[i+1]) 
    {
    nv[nbnv] = i;
    nbnv++;
    }
if (nbnv ==0) return (arg[ind-1]);
else
  {
  var select = Math.ceil (Math.random () * (nbnv) );
  if (select == 0) select = 1;
  return (arg[ nv[select -1] ]);
  }
}


function conditionalFirst (otherwiseItem, itemsAndConds)
// _itemsAndConds_ is of the form:
//     item1, cond1, item2, cond2, item3, cond3,...
// items can be file names or text, or whatever.
// Returns the first item _itemi_ such that _condi_ holds.
// If no condition is satisfied then returns _otherwiseItem_.
{
if (typeof(otherwiseItem) == "object")
  {var arg = otherwiseItem; var ind = 2}
else
  {var arg = conditionalFirst.arguments; var ind = 1}
var arg_len = arg.length;
for (var i=ind ; i<arg_len ; i=i+2)
  if ( arg[i+1] )
    return (arg[i]);
return (arg[ind-1]);
}



// End user functions for node specification.
// These functions can also be used inside conditions.


function hist (rank)
// Returns the node (file name) element of rank _rank_ in the history.
// If _rank_ = 0, Hist(rank) returns the current node's file name. 
// If _rank_ is negative, the rank is counted in reverse from the 
// last element in the history.
{
if (rank > 0) 
  {
  var end = h_hist.length-1;
  var j = 0;
  var k = h_hist.indexOf ('*', j +1);
  for (var i=rank ; i>1 ; i--)
    {
    if (j >= end) break;
    j = k;
    k = h_hist.indexOf ('*', j +1);
    }
  if (j >= end) return (null);
  var ident = h_hist.substring (j+1, k);
  return (makeFileName (ident) );
  }
if (rank <= 0) 
  {
  var j = h_hist.length -1;
  var k = h_hist.lastIndexOf ('*', j-1);
  for (var i=rank ; i<0 ; i++)
    {
    if (j <= 0) break;
    j = k;
    k = h_hist.lastIndexOf ('*', j-1);
    }
  if (j <= 0) return (null);
  var ident = h_hist.substring (k+1, j);
  return (makeFileName (ident) );
  }
}

function histRank (node)
// Returns an array containing the indices where the node
// _node_ occurs in the history.
{
var ident = makeIdent (node);
indarray = new Array ();
var i = 0;  var j = 0; var ind = 1;
var k = h_hist.indexOf ('*', j +1);
while (k != -1)
  {
  if (h_hist.substring (j+1, k) == ident)
    {
    indarray[i] = ind;
    i++;
    }
  j = k;
  k = h_hist.indexOf ('*', j +1);
  ind++;
  }
return (indarray);
}

function visitedFirst (nodes)
// Returns the node (file name) in _nodes_ that was visited first.
// Returns null if none of _nodes_ were visited.
{
var arg = h_makeArg(visitedFirst.arguments,0);
var arg_len = arg.length;
var max_val = h_hist.length;
var max_node = null;
for (var i=0 ; i<arg_len ; i++)
  {   
  var ident = makeIdent (arg[i]);
  var j = h_hist.indexOf ('*' + ident + '*');
  if (j != -1 && j < max_val)
    {
    max_val = j;
    max_node = arg[i];
    }
  }
return (max_node);
}

function visitedLast (nodes)
// Returns the node (file name) in _nodes_ that was visited last.
// Returns null if none of _nodes_ were visited.
{
var arg = h_makeArg (visitedLast.arguments, 0);
var arg_len = arg.length;
var max_val = -1;
var max_node = null;
for (var i=0 ; i<arg_len ; i++)   
  {
  var ident = makeIdent(arg[i]);
  var j = h_hist.lastIndexOf('*' + ident + '*')
    if (j > max_val)
      {
      max_val = j;
      max_node = arg[i];
      }
  }
return (max_node);
}


// Other end user functions for condition building.


function allVisited (nodes)
// Returns true if all nodes in _nodes_ belong to the history.
{
var arg = h_makeArg (allVisited.arguments, 0);
var arg_len = arg.length;
for (var i=0 ; i<arg_len ; i++)
  {
  if (typeof (arg[i]) == "string") {
    var ident = makeIdent (arg[i]);
    if ( ! h_member('*' + ident + '*', h_hist) ) 
      return (false);
    }
  else
   if (arg[i].allNodesVisited() == false)
     return (false);
  }
return (true);
}

function someVisited (nodes)
// Returns true if at least one node in _nodes_ belongs to the history.
{
var arg = h_makeArg (someVisited.arguments, 0);
var arg_len = arg.length;
for (var i=0 ; i<arg_len ; i++)
  {
  if (typeof (arg[i]) == "string") {
    var ident = makeIdent (arg[i]);
    if ( h_member('*' + ident + '*', h_hist) ) 
      return (true);
    }
  else
   if (arg[i].someNodeVisited() == true)
     return (true);    
  }
return (false);
}

function visited (node)
{
return ( allVisited(node) );
}

function notVisited (node)
{
return ( ! someVisited (node) );
}

function visitedFrom (node)
// Returns true if _node_ was visited immediately after the current node.
// if more than 1 arguments, tests whether _node_ was visited  
// immediately after one (at least) of the other arguments.
{
var arg = h_makeArg (visitedFrom.arguments, 0);
var arg_len = arg.length;
var offset = "*" + makeIdent (node) + "*";
if (arg_len == 1)
  {
  offset = "*" + makeIdent ( h_thisFile() ) + offset;
  return ( h_member (offset, h_hist) );
  }
else
  {
  for (var i=1 ; i<arg_len ; i++)
    {
    var offset2 = "*" + makeIdent (arg[i]) + offset;
    if ( h_member(offset2, h_hist) ) return (true);
    }
  return (false);
  }
}

function visitedNotFrom (node, nodes)
// Boolean function.
// Returns true if _node_ was visited immediately after a node not in _nodes_.
// Returns false otherwise. i.e., if every time _node_ was visited,
// it was visited from one node in _nodes_.
// Note that if _node_ was not visited then the function returns false. 
{
var arg = h_makeArg (visitedNotFrom.arguments, 1);
var arg_len = arg.length;
var arg_list = "*";
for (var i=0 ; i<arg_len ; i++)
  arg_list = arg_list + makeIdent(arg[i]) + "*";
var offset = "*" + makeIdent (node) + "*";
var indsup = h_hist.indexOf("*");
indsup = h_hist.indexOf(offset, indsup +1);
while (indsup != -1)
  {
  var indinf = h_hist.lastIndexOf("*", indsup -1);
  if ( ! h_member (h_hist.substring(indinf, indsup +1), arg_list))
    return (true);
  indsup = h_hist.indexOf(offset, indsup +1);
  }
return (false);
}

function allLinked (frameArg, nodes)
// Returns true if all nodes in _nodes_ are linked above in the 
// frame _frameArg_. _frameArg_ can be omitted (default = current window).
{
if (typeof (frameArg.location) == "undefined")
  {var frame = window;
   var arg = h_makeArg (allLinked.arguments, 0);
  }
else
  {var frame = frameArg;
   var arg = h_makeArg (allLinked.arguments, 1);
  }
var arg_len = arg.length;
var nb_links = frame.document.links.length;
var l_list = "*";
for (var i=0 ; i<nb_links ; i++)
  l_list = l_list + makeIdent(frame.document.links[i].toString()) + "*";
for (var i=0 ; i<arg_len ; i++)
  {
  if (typeof (arg[i]) == "string") {
    var ident = makeIdent (arg[i]);
    if ( ! h_member('*' + ident + '*', l_list) ) 
      return (false);
    }
  else
    if (arg[i].allNodesLinked() == false)
      return (false);
  }
return (true);
}

function someLinked (frameArg, nodes)
// Returns true if at least one nodes in _nodes_ is linked above 
// in the frame. _frameArg_ can be omitted.
{
if (typeof (frameArg.location) == "undefined")
  {var frame = window;
   var arg = h_makeArg (someLinked.arguments, 0);
  }
else
  {var frame = frameArg;
   var arg = h_makeArg (someLinked.arguments, 1);
  }
var arg_len = arg.length;
var nb_links = frame.document.links.length;
var l_list = "*";
for (var i=0 ; i<nb_links ; i++)
  l_list = l_list + makeIdent(frame.document.links[i].toString()) + "*";
for (var i=0 ; i<arg_len ; i++)
  {
  if (typeof (arg[i]) == "string") {
    var ident = makeIdent (arg[i]);
    if ( h_member('*' + ident + '*', l_list) ) 
      return (true);
    }
  else
    if (arg[i].someNodeLinked() == true)
      return (true);
  }
return (false);
}

function linked (node)
{
return ( allLinked (node) );
}

function notLinked (node)
{
return ( ! someLinked (node ));
}

function notVisitedAndNotLinked (node)
{
return ( ! (someVisited (node) || someLinked (node) ) );
}

function allLinksFollowed (frameArg)
// Returns true if all links in the document belong to the history.
{
if (allLinksFollowed.arguments.length == 0)
  var frame = window;
else
  var frame = frameArg;
var nb_links = frame.document.links.length;
for (var i=0 ; i<nb_links ; i++)
  {
  var ident = makeIdent (frame.document.links[i].toString());
  if ( ! h_member('*' + ident + '*', h_hist) ) 
    return (false);
  }
return (true);
}

function nbVisits (nodes)
// Returns how many times the nodes _nodes_ were already visited.
// if no argument, then returns the number of nodes already visited, 
// i.e., the length of the history.
{
var arg_len = nbVisits.arguments.length;
var nb_visits = 0;
if (arg_len == 0)
  {
  var i = h_hist.indexOf ('*');
  i = h_hist.indexOf ('*', i+1);
  while ( i != -1)
    {
    i = h_hist.indexOf ('*', i+1);
    nb_visits++;
    }
  }
else
  {
  var arg = h_makeArg (nbVisits.arguments, 0);
  arg_len = arg.length;
  for (var j=0 ; j<arg_len ; j++)
    {
    var ident = makeIdent (arg[j]);
    var i = h_hist.indexOf ('*' + ident + '*');
    while (i != -1)
      {
      nb_visits++;
      i = h_hist.indexOf ('*' + ident + '*',i+1);
      }
    }
  }
return (nb_visits);
}
        
function nbVisited (nodes)
// Returns how many nodes in _nodes_ were visited yet.
// if called with no argument, then returns the number of 
// different nodes already visited.
{
var nbnodes = 0;
var arg_len = nbVisited.arguments.length;
if (arg_len == 0)
  {
  var i = 0;
  while ( i < h_hist.length)
    {
    var next = h_hist.indexOf ('*', i+1);
    if (next == -1) break;
    var h_ident = h_hist.substring (i, next +1);
    if (h_hist.indexOf(h_ident) == i)
      nbnodes++;
    i = next;
    }
  }
else
  {
  var arg = h_makeArg (nbVisited.arguments, 0);
  arg_len = arg.length;
  for (var i=0 ; i<arg_len ; i++)
    {
    if (typeof (arg[i]) == "string") {
      var ident = makeIdent (arg[i]);
      if (h_member('*' + ident + '*', h_hist)) 
        nbnodes++;
      }
    else
      nbnodes = nbnodes + arg[i].nbNodesVisited();
    }
  }
return (nbnodes);
}

function nbLinks ()
// Returns the number of links in the document.
{
return (document.links.length);
}

function nbLinksNotFollowed ()
// returns how many links in the document lead to non-visited nodes.
{
var nb_links = document.links.length;
var nb = 0;
for (var i=0 ; i<nb_links ; i++)
  {
  var ident = makeIdent (document.links[i].toString());
  if ( ! h_member('*' + ident + '*', h_hist) ) 
    nb++;
  }
return (nb);
}


// End-user functions for history munipulation


function initHist (node)
// Initialises the history with the current node 
// (or with _node_ if specified).
{
h_setCookie (histCookieName + "Nb","1");
h_nbCookies = 1;
if (initHist.arguments.length == 0)
  h_hist = makeIdent( h_thisFile() ) + '*';  
else
  h_hist = makeIdent(node) + '*';  
h_currHist = h_hist;
setCurrHist (h_currHist);
}

function addHist (node)
// Adds the current node (or the node _node_ if specified) to the history.
{
if (addHist.arguments.length == 0)
  ident = makeIdent( h_thisFile() ) + '*';
else
  ident = makeIdent(node) + '*';
h_hist = h_hist + ident;
if (h_currHist.length + ident.length > 1950)
  {
  h_nbCookies = h_nbCookies + 1;
  if (h_nbCookies > 9)
    alert('maximum history length exceeded: the history may be truncated and result in unexpected behavior');
  h_setCookie(histCookieName + 'Nb',h_nbCookies.toString());
  h_currHist = ident;
  }
else
  h_currHist = h_currHist + ident;
setCurrHist (h_currHist);
}

function deleteHist ()
// Deletes the history.
{
for (var i=1; i <= h_nbCookies ; i++)
  h_deleteCookie (histCookieName + i.toString());
h_setCookie (histCookieName + "Nb","1");
h_nbCookies = 1;
h_currHist = "";
h_hist = "";
}

function histEmpty ()
// Returns true if the history is empty.
{
return (h_histEmpty);
}


// End-user functions for writing and linking


function condJSCode (yesJavaScriptCode, cond, noJavaScriptCode)
// Conditional JavaScript code.
// _yesJavaScriptCode_ is  executed if _cond_ holds.
// _noJavaScriptCode_, if specified,  is executed if _cond_ does not hold.
{
if (cond)
  h_write("<SCRIPT type='text/javascript'>" +  yesJavaScriptCode + "</SCRIPT>");
else
  if ( typeof(noJavaScriptCode) == "string" )
    h_write("<SCRIPT type='text/javascript'>" + noJavaScriptCode + "</SCRIPT>");
}

function display (yesText, cond, noText)
// Displays conditional text, 
// possibly with popup or/and status text if specified
// (in this case, the text is displayed as a link with void target
// and color _hotTextColor_).
{
if (display.arguments.length == 1 || cond)
  h_display (yesText);
else
  if ( typeof(noText) == "string" )
    h_display (noText);

hotTextColor = hotTextColorDefault;
popupText = popupTextDefault;
statusText = statusTextDefault;
popupAtt = popupAttDefault;
popuptextColor = popupTextColorDefault;
linkPrefix = linkPrefixDefault;
linkSuffix = linkSuffixDefault;
linkClass = "link";
}

function h_display (text)
// Displays text, possibly with popup or/and status text if specified.
{
if (popupText == null && statusText == null)
  h_write (text);
else
  {
  if (statusText == null)
    statusText = " ";
  linkColor = hotTextColor;
  linkClass = "hotText";
  h_link("javascript:void(0)", text);
  linkColor = linkColorDefault;
  }
}


function h_write (text)
// Writes text on the outputWin window
{
outputWin.document.write (text);
}


function h_link (node, text)
// Creates a link to file _node_ with text _text_.
{
if (node == null)
  {
  if (deadLinkPopup == false) 
    popupText = null;
  else
    if (popupText == null && titlePopup == true)
      if (typeof(titleArray['deadLink']) != "undefined")
        popupText = titleArray['deadLink'];
  if (deadLinkStatus == false)
    statusText = null;
  else
    if (statusText == null && titleStatus == true)
      if (typeof(titleArray['deadLink']) != "undefined")
        statusText = titleArray['deadLink'];
  if (popupText == null && statusText == null)
    {
    if (deadLinkStyle == "link")
      {var Btag = "<U>"; var Etag = "</U>";}
    else
      {var Btag = ""; var Etag = "";}
    h_write ('<FONT color=' + '"' + deadLinkColor + '">' + Btag + text + Etag + '</FONT>');
    return (true);
    }
  node = "javascript:void(0)";
  linkColor = deadLinkColor;
  linkClass = "deadLink";
  }
else
  {
  if (popupText == null && titlePopup == true)
    if (typeof(titleArray[node]) != "undefined")
      popupText = titleArray[node];
  if (statusText == null && titleStatus == true)
    if (typeof(titleArray[node]) != "undefined")
      statusText = titleArray[node];
  }

if (linkColor != null)
  {
  var begin_font_tag = '<FONT color="' + linkColor + '">';
  var end_font_tag = "</FONT>";
  }
else
  {
  var begin_font_tag = "";
  var end_font_tag = "";
  }

h_write (linkPrefix + '<A href=' + node);
if (linkClass != null)
  h_write(" class='" + linkClass + "'");
if (statusText != null || popupText != null)
  h_write(" onMouseOver='");
if (statusText != null)
  h_write("window.status=" + '"' + statusText + '";');
if (popupText != null)
  h_write("h_show(" + '"pop' + h_popnb.toString() + '"' + ");");
if (statusText != null || popupText != null)
  h_write(" return true' onMouseOut='");
if (statusText != null)
  h_write('window.status="";');
if (popupText != null)
  h_write ("h_hide(" + '"pop' + h_popnb.toString() + '"' + ");");
if (statusText != null || popupText != null)
  h_write(" return true'");
h_write (">" + begin_font_tag + text + end_font_tag + '</A>' + linkSuffix);
if (popupText != null)
  {
if (popupTextColor != null)
  {
  var begin_font_popup = '<FONT color="' + popupTextColor + '">';
  var end_font_popup = "</FONT>";
  }
else
  {
  var begin_font_popup = "";
  var end_font_popup = "";
  }
  h_write("<SPAN id=" + '"pop' + h_popnb.toString() + '"' + " CLASS='popup'><TABLE " + popupAtt + " " + popupAttDefault + "><TR><TD>" + begin_font_popup + popupText + end_font_popup + "</TD></TR></TABLE></SPAN>");  
  h_popnb++;
  }
linkColor = linkColorDefault;
linkClass = "link";
return (true);
}    

function link(node, text, cond, noNode, noText)
// If called with exactly 2 arguments then creates a link.
// If called with more than 2 arguments, then creates a 
// link to node _node_ with text _text_ if _cond_ holds.
// If _cond_ does not hold then, if _noText_ is specified 
// then creates a link to _noNode_ with text _noText_
// else, creates a link to _noNode_ with text _text_.
{
var arg_len = link.arguments.length;
if (arg_len == 2)
  h_link (node, text);
if (arg_len > 2)
  {
  if (cond)
    h_link (node, text);
  else 
    {
    if ( typeof(noText) != "undefined" )
      text = noText;
    if ( typeof(noNode) !="undefined" )
      h_link (noNode, text);
    }
  }

linkColor = linkColorDefault;
popupText = popupTextDefault;
statusText = statusTextDefault;
popupAtt = popupAttDefault;
popupTextColor = popupTextColorDefault;
linkPrefix = linkPrefixDefault;
linkSuffix = linkSuffixDefault;
linkClass = "link";
}


//
// Style definitions
//

// Style A.link: 
var linkCSS = "";
h_write("<style type='text/css'>A.link {" + linkCSS + "}</style>");

// Style A.hotText:
var hotTextCSS = "text-decoration: none";
h_write("<style type='text/css'>A.hotText {" + hotTextCSS + "}</style>");

// Style A.deadLink:
var deadLinkCSS = "text-decoration: none";
h_write("<style type='text/css'>A.deadLink {" + deadLinkCSS + "}</style>");

// Style SPAN.popup
var popupCSS = "position: absolute; visibility: hidden;";
h_write("<style type='text/css'>SPAN.popup {"+ popupCSS + "}</style>");




