//                       -*- mode: javascript -*-
//
// Author: Memetrics Holding Pty. Ltd. 2005.
//         All Rights Reserved.
//
// License: You may use and extend this library in accordance
//          with the terms of your XOS(TM) license.
//
// Version: $Id: xos-toolkit.js,v 1.2 2007/03/14 13:13:32 ssingh3 Exp $
// Name:    $Name: HEAD $
//
//

////////////////////////////////////////////////////////////////////////////////
//
//  Cookies and P3P handling, do the equivalent of;
//
//  FIXME: better comment here.
// <meta http-equiv="P3P" content='CP="CAO DSP AND SO ON" policyref="/w3c/p3p.xml"' />
//

////////////////////////////////////////////////////////////////////////////////
//
//  Cookie Handling from: http://www.dustindiaz.com/top-ten-javascript
//
var getCookie = function( name ) {
  var start = document.cookie.indexOf( name + "=" );
  var len = start + name.length + 1;
  if ((!start) && (name != document.cookie.substring( 0, name.length ))) {
    return null;
  }

  if (start == -1) {
    return null;
  }

  var end = document.cookie.indexOf( ';', len );
  if (end == -1) {
    end = document.cookie.length;
  }

  return unescape( document.cookie.substring( len, end ) );
};

var setCookie = function( name, value, expires, path, domain, secure ) {
  var today = new Date();
  today.setTime( today.getTime() );

  if (expires) {
    expires = expires * 3000 * 60 * 60 * 24;
  }

  var expires_date = new Date( today.getTime() + (expires) );

  document.cookie = name + '=' + escape( value ) +
  ( ( expires ) ? ';expires=' + expires_date.toGMTString() : '' ) + //expires.toGMTString()
  ( ( path    ) ? ';path='    + path : ''                       ) +
  ( ( domain  ) ? ';domain='  + domain : ''                     ) +
  ( ( secure  ) ? ';secure' : ''                                );
};

var deleteCookie = function( name, path, domain ) {
  if (getCookie( name )) {
    document.cookie = name + '=' +
    ( ( path   ) ? ';path='   + path : ''   ) +
    ( ( domain ) ? ';domain=' + domain : '' ) + ';expires=Thu, 01-Jan-1970 00:00:01 GMT';
  }
};


////////////////////////////////////////////////////////////////////////////////
//
//  JS/DOM Utilities
//
var _XOS_addEvent = function(o,e,f) {
   if (o.addEventListener){ o.addEventListener(e,f,true); return true; }
   else if (o.attachEvent){ return o.attachEvent("on"+e,f); }
   else { return false; }
};

var _XOS_getElementsByTagAndClassName = function( tagName, classRegExp, parent ) {
  tagName     = tagName || "*";
  parent      = parent  || document;

  classRegExp = classRegExp || null;

  var all      = parent.getElementsByTagName( tagName );
  var element  = null;
  var elements = [];
  for (var idx = 0; idx < all.length; idx++) {
    element = all[idx];

    // ignore anything that isn't an element.
    if (element.nodeType != 1) { continue; }

    //  If the want the additional test for a class then do it
    if (classRegExp !== null) {
      if (element.className && (-1 < element.className.search( classRegExp ))) {
        elements.push( element );
      }
    }
    //..otherwise it matched the on the tag so include it
    else {
      elements.push( element );
    }
  }
  return elements;
};

var _XOS_isNullOrUndefined = function( thing ) {
  return (thing === null) || (thing === undefined);
};

var _XOS_hasProperties = function( obj ) {
  for (var key in obj) { return true; }
  return false;
};

////////////////////////////////////////////////////////////////////////////////
//
//  Functional Helpers
//
var _XOS_identity = function( thing ) {
  return thing;
};

var _XOS_removeIf = function( predicate, list ) {
  var results = [];
  for (var idx = 0; idx < list.length; idx++) {
    if (!predicate( list[idx] )) {
      results.push( list[idx] );
    }
  }
  return results;
};

