

/**
 * Kerpoof.js
 *
 * Copyright (C)2006 Apictura, LLC.
 *
 * Defines global constants, attributes and settings.
 */


// Global variables
var ie = document.all;
var nn6 = document.getElementById&&!document.all;
var opera = window.opera?true:false; // was: (navigator.appName=="Opera");
var safari = (navigator.userAgent.toLowerCase().indexOf("safari") >0 );
var lin = (/Linux/i.test(navigator.userAgent));

// Detect CSS1Compat (aka strict) mode vs backCompat (aka quirks) mode
var CSSStrict = /css\dcompat/i.test(document.compatMode);
var firefox = /firefox/i.test(navigator.userAgent);
var ff1_5 = /firefox.1\.5/i.test(navigator.userAgent);
var ff2_0 = /firefox.2\.0/i.test(navigator.userAgent);
var ie6 = /msie.6\./i.test(navigator.userAgent);


// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Code formerly in CbAttributes.js:


/**
 * CbAttributes.js
 *
 * Copyright (C)2006 Apictura, LLC.
 *
 * Defines global constants, attributes and settings.
 */


var CB_IMAGE_PATH = "/cb/images/cb/";
var CB_IMAGEJS_PATH = "/cb/dat/js/images/";
var CB_SCENEJS_PATH = "/cb/dat/js/scenes/";


  var CB_WS_IMAGEJS_PATH = "dat/js/images/";
  var CB_WS_SCENEJS_PATH = "dat/js/scenes/";


var CB_BORDER_PATH = "/cb/images/cb/borders/"; // relative path for border images
var CB_BUTTON_PATH = "/cb/images/cb/buttons/"; // relative path for button images
var CB_MAIN_BORDER = CB_BORDER_PATH + 'main_border_'; // relative path for main border page

var ENV = {};

ENV.creationTime = CurrentTime();

ENV.uid = [];

ENV.nn6 = nn6;
ENV.ie = ie;
ENV.opera = opera;
ENV.safari = safari;
ENV.lin = lin;

ENV.firefox = firefox;
ENV.ff1_5 = ff1_5;
ENV.ff2_0 = ff2_0;
ENV.ie6 = ie6;

ENV.getWindowDims = function() {
  if (nn6) {
    return {
      xmin: 0,
      ymin: 0,
      xmax: window.innerWidth,
      ymax: window.innerHeight,
      w: window.innerWidth,
      h: window.innerHeight
    };
  }
  if (ie) {
    return {
      xmin: 0,
      ymin: 0,
      xmax: document.body.offsetWidth - 4,
      ymax: document.body.offsetHeight - 4,
      w: document.body.offsetWidth - 4,
      h: document.body.offsetHeight - 4
    };
  }
  return {
    xmin: 0,
    ymin: 0,
    xmax: window.innerWidth,
    ymax: window.innerHeight,
    w: window.innerWidth,
    h: window.innerHeight
  };
}


ENV.setup_args = function() {
  var S = window.location.search;
  ENV.args = [];
  if (S.length>0) {
    S = S.substring(1,S.length);
    var A = S.split('&');
    for (var i in A) {
      var B = A[i].split('=');
      if (B.length == 1) {
        ENV.args['_'+B[0]] = true;
      }
      else {
        ENV.args['_'+B[0]] = unescape(B[1]);
      }
    }
  }






}

ENV.setup_args();

if (ENV.args._log) {
  aJaxPost('/live_log.php?ksid=' + ENV.args._log + '&action=clear');
  puts('**** logging ****');
}


if (ENV.args._hl) {
  langCode = ENV.args._hl;
}


ENV.godmode = true;



// Shows hidden answers in FAQs, etc
function show_answer(loc, ele)
{
  var i, div;
  var on_not_off=false;
  if (ele && ele.length
      && document.getElementById('ans-'+ele).style.height=='')
      on_not_off=true;

  div = document.getElementsByTagName("div");

  // Turn off all answers
  for (i=0; i<div.length; i++) {
    if(div[i].id.match(/^ans-/)) {
      div[i].style.height = '0px';
    }
  }

  // Turn on ans (only if it was off)
  if (ele && ele.length && !on_not_off) {
    document.getElementById('ans-'+ele).style.height = '';
    if (typeof pageTracker != "undefined") {
      pageTracker._trackPageview('/show_ans/'+loc+'/'+ele);
    }
  }
}

window.deeplink = function(u) {
  // TODO: get home_url into Kerpoof.js
  window.location.href='/#'+u;
}


// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Code formerly in CbUtil.js:


function readCookie(name)
{
  var nameEq = name + "=";
  var ca = document.cookie.split(';');
  for (var i=0; i < ca.length; ++i) {
    var c = ca[i];
    while (c.charAt(0)==' ') c = c.substring(1, c.length);
    if (c.indexOf(nameEq) == 0) return c.substring(nameEq.length, c.length);
  }
  return null;
}

// - - - - - - - - - - - - - - - - -


  var wsd;

  // Read wsd cookie and assign it to a global variables.
  wsd = readCookie('cbwscookie');
  if (wsd != null) wsd = '../' + wsd;


// - - - - - - - - - - - - - - - - -

// return the time since epoch
function CurrentTime() {
  return (new Date()).getTime();
}

function ElapsedTime(t0,t1) {
  t1 = t1 || CurrentTime();
  t0 = t0 || ENV.creationTime || t1;
  return Math.abs(t1-t0);
}

// - - - - - - - - - - - - - - - - -


var _loc_warnings = {};
var _loc_warnings_only = false;

function LW() { _loc_warnings_only = true; }
function AW() { _loc_warnings_only = false; }

var puts_message_count = 0;