var _XOS_removeIfNot = function( predicate, list ) {
  var results = [];
  for (var idx = 0; idx < list.length; idx++) {
    if (predicate( list[idx] )) {
      results.push( list[idx] );
    }
  }
  return results;
};

//  An extremely simple map, treats each entry in the list as a
//  arguments to the function.
//
//  Example Usage: map( function( p ){ return p + 2; }, [1, 2, 3] );
var _XOS_mapApply = function( fn, list ) {
  var result = [];
  var args   = null;
  for (var idx = 0; idx < list.length; idx++) {
    args = list[idx];

    //  If the args are already a list, assume that the caller knows
    //  what they are doing and wants these passed as arguments to the
    //  function.  However as a convenience if we only have a single
    //  argument for each call just wrap it in a list to stop JS from
    //  complaining.  We do this instead of just calling fn( args ) as
    //  we want to explicitly make this == null to avoid any crazy JS
    //  this wrangling and hence unexpected behaviour.
    if (typeof(args.length) != 'number') {
      args = [args];
    }

    result.push( fn.apply( null, args ) );
  }
  return result;
};

////////////////////////////////////////////////////////////////////////////////
//
//  Misc Helpers
//
var _XOS_items = function (obj) {
  var result = [];
  var e;
  for (var prop in obj) {
    var v;
    try {
      v = obj[prop];
    } catch (e) {
      continue;
    }
    result.push([prop, v]);
  }
  return result;
};

function _XOS_makeGUID() {
   return Math.floor(Math.random() * 1000000000000);
}

//  Joins the arguments together inserting the prefix between each.
//  null/undefined arguments are ignored
var _XOS_Join = function() {
  if (arguments.length < 2) { return arguments[1] || ""; }

  var prefix    = arguments[0];
  var delimiter = "";
  var result    = "";
  for (var i = 1; i < arguments.length; i++) {
    if (!_XOS_isNullOrUndefined(arguments[i])) {
      result    = result + delimiter + arguments[i];
      delimiter = prefix;
    }
  }
  return result;
};


////////////////////////////////////////////////////////////////////////////////
//
//  XOS Treatment Handler
//
var SCRIPT_PATH      = null;
var TREATMENT_SCRIPT = null;
var PREVIEW_SCRIPT   = null;
var XOS_TEST_COOKIE  = "XOS Test Cookie";
var XOS_VISITOR_KEY  = "xosVisitorKey";

//  The JS execution model means that each script is executed before
//  the next one (if any) is loaded, this means that at the time the
//  following bit of JS runs this script (ie, xos-toolkit.js) will be
//  the last <script> element in the header.
//
//  We use this fact to keep a handle on this script so that we can
//  query it later to find out its location, etc.  Once we know the
//  location we can dynamically add other scripts that are in the same
//  location.
var _XOS_setupGlobalScriptPath = function() {
  var scripts = document.getElementsByTagName( "script" );
  if (scripts.length === 0) {
    throw new Error( "Failed to find the current script element." );
  }
  else {
    SCRIPT_PATH = scripts[scripts.length - 1].src;
    var pos     = SCRIPT_PATH.lastIndexOf( "/" );
    SCRIPT_PATH = (pos == -1) ? "" : SCRIPT_PATH.substring(0, pos + 1);
  }
};
_XOS_setupGlobalScriptPath();

/*
 *  Possible META values.
 */
var NONE       = 'NONE';
var URL        = 'URL';
var FILE       = 'FILE';
var INNER_HTML = FILE;


/*
 *  XOS Object
 *
 */
var XOS = { Version   : "$Id: xos-toolkit.js,v 1.2 2007/03/14 13:13:32 ssingh3 Exp $",
            Name      : "$Name: HEAD $",
            URL       : null,
            BaseURL   : null,
            Client    : null,
            Workspace : null,

            QueryArray : null,

            AllowsCookies : null,

            Attributes : {}, // list of name/dom ids

            Characteristics : {},
            Facts           : {},

            HasRun    : false,
            onSuccess : null,
            onTimeout : null,
            Timeout : 5000 }; //  Timeouts spec'd in milliseconds.

XOS.includeScript = function( name, relative /*optional: defaults to false*/ ) {
  //  Can optionally load javascript relative to the location of this script.
  var url = relative ? SCRIPT_PATH + name : name;

  var result = document.createElement( "script" );
  result.setAttribute( "src", url );
  document.getElementsByTagName( "HEAD" )[0].appendChild( result );
  return result;
};

XOS.initialise = function( base_url, client, workspace, timeout ){
   XOS.BaseURL     = base_url;
   XOS.Client      = encodeURIComponent( client    );
   XOS.Workspace   = encodeURIComponent( workspace );
   XOS.Timeout     = timeout ? timeout : XOS.Timeout;
   XOS.Previewing  = XOS.getQueryStringValue( 'XOS_PREVIEW'  ) == 'YES';
   XOS.ShowCapture = XOS.getQueryStringValue( 'SHOW_CAPTURE' ) ? true : false;

   XOS.URL = _XOS_Join( '/',
                        XOS.BaseURL,
                        'allocator',
                        XOS.Client,
                        XOS.Previewing ? 'fetch-preview' : 'fetch-treatment',
                        XOS.Previewing ? 'manager' : 'GUID',
                        XOS.Workspace );

   //  If we can't set cookies then we can't do anything useful with a
   //  treatment.
   setCookie( XOS_TEST_COOKIE, "yep", 1, "/" );
   XOS.AllowsCookies = getCookie( XOS_TEST_COOKIE );
};

XOS.finalise = function( onSuccess /*optional*/, onTimeout /*optional*/ ) {
  //  If Cookies are allowed then we are going to hide all of the
  //  default content and then attempt to splice in a treatment.
  if (XOS.AllowsCookies || XOS.Previewing) {
    document.write( '<style>.xos-attribute { display : none; }</style>' );
    _XOS_addEvent( window, 'load', function() { XOS.fetchTreatmentFromXOS( onSuccess, onTimeout ); } );
  }
  //  ..Otherwise; cookies aren't allowed so we can't do anything, the
  //  xos-attribute style wont be written to the document header so
  //  all of the default content is just displayed as per normal.
};

XOS.setHasRun = function () {
   XOS.HasRun = true;
};

//  Parts of the treatment need to be updated with information that is
//  only available on the Javascript side, things like images.
XOS.ensureTreatmentConsistency = function( treatment ) {

  // Update the DOM_ID in the treatment we got from XOS
  for (var key in treatment){
    //  Copy the registered Attribute dom ID into the treatment object
    //  so that later handlers can swap in the correct thing.
    treatment[key].DOM_ID = XOS.Attributes[key];

    // We have a special case for images;
    //
    // If the meta type is URL but the content is null then we
    // construct an image url from our configuration and the
    // attribute.
    if ((treatment[key].META == 'URL') && (treatment[key].CONTENTS == 'NONE')) {
      var url = _XOS_Join( '/',
                           XOS.BaseURL,
                           'allocator',
                           XOS.Client,
                           'fetch-attribute/manager',
                           XOS.Workspace,
                           encodeURIComponent( key ),
                           encodeURIComponent( treatment[key].LEVEL ) );
      treatment[key].CONTENTS = url;
    }
  }

  XOS.Treatment = treatment;
};

XOS.getTreatmentURL = function() {
  return XOS.URL + "?" + XOS.getXOSQueryString();
};

//  The last line in the fetched treatment calls this method, and gets
//  the results spliced into the DOM.
XOS.doOnSuccess = function( treatment ) {
  if (XOS.HasRun) { return; }
  XOS.setHasRun();

  XOS.ensureTreatmentConsistency( treatment );
  XOS.onSuccess( treatment );
};
XOS.doCallback = XOS.doOnSuccess; // Backward compatibility.

XOS.doOnTimeout = function() {
  if (XOS.HasRun) { return; }
  XOS.setHasRun();

  XOS.onTimeout();
};
XOS.doDefaults = XOS.doOnTimeout; // Compatibility with allocator.