function puts(s) {


  // static:
  if (!puts.backlog) puts.backlog = [];

  // Hook for localization warnings only
  if (_loc_warnings_only) {
    if (!(s.match(/LOC WARNING/))) {
      return;
    }
    if (_loc_warnings[s]) return; // Print message only once
    _loc_warnings[s] = true;
  }

  var add_caller_prefix = false;
  // note: s may be null because of the timeout
  // callback mechanism at the end of this function
  if (s) {
    if (add_caller_prefix) {
      var caller = 'unknown';
      try {caller = GetFunctionName(puts.caller);} catch(e) {caller='unknown';};
      s = '[' + caller + '] ' + s;
    }
    puts.backlog[puts.backlog.length] = s;
  }
  try {
    for (var i in puts.backlog)
    {
      if (puts.backlog[i])
        if (typeof println != 'undefined') {
          println(puts.backlog[i]); // println is defined in CbShell.js
        }
      puts.backlog[i] = null;
    }
    puts.backlog=[];
  }
  catch(e){};
  if (puts.backlog.length>0) {
     // attempt to flush the backlog again in 1 second
    setTimeout("puts()",1000);
  }



  if (window.ENV && ENV.args._log) {
    aJaxPost('/live_log.php?ksid=' + ENV.args._log +
             '&action=add&mnum=' + (puts_message_count++) +
             '&txt=' + escape(s));
  }



  if (s && window.console) {
    eval("console."+"log("+"s"+")");
  }

}



var EventLog = {};
EventLog.t0 = CurrentTime();
EventLog.event = [];
EventLog.last_update = 0;

EventLog.add = function(s, level) {
  level = level || "DEBUG";
  var t = ElapsedTime(EventLog.t0);
  var event = {
    level: level,
    t: t,
    s: s,
    toString: EventLog.formatter
  }
  EventLog.event.push(event);
}

EventLog.report = function(s) { EventLog.add(s,"REPORT"); }


EventLog.formatter = function() {
  var zp = '';
  for (var i=10;i<=1e7;i*=10) {if (this.t<i) zp += '0';} // zero padding
  return (zp + this.t + ': [' + this.level + '] ' + this.s)
}

EventLog.getReport = function(useTEpoch) {
  var prev = EventLog.last_update;
  var next = EventLog.event.length;
  EventLog.last_update = next;
  var update = [];
  var t0 = useTEpoch ? EventLog.t0 : 0;
  for (var i=prev; i<next; i++) {
    var e = EventLog.event[i];
    if (e.level == "REPORT") {
      update.push([t0 + e.t, e.s])
    }
  }
  return update;
}

EventLog.dump = function() {
  for (var i in EventLog.event) {
    puts('> ' + EventLog.event[i].toString());
  }
}


Function.prototype.method = function(name, func) {
    this.prototype[name] = func;
    return this;
};

Function.method('inherits', function(parent) {
    var d = 0, p = (this.prototype = new parent());
    this.method('uber', function(name) {
        var f, r, t = d, v = parent.prototype;
        if (t) {
            while (t) {
                v = v.constructor.prototype;
                t -= 1;
            }
            f = v[name];
        } else {
            f = p[name];
            if (f == this[name]) {
                f = v[name];
            }
        }
        d += 1;
        r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
        d -= 1;
        return r;
    });
    return this;
});

Function.method('inherit_swiss', function(parent) {
    for (var i = 1; i < arguments.length; i += 1) {
        var name = arguments[i];
        this.prototype[name] = parent.prototype[name];
    }
    return this;
});


function isAlien(a) {
   return isObject(a) && typeof a.constructor != 'function';
}

function isArray(a) {
    return isObject(a) && a.constructor == Array;
}

function isBoolean(a) {
    return typeof a == 'boolean';
}

function isEmpty(o) {
    var i, v;
    if (isObject(o)) {
        for (i in o) {
            v = o[i];
            if (isUndefined(v) && isFunction(v)) {
                return false;
            }
        }
    }
    return true;
}

function isFunction(a) {
    return typeof a == 'function';
}

function isNull(a) {
    return typeof a == 'object' && !a;
}

function isNumber(a) {
    return typeof a == 'number' && isFinite(a);
}

function isObject(a) {
    return (a && typeof a == 'object') || isFunction(a);
}

function isString(a) {
    return typeof a == 'string';
}

function isUndefined(a) {
    return typeof a == 'undefined';
}

String.
    method('quote', function() {
        var c, i, l = this.length, o = '"';
        for (i = 0; i < l; i += 1) {
            c = this.charAt(i);
            if (c >= ' ') {
                if (c == '\\' || c == '"') {
                    o += '\\';
                }
                o += c;
            } else {
                switch (c) {
                case '\b':
                    o += '\\b';
                    break;
                case '\f':
                    o += '\\f';
                    break;
                case '\n':
                    o += '\\n';
                    break;
                case '\r':
                    o += '\\r';
                    break;
                case '\t':
                    o += '\\t';
                    break;
                default:
                    c = c.charCodeAt();
                    o += '\\u00' + Math.floor(c / 16).toString(16) +
                        (c % 16).toString(16);
                }
            }
        }
        return o + '"';
    }).
    method('trim', function() {
        return this.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1");
    });

/*
 * Take the value in A (or values if A is an array) and limit them
 * to p digits of precision
 * @param A the value (or array of values) to limit
 * @param p n digits of precision (defaults to 4)
 * @return limited value or array of values
 */

Math.limitPrecision = function(A,p) {
  p = p || 4;
  var B;
  if(isArray(A)) {
    B = [];
    for (var i in A) {
      B[i] = Math.limitPrecision(A[i],p);
    }
  }
  else {
    if (isNaN(A) || A==Infinity) return A;
    if (A < 0) {
      return -Math.limitPrecision(-A,p);
    }
    // the precision is limited in decimal through string
    // processing (slow but robust and always correct)
    var S = A.toString();
    var E = S.split("e"); // look for scientific notation
    var e = (E[1]) ? Math.floor(E[1]) : 0;
    // e is the exponent
    var D = E[0].split("."); // split on the decimal
    // if multi-digits to right of decimal, adjust e:
    e += D[0].length-1;
    // collect all of the digits (right or left of decimal)
    // into a string, d:
    var d = D[0];
    if (D[1]) d += D[1];
    // round to the correct number of significant digits
    // by constructing a string of value in scientific notation:
    var x = Math.round(d + 'e' + (p - d.length));
    // scale with exponent e by constucting a string in scientific
    // notation and evaling it:
    B = eval('(' + x + 'e' + (e+1-p) + ')');
  }
  return B;


  // UNUSED CODE: - - - - - - - - - - - - - - - - - - - - - - - -
  // Note: the following should compute a value with p digits past
  // the decimal point, but it fails from time to time due to rounding
  // errors, returning, e.g. 3.1400000000000001:

  if (!Math.limitPrecision_LUT) {
    Math.limitPrecision_LUT = [];
    for (var i=-15; i<15; i++) {
      Math.limitPrecision_LUT[i] =
        eval('(1e' + i + ')');
    puts('Math.limitPrecision_LUT['+i+'] = '+
         Math.limitPrecision_LUT[i]);
    }
  }
  p = p || 2;
  var B;
  if(isArray(A)) {
    B = [];
    for (var i in A) {
      B[i] = Math.limitPrecision(A[i],p);
    }
  }
  else {
    B = Math.limitPrecision_LUT[-p] *
      Math.round(A * Math.limitPrecision_LUT[p]);
  }
  return B;
  // END, UNUSED CODE: - - - - - - - - - - - - - - - - - - - - - -


}