XOS.fetchTreatmentFromXOS = function( onSuccess, onTimeout ) {
  //  If we have been called before (ie, from DBG) them make sure that
  //  we don't have anything left over from before.
  if (TREATMENT_SCRIPT) {
    delete TREATMENT;
    delete TREATMENT_SCRIPT;
    TREATMENT_SCRIPT = null;
  }

  //  If applicable, honour the request by the treatment explorer to
  //  show the captured data for this page.
  XOS.showCapturedData();

  XOS.HasRun    = false;
  XOS.onSuccess = onSuccess || XOS.defaultOnSuccess;
  XOS.onTimeout = onTimeout || XOS.defaultOnTimeout;
  XOS.TimeoutID = window.setTimeout( XOS.doOnTimeout, XOS.Timeout );

  //  All or our safe guards are in place and we are ready to actually
  //  handle a treatment.
  XOS.includeScript( XOS.getTreatmentURL() );
};

XOS.removeXOSAttributeClassFromAllElements = function() {
  var xosAttributeRE = /\bxos-attribute\b/;
  var removeXOSAttributeClassName = function( element ) {
    element.className = element.className.replace( xosAttributeRE, '' );
  };

  _XOS_mapApply( removeXOSAttributeClassName,
                 _XOS_getElementsByTagAndClassName( null, "xos-attribute" ) );
};

////////////////////////////////////////////////////////////////////////////////
//
//  Helpers for splicing our content into the DOM.
//
XOS.HREF_TYPE       = 1;
XOS.SRC_TYPE        = 2;
XOS.INNER_HTML_TYPE = 3;
XOS.getNodeDestType = function( node ) {
   var nodeName = '~' + node.nodeName.toLowerCase() + '~';
   if (-1 < '~a~area~link~base~'.search( nodeName )) { return XOS.HREF_TYPE; }

   if (-1 < '~img~iframe~frame~script~'.search( nodeName )) { return XOS.SRC_TYPE; }

   return XOS.INNER_HTML_TYPE;
};

XOS.log = function() {
  if (DBG) {
    DBG.log.apply( null, arguments );
  }
};

XOS.defaultOnSuccess = function( treatment ) {
  var name    = null;
  var content = null;
  var node    = null;
  for (name in XOS.Attributes) {
    //  Get the content that XOS would like us to splice into the
    //  page.
    content = treatment[name];

    if (!content) {
      XOS.log( "There is no content with name '" + name + "' in fetched treatment." );
      continue;
    }

    if (content.IGNORE) {
      XOS.log( "Told to ignore content with name '" + name + "'." );
      continue;
    }

    // NOTE: By checking the content.CONTENTS below we are in effect
    // saying that if they haven't uploaded any content then we show
    // the default content.  Is this correct?  Presumably they will be
    // using the treatment explorer to double check the uploaded
    // content so this check is more to prevent visitors from
    // accidentally seeing bogus pages.
    if (!content.CONTENTS) {
      XOS.log( "Content with name '" + name + "' has no CONTENT. ** Advanced usage assumed **" );
      continue;
    }

    //  Make sure we have somewhere to put any content.
    node = XOS.getElement( name );
    if (!node) {
      XOS.log( "Failed to find DOM Element for registered Attribute '" + name + "'." );
      continue;
    }

    //  We have contents and we have a place to put it so try and
    //  match the type of the content with the type of the DOM element
    //  and splice it in.
    switch (XOS.getNodeDestType( node )) {
    case XOS.HREF_TYPE :
      node.href = content.CONTENTS;
      break;
    case XOS.SRC_TYPE :
      node.src = content.CONTENTS;
      break;
    default:
      node.innerHTML = content.CONTENTS;
      break;
    }
  }

  //  we have finished so show everything
  XOS.removeXOSAttributeClassFromAllElements();
};

XOS.defaultOnTimeout = function () {
  //  This searches the entire DOM and removes the class name
  //  'xos-attribute' from the element.className attribute.  This has
  //  the effect of removing the "display:none" CSS style from these
  //  elements and hence showing the default content.
  XOS.removeXOSAttributeClassFromAllElements();
};