function stripLeadingUnderscores(obj,recurse)
{
  var stripped_obj = {};
  var stripped_attr;
  for (var attr in obj) {
    if (attr.match(/^_/)) {
      stripped_attr = attr.substr(1,attr.length-1);
    }
    else {
      stripped_attr = attr;
    }
    if (recurse && isObject(obj[attr])) {
      stripped_obj[stripped_attr] = stripLeadingUnderscores(obj[attr],true);
    }
    else {
      stripped_obj[stripped_attr] = obj[attr];
    }
  }

  return stripped_obj;
}

function applyStyle(O,style) {
  for (k in style) {
     O.style[k] = style[k];
  }
}

function GetFunctionName(fn)
{
  var m = fn.toString().match(/^\s*function\s+([^\s\(]+)/);
  return m ? m[1] : "";
}

function GetMasterImage(index, onload, ttl, ontimeout)
{

        var path = (index < 1000000) ?
          wsd + CB_WS_IMAGEJS_PATH + "kpimg" + index + ".js":
          CB_IMAGEJS_PATH + "kpimg" + index + ".js";




  EventLog.add('Issued get-master-image(' + index + ')');
  _load_object(
    path,
    function(o)
    {
      EventLog.add('get-master-image(' + index + ') callback');
      onload(o);
    }
    ,ttl, ontimeout
  );
}

var flashElement = null;

var AS = {};

AS.call = function(varargin)
{
  if (!flashElement) {
    flashElement = (ie) ? window["kerpoof_application"] : document["kerpoof_application"];
  }

  if (arguments.length<1) {
    puts("AS.call() requires at least one argument.");
    return null;
  }

  var f = arguments[0];
  var A = [];
    for (var i=1; i<arguments.length; i++) {A.push(arguments[i]);}

  var rval = flashElement["AS_export_request"]("call", f,
                                               JSON.arrayToJSON(A,true));

  puts ('rval = ' + rval);

  return rval;
}

AS.get = function(f)
{
  if (!flashElement) {
    flashElement = (ie) ? window["kerpoof_application"] : document["kerpoof_application"];
  }

  if (arguments.length!=1) {
    puts("AS.get() requires one argument.");
    return null;
  }

  var rval = flashElement["AS_export_request"]("get", f, "");
  puts ('rval = ' + rval);
  rval = JSON.assignFrom(rval);
  return rval;
}

AS.set = function(f,val)
{
  if (!flashElement) {
    flashElement = (ie) ? window["kerpoof_application"] : document["kerpoof_application"];
  }

  if (arguments.length!=2) {
    puts("AS.set() requires two arguments.");
    return null;
  }

  flashElement["AS_export_request"]("set", f, JSON.arrayToJSON([val]));
  return;
}


window.onbeforeunload = function() {
  try {
    AS.call('on_before_unload')
  } catch (e) {}
};

// Debugging shortcuts

function DM() {AS.call('mem_watch');}
function DL() {AS.call('listener_watch');}

function _Flash_Async_Call(f, serialized_args, cbindex)
{
  // puts('_Flash_Async_Call entered, f = ' + f);
  try{
    if (!flashElement) {
      flashElement = (ie) ? window["kerpoof_application"] : document["kerpoof_application"];
    }

    var cbhook = function(args) {
      try{
        // puts('_Flash_Async_Call::cbhook entered, f = ' +f);
        // NOTE: Do NOT cache flashElement["ajax_handler"] into a
        // javascript variable -- It can crash FireFox!
        var json = JSON.arrayToJSON(arguments,true);
        flashElement["ajax_handler"](cbindex, json);
      } catch (e) {
        puts('_Flash_Async_Call::cbhook caught: ' + e.message);
        puts(e);
      }
    }


    // deserialize the argument list for f
    // note: the following construction fails in IE, so we resort to
    // assembling A via a loop:
//     var A = Array.concat(cbhook, JSON.assignFrom(serialized_args));
    var deser_args = JSON.assignFrom(serialized_args);
    var A = [cbhook];
    for (var i=0; i<deser_args.length; i++) {
      A.push(deser_args[i]);
    }

    // lookup the function to call (f is a string):
    var js_function = window[f];

    if (js_function == null) {
      throw (Error("There is no JavaScript function '" + f + "'"));
    }
    js_function.apply(null,A);
  } catch (e) {
    puts(e);
    puts('_Flash_Async_Call caught: ' + e.message);
  }
}


function _getLangCode() {
  return langCode;
}

function _setLangCode(lang) {
  langCode = lang;
}


function gets(s, lang) {
  lang = lang || _getLangCode();
  if (lang=='en' || !KpLangTable || !KpLangTable[lang]) {
    return s;
  }
  if (!KpLangTable[lang][s]) {
    return s;
  }
  return KpLangTable[lang][s];
}

function _Flash_GetENV() {
  return (JSON.objectToJSON(ENV));
}

function _Flash_GetLangTable() {
  return (JSON.objectToJSON(KpLangTable));
}

////function _Flash_GetNumScenes(callback, uid)
////{
////  //_ImageDAO._genNumScenes(uid, function(id) {callback(id)});
////  _ImageDAO._genId(function(a) {callback(a[1])});
////}


function _Flash_Track(push_google, a, t, data)
{
  // TODO: push to database
  if (push_google) {
    // Google Analytics push:
    pageTracker._trackPageview(a);
  }
}

var flashIframe;

function _Flash_addIFrame(url, style)
{
  puts('addIFrame: ' + url);
  flashIframe = document.createElement("iframe");
  if (ie) { flashIframe.frameBorder = "no"; }
  flashIframe.id = 'altframe';
  _Flash_updateIFrame(url,style);

  document.body.appendChild(flashIframe);
}

function _Flash_updateIFrame(url, style)
{
  puts('updateIFrame: ' + url);
  if (url) flashIframe.src = url;
  if (style) applyStyle(flashIframe,style);
}


function _Flash_delIFrame()
{
  puts('delIFrame');
  if (flashIframe) document.body.removeChild(flashIframe);
  flashIframe = null;
}


function _Flash_LimitPrecision(x, p)
{
  return Math.limitPrecision(x,p);
}

function _Flash_GetMasterImage(onload, index)
{
  GetMasterImage(index,
      function(o) {
        onload(stripLeadingUnderscores(o));
      });
}

function _Flash_GetMasterImageArray(onload, indices)
{
  GetMasterImageArray(
    indices,
    function(o_array) {
      for (ii in o_array) {
        o_array[ii] = stripLeadingUnderscores(o_array[ii]);
      }
      onload(o_array);
    });
}


function _Flash_GetImageAssoc(onload, index, sceneIndex)
{
  // -- code copied from GetImageAssoc()

  var path = (sceneIndex < 1000000) ?
    wsd + CB_WS_SCENEJS_PATH + "scene" + sceneIndex + "/" +
        "kpimgassoc" + index + ".js" :
        CB_SCENEJS_PATH + "scene" + sceneIndex + "/" +
    "kpimgassoc" + index + ".js";





  EventLog.add('Issued get-image-assoc(' + index + ')');
  // -- END code copied from GetImageAssoc()

  _load_object(
    path,
        function(o)
        {
          EventLog.add('get-image_assoc(' + index + ') callback');
          onload(o);
        }
  );
}

function _Flash_GetBaseScene(onload, index)
{

  var path = (index < 1000000) ?
          wsd + CB_WS_SCENEJS_PATH + "kpscene" + index + ".js" :
          CB_SCENEJS_PATH + "kpscene" + index + ".js";




  _load_object(
    path,
    onload);
}

var SCENE_CACHE = {};

function _Flash_GetSceneData(onload, scene)
{
  puts('GSD for scene: ' + scene);
  if (isNaN(parseInt(scene))) {
    ReconstituteScene(scene, onload, true);
  }
  else {
    GetBaseScene(scene, onload, true);
  }
}

//// Deprecated DWR save mechanism
////function _Flash_SaveSceneData(done, sceneName, title, app_type, num_scenes, serial, parentName, save_type, KSID, del_scene)
////{
////  var S = JSON.objectToJSON(serial);
////  SCENE_CACHE[sceneName] = S;
////  _ImageDAO._writeAndDelScene(S, sceneName, title, app_type,
////                              num_scenes, parentName, save_type,
////                              KSID, [del_scene],
////                              function() {done();}
////  );
////}

function _Flash_GetBaseImages(onload, sceneIndex)
{
    EventLog.add('Issued get-sceneobjset');

        var path = (sceneIndex < 1000000) ?
          wsd + CB_WS_SCENEJS_PATH + "kpsceneobjset" +
          sceneIndex + ".js" :
          CB_SCENEJS_PATH + "kpsceneobjset" +
          sceneIndex + ".js";






  _load_object(
    path,
    onload);
}



/**
 * Loads the category associations of this (working set) scene.
 */
function _Flash_GetSceneAssoc(onload, sceneIndex)
{
  _ImageDAO._getSceneAssoc(
    function(s) {
      var m = JSON.assignFrom(s);
      puts(m);
      var A = {};
      A.weightedCategories = m['categories'];
      A.advertisersByCategory = m['advertisers'];
      onload(A);
    }, sceneIndex, wsd);
}


/**
 * Saves the category associations of this (working set) scene.
 */
function _Flash_SaveSceneAssoc(A, sceneIndex)
{
  var assoc = {};
  assoc['categories'] = A.weightedCategories;
  assoc['advertisers'] = A.advertisersByCategory;
  var assocStr = JSON.objectToJSON(assoc);
  _ImageDAO._saveSceneAssoc(function(s) {puts(s);},
    assocStr, sceneIndex, wsd);
}



function GetMasterImageArray(A, onload, ttl, ontimeout, o_array) {
  if (A.length==0) {
    onload([]);
    return;
  }

  if (!o_array) {
    o_array = [];
    // A will be destroyed in processing of GetMasterImage
    var A_copy = [];
    for (var i in A) {A_copy[i] = A[i];}
    var A2 = A_copy;
  }
  else {
    var A2 = A;
  }

  if (A2.length==1) {
    GetMasterImage(A2[0],
                   function(o){
                     o_array.push(o);
                     onload(o_array);
                   },ttl,ontimeout);
  }
  else {
    var a = A2.shift();
    GetMasterImage(a,
                   function(o){
                     o_array.push(o);
                     GetMasterImageArray(A2,onload,ttl,ontimeout,o_array);
                   },ttl,ontimeout);
  }
}

function GetImageAssoc(mi, sceneIndex, ttl, ontimeout)
{

  var path = (sceneIndex < 1000000) ?
    wsd + CB_WS_SCENEJS_PATH + "scene" + sceneIndex + "/" +
        "kpimgassoc" + mi._masterIndex + ".js" :
        CB_SCENEJS_PATH + "scene" + sceneIndex + "/" +
    "kpimgassoc" + mi._masterIndex + ".js";





  EventLog.add('Issued get-image-assoc(' + mi._masterIndex + ')');
  _load_object(
    path,
        function(o)
        {
          EventLog.add('get-image_assoc(' + mi._masterIndex + ') callback');
          var L = new CbWeightedList();
          for (var i in o) {
            var a = o[i];
            L.add(a[0], a[1], a[2]?a[2]:null);
          }
          mi._imageAssoc = L;
        }
        ,ttl, ontimeout
  );
}

/**
 * Fetches a base scene by evaluating serialized JavaScript
 * code residing on the server.
 */

function GetBaseScene(index, onload, fromFlash)
{

  var path = (index < 1000000) ?
          wsd + CB_WS_SCENEJS_PATH + "kpscene" + index + ".js" :
          CB_SCENEJS_PATH + "kpscene" + index + ".js";




  if (fromFlash) {
    _load_object(
      path,
      function(o) {
        try {
          onload(o);
        }
        catch(e) {puts('get base threw: ' + e);};
      }
    );
    return;
  }

  _load_object(
    path,
    function(o) {
      try {
        onload(
          o,
          function() {
            page.canvas.source_scene = {
              sceneName: ""+index,
              userScene: false
            };
          }
        );
      } catch(e) {puts('get base threw: ' + e);};
    }
  );
}

/**
 * Reconstitutes a scene by building the scene from a serialized
 * string returned from an AJAX call.
 */

function ReconstituteScene(sceneName, onload, fromFlash)
{
  try {
    if (SCENE_CACHE[sceneName]) {
      var s = SCENE_CACHE[sceneName];
      // TODO: combine cut-and-pasted code from funciton closure below
      // Deserialize the JSON scene.
      var scene = JSON.assignFrom(s);

      if (!fromFlash) {
        // Grab the scene index.
        for (var i = 0; i < scene.length; ++i) {
          var el = scene[i];
          if (el._tp == 0) {
            try {
              _bootstrap.sceneIndex = el._ix;
            } catch(e) {};
          }
        }
      }

      // 100 ms timeout for Cache hit callback
      // -- gets around Flash bug (cant call back directly to
      // external interface)
      setTimeout(function()
             {
               if (fromFlash) {
            puts('-- CACHE --');
            puts('-- s --');
            puts(s);
            puts('-- scene --');
            puts(scene);
                 onload(scene);
               }
               else {
                 onload(
                   scene,
                   function() {
                     page.canvas.source_scene = {
                       sceneName: sceneName,
                       userScene: true
                     };
                   }
                 );
               }
             }
             ,100);
      return;
    }

  _ImageDAO._getJsonScene(
    sceneName,
    function(s) {
      try {
        if (s) {
          // Deserialize the JSON scene.
          var scene = JSON.assignFrom(s);

          if (!fromFlash) {
            // Grab the scene index.
            for (var i = 0; i < scene.length; ++i) {
              var el = scene[i];
              if (el._tp == 0) {
                try {
                  _bootstrap.sceneIndex = el._ix;
                } catch(e) {};
              }
            }
          }
          if (fromFlash) {
            puts('-- s --');
            puts(s);
            puts('-- scene --');
            puts(scene);
            onload(scene);
          }
          else {
            onload(
              scene,
              function() {
                page.canvas.source_scene = {
                  sceneName: sceneName,
                  userScene: true
                };
              }
            );
          }
        }
        else {
          // Fallback in case sceneName is invalid.
          ENV.args._scene = "1000000";
          try {
            _bootstrap.sceneIndex = 1000000;
          } catch(e) {};
          GetBaseScene(100000, onload);
        }
      } catch(e) {
        puts('recon threw: ' + e);
        if (page && page.canvas && page.canvas.source_scene) {
          page.canvas.source_scene = {
            sceneName: "",
            userScene: false
          }
        }
      };
    }
  );
  } catch (e) {puts('recon threw: ' + e);}

}



function SparseMap() {
  this.map = new Object();
}

SparseMap.prototype.add = function(k,o)
{
  this.map[k] = o;
}

SparseMap.prototype.remove = function( k )
{
  delete this.map[k];
}

SparseMap.prototype.get = function( k )
{
  return (k!==null && this.map[k]) ?
    this.map[k] : null;
}

SparseMap.prototype.first = function( )
{
  return this.get( this.nextKey( ) );
}

SparseMap.prototype.next = function( k )
{
  return this.get( this.nextKey(k) );
}

SparseMap.prototype.nextKey = function( k )
{
  for (var i in this.map) {
    if (!k) return i;
    if (k==i) k=null; /*tricky*/
  }
  return null;
}


// An AJANX method to asynchronously load some static javascript
// defined object.
//
// Sample Usage:
//
//   _load_object(
//     './g1031.js',
//     function(s) {
//       alert('got: "' + s.greeting + '" from object #' + s.id);
//     }
//   );
//
//  Or
//
//   _load_object(
//     './g1031.js',
//     function(s) {
//       alert('got: "' + s.greeting + '" from object #' + s.id);
//     },
//     1000, /* In 1000ms, timeout to: */
//     function(s) {
//       alert('timeout for object g1031');
//     }
//   );
//
// Where file ./g1031.js might contain:
//  -----------------------------------
// | // This file defines an anonymous javascript object
// | // that contains three fields: "greeting", "recurs", and "id".
// | // The anonymous object is then passed as an argument to
// | // the function _load_object.g1031, which has been previously defined
// | // (see the _load_object function). In general, the function
// | // called must always match the name of the loaded javascript
// | // object definiton file (in this case g1031), prepended with
// | // _load_object.
// |
// | _load_object.g1031(
// |   {
// |     greeting: 'hello world',
// |     recurs:  {a: 1, b: [1,2,{a: 1, b:[]}]},
// |     id : 1031    // no trailing comma
// |                  // -- trailing commas break the IE interpreter
// |   }
// | );
//  -----------------------------------

var _load_object_MUTEX_ID = 0;

function _load_object(src,onload,ttl,ontimeout)
{
  var PC = src.split('/'); // split out the path components
  var FC = PC[PC.length-1].split('.'); // and the file components
  var name = FC[0]; // if src=bar/foo123.js then name=foo123

  var script = null;

  var head = document.getElementsByTagName('head')[0];

  var logEvents = false;

  new Mutex(
    {
      id: ++_load_object_MUTEX_ID,
      exec: function() {
        if (logEvents) EventLog.add('enter mutex: _load_object ' + src);

        // Establish the callback pathway:

        // The loaded script will create an object and then
        // call a function with the object as an argument
        // This function will be available from the global name
        // space as a member of the object called "_load_object"
        // -- i.e. the function defined here (note: javascript
        // functions are objects).  Thus when we load an object named
        // foo123, defined in foo123.js, the callback function goes
        // through _load_object.foo123.

        if (!_load_object.callback_queue) _load_object.callback_queue=[];

        if (!_load_object.callback_queue[name]) {
          _load_object.callback_queue[name] = [0];
          // first element is a ref count
          // the rest of the elements will be callback functions

          // this is a new object request, so we need to load the script
          // Setup the script element but do not load it into the DOM
          // just yet:
          script = document.createElement('script');
          script.type = 'text/javascript';
          script.src = src;
          script.defer = false;
        }

        if (logEvents) EventLog.add('Scheduling load_object for ' + name);

        var callback_obj =
          {onload: function(s) {
             if (onload) onload(s);
           },
           ontimeout: ontimeout
          };

        if (ttl) {
          setTimeout(
            function() {
              new Mutex(
                {
                  id: ++_load_object_MUTEX_ID,
                  exec: function() {
                    if (logEvents) EventLog.add('enter mutex: ontimeout ' + name);
                    if (callback_obj.ontimeout) {
                      try {callback_obj.ontimeout();}
                      catch (e) {}
                      callback_obj.onload = null;
                      // decrease ref count
                      --_load_object.callback_queue[name][0];
                      if (!_load_object.callback_queue[name][0]) {
                        if (logEvents) EventLog.add('Finished _load_object ' + name);
                        // remove the [name] entry from the _load_object
                        delete _load_object[name];
                        delete _load_object.callback_queue[name];
                      }
                      try {head.removeChild(script);} catch (e) {}
                    }
                    else {
                      if (logEvents) EventLog.add(' -- ignoring timeout, object already loaded');
                    }
                    if (logEvents) EventLog.add('exit mutex: ontimeout ' + name);
                  }
                }); // end of ontimeout mutex
            },ttl)
        }

        var q = _load_object.callback_queue[name];

        ++q[0]; // increase ref count

        q[q.length] = callback_obj;

        _load_object[name] = _load_object[name] ||
          function(s) {
            new Mutex(
              {
                id: ++_load_object_MUTEX_ID,
                exec: function() {
                  if (logEvents) EventLog.add('enter mutex: onload ' + name);
                  for (var i in _load_object.callback_queue[name]) {
                    if (i!=0) {
                      if (logEvents) EventLog.add('Exec onload for ' + name);
                      if (_load_object.callback_queue[name][i].onload) {
                        try {_load_object.callback_queue[name][i].onload(s);}
                        catch(e) {if (logEvents) EventLog.add('_load_object.'+name+' onload exception.')}
                      }
                      _load_object.callback_queue[name][i].ontimeout = null;
                      delete _load_object.callback_queue[name][i];
                    }
                  }
                  --_load_object.callback_queue[name][0]; // decrease ref count
                  if (!_load_object.callback_queue[name][0]) {
                    // remove the [name] entry from the _load_object
                    if (logEvents) EventLog.add('Finished _load_object ' + name);
                    delete _load_object[name];
                    delete _load_object.callback_queue[name];
                  }
                  try {head.removeChild(script);} catch (e) {}
                  if (logEvents) EventLog.add('exit mutex: onload ' + name);
                }
              }); // end of onload Mutex
          } // end of callback function
        // Load the script into the DOM;
        if (script) head.appendChild(script);
        if (logEvents) EventLog.add('exit mutex: _load_object ' + name);
      } // end of Mutex exec
    }); // end of Mutex for _load_object
}


function Mutex( cmdObject, methodName ) {
  methodName = methodName || "exec";
  // define static variable and method
  if (!Mutex.Wait) Mutex.Wait = new SparseMap();
  Mutex.SLICE = function( cmdID, startID ) {
    Mutex.Wait.get(cmdID).attempt( Mutex.Wait.get(startID) );
  }
  // define instance method
  this.attempt = function( start ) {
    for (var j=start; j; j=Mutex.Wait.next(j.c.id)) {
      if (j.enter ||
          (j.number &&
           (j.number < this.number ||
            (j.number == this.number && j.c.id < this.c.id) ) ) )
        return setTimeout("Mutex.SLICE("+this.c.id+","+j.c.id+")",5);
    }
    this.c[ this.methodID ](); //run with exclusive access
    this.number = 0; //release exclusive access
    Mutex.Wait.remove( this.c.id );
    return false;
  }
  // constructor logic
  this.c = cmdObject;
  this.methodID = methodName;
  Mutex.Wait.add( this.c.id, this ); //enter and number are "false"
  this.enter = true;
  this.number = (new Date()).getTime();
  this.enter = false;
  this.attempt( Mutex.Wait.first() );
}


function OnContentLoad(doc,f)
{
  if (ie) {
    doc.onreadystatechange =
      function() {
        if (doc.readyState == "complete") {
          f();
        }
      }
  }
  else {
    doc.addEventListener("DOMContentLoaded",f,null);
  }
}


function dump_stack()
{
  if (nn6 && window.console) {
    eval('console.' + 'trace()');
    return;
  }
  try {
    throw new Error("Stack Dump");
  }
  catch (e) {
    puts();
    puts(e.stack);
    puts();
  }
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// JSON code

var JSON = {}; // namespace

JSON.quote_all_identifiers = false;

(function () {
     var m = {
       '\b': '\\b',
       '\t': '\\t',
       '\n': '\\n',
       '\f': '\\f',
       '\r': '\\r',
       '"' : '\\"',
       '\\': '\\\\'
     };
     var s = {
       array: function(x) {
         var a = ['['], b, f, i, l = x.length, v;
         for (i = 0; i < l; i += 1) {
           v = x[i];
           f = s[typeof v];
           if (f) {
             v = f(v);
             if (typeof v == 'string') {
               if (b) {
                 a[a.length] = ',';
               }
               a[a.length] = v;
               b = true;
             }
           }
         }
         a[a.length] = ']';
         return a.join('');
       },
       'boolean': function(x) {
         return String(x);
       },
       'null': function(x) {
         return "null";
       },
       number: function(x) {
         return isFinite(x) ? String(x) : 'null';
       },
       object: function(x) {
         if (x) {
           if (x instanceof Array) {
             return s.array(x);
           }
           var a = ['{'], b, f, i, v;
           for (i in x) {
             v = x[i];
             f = s[typeof v];
             if (f) {
               v = f(v);
               if (typeof v == 'string') {
                 if (b) {
                   a[a.length] = ',';
                 }
                 if (!JSON.quote_all_identifiers &&
                     /^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test(i))
                   a.push(i /*s.string(i)*/, ':', v);
                 else
                   a.push(s.string(i), ':', v);
                 b = true;
               }
             }
           }
           a[a.length] = '}';
           return a.join('');
         }
         return 'null';
       },
       string: function(x) {
         if (/[\"\\\x00-\x1f]/.test(x))
         {
           x = x.replace(
               /([\x00-\x1f\\\"])/g,
             function(a, b) {
               var c = m[b];
               if (c) {
                 return c;
               }
               c = b.charCodeAt();
               return '\\u00' +
                 Math.floor(c / 16).toString(16) +
                 (c % 16).toString(16);
             });
     }
     return '"' + x + '"';
   }
  };

    JSON.objectToJSON = function(o,escpae) {
        JSON.quote_all_identifiers = escape ? true : false;
        return s.object(o);
    };

    JSON.arrayToJSON = function(o, escape) {
        JSON.quote_all_identifiers = escape ? true : false;
        return s.array(o);
    };
})();

JSON.safelyAssignFrom = function(s) {
    try {
      // look for malicious code:
      // first look to see if string contains parens or equal sign
      // (potentially malicious):
      if (/[()=]/.test(s)) {
        // TODO: parse s to see if has parens or equal sign
        // outside of a strign literal [indicating a function
        // call or variable assignment, hence malicious].
        // For now just look for the characters anywhere (fine
        // until we add user text to the JSON).

        // TODO: report malicious event to server.
        return false;
      }
      return (eval('(' + s + ')'));
    } catch (e) {
        return false;
    }
};

JSON.assignFrom = function(s) {
    try {
      return (eval('(' + s + ')'));
    } catch (e) {
        return false;
    }
};


// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

/**
 * Launch new window with location set to URL.
 *
 * All arguments except url are optional.
 * Set any argument you want defalted to null.
 *  bars:  0-->none, 1-->menubar only, 2-->all [default]
 *  w: width  in pixel (integer) or % of window (string), e.g. "75%"
 *  h: height in pixel (integer) or % of window (string), e.g. "75%"
 *  aspect: desired ratio of w to h
 *
 * Can set all (w,h,aspect) = null: new window will be full screen
 *   TODO: better default would be to launch default sized window
 * Can specify only aspect, (w,h) = null, to get maximally sized window
 *   with approx. that aspect ratio in available window space.
 * Can specify only aspect plus either w or h.
 * Can specify w and height and leave aspect null.
 *
 */

function cbNewWindow(url,bars,w,h,aspect)
{
  // default:
  if (bars===null) bars=2;


   bars=2;


  var options = "";

  var add_option = function(key,value) {
    if (options.length > 0) {
      options += ","
    }
    if (value==null) {
      options += key + "=1"
    }
    else if (value==true) {
      options += key + "=1"
    }
    else if (value==false) {
      options += key + "=0"
    }
    else {
      options += key + "=" + value;
    }
  }

//  add_option("menubar");
  add_option("resizable");

  add_option("fullscreen",false);

  switch(bars) {
  case 0:
    add_option("menubar",false);
    add_option("toolbar",false);
    add_option("location",false);
    add_option("scrollbars",true);
    add_option("status",false);
    break;
  case 1:
    add_option("menubar",true);
    add_option("toolbar",ie?true:false);
    add_option("location",false);
    add_option("scrollbars",true);
    add_option("status",false);
    break;
  case 3:
    add_option("menubar",false);
    add_option("toolbar",ie?true:false);
    add_option("location",false);
    add_option("scrollbars",true);
    add_option("status",false);
    break;
  default: // God/2
    add_option("menubar",true);
    add_option("toolbar",true);
    add_option("location",true);
    add_option("scrollbars",true);
    add_option("status",true);
    break;
  }

  var maxh = window.screen.availHeight;
  var maxw = window.screen.availWidth;

  if (h!==null) h = Math.min(h,maxh);
  if (w!==null) w = Math.min(w,maxw);

  if (isString(h)) {
    // interpret as percentage of window height;
    // trailing characters will be ignored, so "90%" --> 0.9*wh
    h = parseFloat(h)/100 * maxh;
  }

  if (isString(w)) {
    w = parseFloat(w)/100 * maxw;
  }

  var vwaste = 50;

  if (aspect) {
    if (h!==null) {
      w = aspect * (h-vwaste);
    }
    else if (w!==null) {
      h = w / aspect + vwaste;
    }
    else {
      if (aspect < maxw/(maxh-vwaste)) {
        h = maxh;
        w = aspect * (h-vwaste);
      }
      else {
        w = maxw;
        h = w / aspect + vwaste;
      }
    }
  }

  w = Math.floor(w);
  h = Math.floor(h);

  add_option("height",h);
  add_option("width",w);

  // puts('Options: ' + options);

  var newwindow = window.open(url, "_blank"+Math.floor(Math.random()*50000), options);
  return newwindow;
}

function _newWin(url,bars,h,w,aspect)
{
  var newwindow = cbNewWindow(url,bars,h,w,aspect);
  puts ('newwindow');
  puts (newwindow!=null);
  return (newwindow!=null);
}

/**
 * Writes the scene, then changes the page window location.
 */

function cbChangeWindowLocation(newUrl, KSID, moveParent)
{
  if (KSID==null) { KSID=''; }
  ENV.enable_autosave = false;
  ENV.write_on_unload = false;

  SHIELD_ON();

  var canvas = page.canvas;

  var sceneName = canvas.getLabel();

  var hideList = [];

  if (canvas.last_autosave && canvas.last_autosave.sceneName &&
      canvas.last_autosave.sceneName.length>0)
  {
    hideList.push(canvas.last_autosave.sceneName);
  }

  if (canvas.source_scene && canvas.source_scene.userScene &&
      canvas.source_scene.sceneName &&
      canvas.source_scene.sceneName.length>0)
  {
    hideList.push(canvas.source_scene.sceneName);
  }

  newUrl = newUrl.replace(/INS_SCENE_NAME/, sceneName);

  var redirect = function() {
    // redirect browser to newUrl after 1/4 second
    setTimeout(
      function() {

        // Try closing self (assuming there's a parent)
        if (SPONSORED) {
          // Simply redirect this window
          window.location = newUrl;
          return;
        } else {
          // Not sponsor: if there's a parent, redirect it and close me
          if (self) {
            if (self.opener && moveParent) {
              self.opener.location = newUrl;
            }
            self.close();
          }
          window.close();
        }

        // If close doesn't work, fall back to redirecting this window
        setTimeout( function() { window.location = newUrl; }, 100 );

      },
      250
    );
  }

//   for (var i=1; i<hideList.length; i++) {
//     // puts('x h: ' + hideList[i]);
//     hideScene(hideList[i]);
//   }

  redirect();

// --- No longer saving on redirect ---
//  try {
//    var parentName = ENV.args._scene;
//
//    if (hideList.length>0) {
//      puts('x w/h, h: ' + hideList[0]);
//      //puts('sn: ' + sceneName);
//      _ImageDAO._writeAndHideScene(
//        page.canvas.getSerialization(),
//        sceneName, parentName, 0, KSID, hideList,
//        redirect);
//    }
//    else {
//      puts('x w sn: ' + sceneName);
//      _ImageDAO._writeScene(
//        page.canvas.getSerialization(),
//        sceneName, parentName, 0, KSID,
//        redirect);
//    }
//  }
//  catch (e) {
//    // Failed to write the scene
//    // The atomic hides should have been skipped
//    // TODO: figure out what else to try
//    redirect();
//  }
}


// Callback argument for compatibility with Flash_Async_Call
function _ahb(callback, query)
{
  aJaxHeartBeat(query);
}

// Make an aJax request to keep a session up to date
function aJaxHeartBeat(query) {

  var objXMLHttp=null;
  if (window.XMLHttpRequest) {
    objXMLHttp = new XMLHttpRequest();
  } else if (window.ActiveXObject) {
    objXMLHttp = new ActiveXObject("Microsoft.XMLHTTP");
  }

  if (objXMLHttp) {
    objXMLHttp.open("GET", "keepalive?rnd="+Math.floor(Math.random()*1000000)+"&"+query, true);
    objXMLHttp.send(null);
  } else {
    // In case aJax isn't supported??
    // Not really necessary, as the whole app depends on aJax
    //-------------------------------------------------------
    // window.location="gallery?delscene="+file_name;
  }
}

// Make an aJax post
function aJaxPost(url, paramObj, callback, obj) {

  var params = '?rnd='+Math.floor(Math.random()*1000);
  for (var i in paramObj) {
    var val = paramObj[i];
    if ((typeof val == 'string' || typeof val == 'number') &&
        (typeof i == 'string' || typeof i == 'number')) {
//      if (first) {
//        params += "?"+escape(i)+"="+escape(val);
//        first=0;
//      } else {
        params += "&"+escape(i)+"="+escape(val);
//      }
    }
  }

  var objXMLHttp=null;
  if (window.XMLHttpRequest) {
    objXMLHttp = new XMLHttpRequest();
  } else if (window.ActiveXObject) {
    objXMLHttp = new ActiveXObject("Microsoft.XMLHTTP");
  }

  var handleAjax = function() {
    if (objXMLHttp.readyState == 4) {
      if (objXMLHttp.status == 200) {
        if (typeof callback=='function') {
          callback(objXMLHttp.responseText, obj);
        }
      } else {
        puts("AJAX Error: Server returned '"+objXMLHttp.status+"' for url '"+url+"'");
      }
    }
  }

  objXMLHttp.onreadystatechange = handleAjax;
  if (objXMLHttp) {
    objXMLHttp.open("POST", url, true);
    objXMLHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    objXMLHttp.setRequestHeader("Content-length", params.length);
    objXMLHttp.setRequestHeader("Connection", "close");
    objXMLHttp.send(params);
  }

}


function getUserAgent() {
  return navigator.userAgent;
}

function getWindowSearch() {
  return window.location.search;
}

function getWindowLocation() {
  return window.location+"";
}

function _Flash_isCreatePage(callback) {
  if (_homePageLoad) {
    callback({"_homePageLoad":true});
  } else {
    callback({"_homePageLoad":false});
  }
}

function _pngie6() {
  var tmp;
  tmp = document.body.getElementsByTagName('img');
  for (var i=0; i<tmp.length; i++) {
    if (tmp[i].style && ie) {
      _iePng(tmp[i]);
    }
  }
}

function _iePng(obj) {
  obj.style.width = obj.offsetWidth+"px";
  obj.style.height = obj.offsetHeight+"px";
  if (!obj.style.behavior) {
    obj.style.behavior = 'url("htc/pngbehavior.htc")';
  }
}

/********************************************************
 * Flash detection                                      *
 *                                                      *
 * Returns:  -1 if Flash is not installed               *
 *            0 if unknown                              *
 *            else returns flash version (>=9 required) *
 ********************************************************/
function detectFlash() {
  var _flashinstalled = 0;
  var _flashversion = 0;

  if (navigator.plugins && navigator.plugins.length) {
    // Firefox, Safari, etc
    x = navigator.plugins["Shockwave Flash"];
    if (x) {
      _flashinstalled = 2;
      if (x.description) {
        y = x.description;
        _flashversion = y.match(/(\d+)\./)[1];
      }
    } else {
      _flashinstalled = 1;
    }
    if (navigator.plugins["Shockwave Flash 2.0"]) {
      _flashinstalled = 2;
      _flashversion = 2;
    }
  } else if (navigator.mimeTypes && navigator.mimeTypes.length) {
    // Older Netscape (no version info)
    x = navigator.mimeTypes['application/x-shockwave-flash'];
    if (x && x.enabledPlugin) _flashinstalled = 2;
    else _flashinstalled = 1;
  } else {
    // MSIE
    for(var i=14; i>0; i--){
      flashVersion = 0;
      try{
        var flash = new ActiveXObject("ShockwaveFlash.ShockwaveFlash." + i);
        flashVersion = i;
        return i;
      }
      catch(e){
      }
    }
  }

  if (_flashinstalled==1) {
    return -1;
  } else if (_flashinstalled==2) {
    return _flashversion;
  } else {
    return 0;
  }
}