XOS.getXOSQueryString = function () {
  //  In preview mode the query string will contain instructions
  //  for all of the attributes that we require, basically we just
  //  pass it all on verbatim to get our treatment.
  if (XOS.Previewing) { return XOS.getQueryString(); }

  var result = [];

  if (_XOS_hasProperties( XOS.Characteristics )) {
    result.push( XOS.convertObjectToQueryString( XOS.Characteristics ) );
  }

  if (_XOS_hasProperties( XOS.Facts )) {
    result.push( '__XOS_FACTS__=YES' );
    result.push( XOS.convertObjectToQueryString( XOS.Facts ) );
  }

  return result.join( '&' );
};

XOS.convertObjectToQueryString = function( obj ) {
  var delimiter = "";
  var result    = "";
  for (var key in obj) {
    result = result + delimiter + encodeURIComponent( key );
    if (obj[key]) {
      result = result + "=" + encodeURIComponent( obj[key] );
    }

    delimiter = "&";
  }
  return result;
};

XOS.registerAttribute = function( name, dom_id /*optional - defaults to name*/ ) {
  XOS.Attributes[name] = dom_id || name;
};

XOS.ignoreAttribute = function( treatment, name ) {
  if (treatment[name]) {
    treatment[name].IGNORE = true;
  }
};

/**
 *  storeFact/storeOutcome -- storeOutcome is an alias for storeFact
 */
XOS.storeFact = function( key, value /* optional defaults to YES */ ){
   XOS.Facts[key] = value || "YES";
};

XOS.storeOutcome = function( key, value ) {
   XOS.storeFact( key, value );
};

/**
 *  storeCharacteristic/storeSegment -- storeSegment is an alias for storeCharacteristic
 */
XOS.storeCharacteristic = function( key, value ) {
   XOS.Characteristics[key] = value;
};

XOS.storeSegment = function( key, value ) {
   XOS.storeCharacteristic( key, value );
};

////////////////////////////////////////////////////////////////////////////////
//
//  Treatment Explorer - Requirements.
//
// callback function of sorts, called by data script when it is loaded
// as IE doesnt seem to support the script onload event
window.XOSDataStored_onload = function () {
  var registerObject = function( obj ) {
    for (var key in obj) {
      XOSDataPane.registerDataStored( key, obj[key] );
    }
  };
  registerObject( XOS.Characteristics );
  registerObject( XOS.Facts           );

  XOSDataPane.setContent();
  XOSDataPane.show();
};

XOS.showCapturedData = function( ) {
  if (XOS.ShowCapture && !PREVIEW_SCRIPT) {
    XOS.includeScript( "xos-preview.js", true );
  }
};

////////////////////////////////////////////////////////////////////////////////
//
//  User Utility Methods
//

//  Extracts the value for the given key from the query string.
//  If no value is found returns the defaultVal.
XOS.getQueryStringValue = function( val, defaultVal ) {
  if (!XOS.QueryArray) {
    XOS.QueryArray = [];
    var pos = null;
    var pairs = window.location.search.substring(1).split('&');
    for (var i = 0; i < pairs.length; i++) {
      pos = pairs[i].indexOf('=');
      if (pos > 0) {
        XOS.QueryArray[unescape(pairs[i].substring(0,pos))] = unescape(pairs[i].substring(pos+1));
      }
    }
  }
  return (XOS.QueryArray[val] || defaultVal);
};

XOS.getQueryString = function() {
   var result = document.location.search;
   return result.charAt(0) == '?' ? result.substring(1) : result;
};

XOS.getElement = function( name ) {
  var domId = XOS.Attributes[name] || name;
  return document.getElementById( domId );
};

XOS.hasAttribute = function( value, attr ) {
   //  typeof( null ) is "object" but null[attr] throws an error
   return value && (typeof(value) == "object") && (typeof(value[attr]) !== undefined);
};

XOS.getAttribute = function( value, attr ) {
  return _XOS_hasAttribute( value, attr ) ? value[attr] : undefined;
};


//Local variables:
//compile-command: "jslint xos-toolkit.js"
//eval: (folding-mode t)

//End:

