// Copyright 2006 Google Inc. All Rights Reserved.
// This startup script should be included in host pages either just after
// <body> or inside the <head> after module <meta> tags.
//

//////////////////////////////////////////////////////////////////////////////
// DynamicResources
//

function DynamicResources() {
  this.pendingElemsBySrc_ = {};
  this.pendingScriptElems_ = new Array();
}
DynamicResources.prototype = {};

// The array is set up such that, pairwise, the entries are (src, readyFnStr).
// Called once for each module that is attached to the host page.
// It is theoretically possible that addScripts() could be called reentrantly
// if the browser event loop is pumped during this function and an iframe loads; 
// we may want to enhance this method in the future to support that case.
DynamicResources.prototype.addScripts = function(scriptArray, insertBeforeElem) {
  var wasEmpty = (this.pendingScriptElems_.length == 0);
  var anyAdded = false;
  for (var i = 0, n = scriptArray.length; i < n; i += 2) {
    var src = scriptArray[i];
    if (this.pendingElemsBySrc_[src]) {
      // Don't load the same script twice.
      continue;
    }
    // Set up the element but don't add it to the DOM until its turn.
    anyAdded = true;
    var e = document.createElement("script");
    this.pendingElemsBySrc_[src] = e;
    var readyFn;
    eval("readyFn = " + scriptArray[i+1]);
    e.__readyFn = readyFn;
    e.type = "text/javascript";
    e.src = src;
    e.__insertBeforeElem = insertBeforeElem;
    this.pendingScriptElems_ = this.pendingScriptElems_.concat(e);
  }
  
  if (wasEmpty && anyAdded) {
    // Kickstart.
    this.injectScript(this.pendingScriptElems_[0]);
  }
}

DynamicResources.prototype.injectScript = function(scriptElem) {
  var parentElem = scriptElem.__insertBeforeElem.parentNode;
  parentElem.insertBefore(scriptElem, scriptElem.__insertBeforeElem);
}

DynamicResources.prototype.addStyles = function(styleSrcArray, insertBeforeElem) {
  var parent = insertBeforeElem.parentNode;
  for (var i = 0, n = styleSrcArray.length; i < n; ++i) {
    var src = styleSrcArray[i];
    if (this.pendingElemsBySrc_[src]) 
      continue;
    var e = document.createElement("link");
    this.pendingElemsBySrc_[src] = e;
    e.type = "text/css";
    e.rel = "stylesheet";
    e.href = src;
    parent.insertBefore(e, insertBeforeElem);
  }
}

DynamicResources.prototype.isReady = function() {
  var elems = this.pendingScriptElems_;
  if (elems.length > 0) {
    var e = elems[0];
    if (!e.__readyFn()) {
      // The pending script isn't ready yet.
      return false;
    }
    
    // The pending script has now finished loading. Enqueue the next, if any.
    e.__readyFn = null;
    elems.shift();
    if (elems.length > 0) {
      // There is another script.
      this.injectScript(elems[0]);
      return false;
    }
  }

  // There are no more pending scripts.
  return true;
}

//////////////////////////////////////////////////////////////////////////////
// ModuleControlBlock
//
function ModuleControlBlock(metaElem, rawName) {
  var parts = ["", rawName];
  var i = rawName.lastIndexOf("=");
  if (i != -1) {
    parts[0] = rawName.substring(0, i) + '/';
    parts[1] = rawName.substring(i+1);
  }

  this.metaElem_ = metaElem;
  this.baseUrl_ = parts[0];
  this.name_ = parts[1];
  this.compilationLoaded_ = false;
  this.frameWnd_ = null;
}
ModuleControlBlock.prototype = {};

/**
 * Determines whether this module is fully loaded and ready to run.
 */
ModuleControlBlock.prototype.isReady = function() {
  return this.compilationLoaded_;
};

/**
 * Called when the compilation for this module is loaded.
 */
ModuleControlBlock.prototype.compilationLoaded = function(frameWnd) {
  this.frameWnd_ = frameWnd;
  this.compilationLoaded_ = true;
}

/**
 * Gets the logical module name, not including a base url prefix if one was
 * specified.
 */
ModuleControlBlock.prototype.getName = function() {
  return this.name_;
}

/**
 * Gets the base URL of the module, guaranteed to end with a slash.
 */
ModuleControlBlock.prototype.getBaseURL = function() {
  return this.baseUrl_;
}

/**
 * Gets the window of the module's frame.
 */
ModuleControlBlock.prototype.getModuleFrameWindow = function() {
  return this.frameWnd_;
}

/**
 * Injects a set of dynamic scripts.
 * The array is set up such that, pairwise, the entries are (src, readyFnStr).
 */
ModuleControlBlock.prototype.addScripts = function(scriptSrcArray) {
  return ModuleControlBlocks.dynamicResources_.addScripts(scriptSrcArray, this.metaElem_);
}

/**
 * Injects a set of dynamic styles.
 */
ModuleControlBlock.prototype.addStyles = function(styleSrcArray) {
  return ModuleControlBlocks.dynamicResources_.addStyles(styleSrcArray, this.metaElem_);
}
 
//////////////////////////////////////////////////////////////////////////////
// ModuleControlBlocks
//
function ModuleControlBlocks() {
  this.blocks_ = [];
}
ModuleControlBlocks.dynamicResources_ = new DynamicResources(); // "static"
ModuleControlBlocks.prototype = {};

/**
 * Adds a module control control block for the named module.
 * @param metaElem the meta element that caused the module to be added
 * @param name the name of the module being added, optionally preceded by
 * an alternate base url of the form "_path_=_module_".
 */
ModuleControlBlocks.prototype.add = function(metaElem, name) {
  var mcb = new ModuleControlBlock(metaElem, name);
  this.blocks_ = this.blocks_.concat(mcb);
};

/**
 * Determines whether all the modules are loaded and ready to run.
 */
ModuleControlBlocks.prototype.isReady = function() {
  for (var i = 0, n = this.blocks_.length; i < n; ++i) {
    var mcb = this.blocks_[i];
    if (!mcb.isReady()) {
      return false;
    }
  }
  
  // Are there any pending dynamic resources (e.g. styles, scripts)?
  if (!ModuleControlBlocks.dynamicResources_.isReady()) {
    // No, we're still waiting on one or more dynamic resources.
    return false;
  }

  return true;
}

/**
 * Determines whether there are any module control blocks.
 */
ModuleControlBlocks.prototype.isEmpty = function() {
  return this.blocks_.length == 0;
}

/**
 * Gets the module control block at the specified index.
 */
ModuleControlBlocks.prototype.get = function(index) {
  return this.blocks_[index];
}

/**
 * Injects an iframe for each module.
 */
ModuleControlBlocks.prototype.injectFrames = function() {
  for (var i = 0, n = this.blocks_.length; i < n; ++i) {
    var mcb = this.blocks_[i];

    // Insert an iframe for the module
    var iframe = document.createElement("iframe");
    var selectorUrl = mcb.getBaseURL() + mcb.getName() + ".nocache.html";
    selectorUrl += "?" + (__gwt_isHosted() ? "h&" : "" ) + i;
    var unique = new Date().getTime();
    selectorUrl += "&" + unique;
    iframe.style.border = '0px';
    iframe.style.width = '0px';
    iframe.style.height = '0px';
    
    // Fragile browser-specific ordering issues below
    
/*@cc_on
    // prevent extra clicky noises on IE
    iframe.src = selectorUrl;
@*/
    
    if (document.body.firstChild) {
      document.body.insertBefore(iframe, document.body.firstChild);
    } else {
      document.body.appendChild(iframe);
    }
    
/*@cc_on
    // prevent extra clicky noises on IE
    return;
@*/

    if (iframe.contentWindow) {
      // Older Mozilla has a caching bug for the iframe and won't reload the nocache.
      iframe.contentWindow.location.replace(selectorUrl);
    } else {
      // Older Safari doesn't have a contentWindow.
      iframe.src = selectorUrl;
    }
  }
}

/**
 * Runs the entry point for each module.
 */
ModuleControlBlocks.prototype.run = function() {
  for (var i = 0, n = this.blocks_.length; i < n; ++i) {
    var mcb = this.blocks_[i];
    var name = mcb.getName();
    var frameWnd = mcb.getModuleFrameWindow();
    if (__gwt_isHosted()) {
      if (!window.external.gwtOnLoad(frameWnd, name)) {
        // Module failed to load.
        if (__gwt_onLoadError) {
            __gwt_onLoadError(name);
        } else {
            window.alert("Failed to load module '" + name + 
            "'.\nPlease see the log in the development shell for details.");
        }
      }
    } else {
      // The compilation itself handles calling the error function.
      frameWnd.gwtOnLoad(__gwt_onLoadError, name);
    }
  }
}

//////////////////////////////////////////////////////////////////////////////
// Globals
//

var __gwt_retryWaitMillis = 10;
var __gwt_isHostPageLoaded = false;
var __gwt_metaProps = {};
var __gwt_onPropertyError = null;
var __gwt_onLoadError = null;
var __gwt_moduleControlBlocks = new ModuleControlBlocks();

//////////////////////////////////////////////////////////////////////////////
// Common 
//

/**
 * Determines whether or not the page is being loaded in the GWT hosted browser.
 */
function __gwt_isHosted() {
  if (window.external && window.external.gwtOnLoad) {
    // gwt.hybrid makes the hosted browser pretend not to be
    if (document.location.href.indexOf("gwt.hybrid") == -1) {
      return true;
    }
  }
  return false;
}

/**
 * Tries to get a module control block based on a query string passed in from
 * the caller. Used by iframes to get references back to their mcbs.
 * @param queryString the entire query string as returned by location.search,
 * which notably includes the leading '?' if one is specified
 * @return the relevant module control block, or <code>null</code> if it cannot 
 * be derived based on <code>queryString</code>
 */
function __gwt_tryGetModuleControlBlock(queryString) {
  if (queryString.length > 0) {
    // The pattern is ?[h&]<index>[&<unique>]
    var queryString = queryString.substring(1);
    if (queryString.indexOf("h&") == 0) {
      // Ignore the hosted mode flag here; only GWTShellServlet cares about it.
      queryString = queryString.substring(2);
    }
    var pos = queryString.indexOf("&");
    if (pos >= 0) {
      queryString = queryString.substring(0, pos);
    }
    var mcbIndex = parseInt(queryString);
    if (!isNaN(mcbIndex)) {
      var mcb = __gwt_moduleControlBlocks.get(mcbIndex);
      return mcb;
    }
    // Ignore the unique number that remains on the query string.
  }
  return null;
}

/**
 * Parses meta tags from the host html.
 * 
 * <meta name="gwt:module" content="_module-name_">
 *    causes the specified module to be loaded
 *
 * <meta name="gwt:property" content="_name_=_value_">
 *    statically defines a deferred binding client property
 *
 * <meta name="gwt:onPropertyErrorFn" content="_fnName_">
 *    specifies the name of a function to call if a client property is set to 
 *    an invalid value (meaning that no matching compilation will be found)
 * 
 * <meta name="gwt:onLoadErrorFn" content="_fnName_">
 *    specifies the name of a function to call if an exception happens during 
 *    bootstrapping or if a module throws an exception out of onModuleLoad(); 
 *    the function should take a message parameter
 */
function __gwt_processMetas() {
  var metas = document.getElementsByTagName("meta");
  for (var i = 0, n = metas.length; i < n; ++i) {
    var meta = metas[i];
    var name = meta.getAttribute("name");
    if (name) {
      if (name == "gwt:module") {
        var moduleName = meta.getAttribute("content");
        if (moduleName) {
          __gwt_moduleControlBlocks.add(meta, moduleName);
        }
      } else if (name == "gwt:property") {
        var content = meta.getAttribute("content");
        if (content) {
          var name = content, value = "";
          var eq = content.indexOf("=");
          if (eq != -1) {
            name = content.substring(0, eq);
            value = content.substring(eq+1);
          }
          __gwt_metaProps[name] = value;
        }
      } else if (name == "gwt:onPropertyErrorFn") {
        var content = meta.getAttribute("content");
        if (content) {
          try {
            __gwt_onPropertyError = eval(content);
          } catch (e) {
            window.alert("Bad handler \"" + content + 
              "\" for \"gwt:onPropertyErrorFn\"");
          }
        }
      } else if (name == "gwt:onLoadErrorFn") {
        var content = meta.getAttribute("content");
        if (content) {
          try {
            __gwt_onLoadError = eval(content);
          } catch (e) {
            window.alert("Bad handler \"" + content + 
              "\" for \"gwt:onLoadErrorFn\"");
          }
        }
      }
    }
  }
}

/**
 * Determines the value of a deferred binding client property specified 
 * statically in host html.
 */
function __gwt_getMetaProperty(name) {
  var value = __gwt_metaProps[name];
  if (value) {
    return value;
  } else {
    return null;
  }
}

/**
 * Determines whether or not a particular property value is allowed.
 * @param wnd the caller's window object (not $wnd!)
 * @param propName the name of the property being checked
 * @param propValue the property value being tested
 */
function __gwt_isKnownPropertyValue(wnd, propName, propValue) {
  return propValue in wnd["values$" + propName];
}

/**
 * Called by the selection script when a property has a bad value or is missing.
 * 'allowedValues' is an array of strings. Can be hooked in the host page using 
 * gwt:onPropertyErrorFn.
 */
function __gwt_onBadProperty(moduleName, propName, allowedValues, badValue) {
  if (__gwt_onPropertyError) {
    __gwt_onPropertyError(moduleName, propName, allowedValues, badValue);
    return;
  } else {
    var msg = "While attempting to load module \"" + moduleName + "\", ";
    if (badValue != null) {
       msg += "property \"" + propName + "\" was set to the unexpected value \"" 
        + badValue + "\"";
    } else {
       msg += "property \"" + propName + "\" was not specified";
    }
    
    msg += "\n\nAllowed values: " + allowedValues;
   
    window.alert(msg);
  }
}

/**
 * Called directly from compiled code.
 */
function __gwt_initHandlers(resize, beforeunload, unload) {
   var oldOnResize = window.onresize;
   window.onresize = function() {
      resize();
      if (oldOnResize)
         oldOnResize();
   };

   var oldOnBeforeUnload = window.onbeforeunload;
   window.onbeforeunload = function() {
      var ret = beforeunload();

      var oldRet;
      if (oldOnBeforeUnload)
        oldRet = oldOnBeforeUnload();

      if (ret !== null)
        return ret;
      return oldRet;
   };

   var oldOnUnload = window.onunload;
   window.onunload = function() {
      unload();
      if (oldOnUnload)
         oldOnUnload();
   };
}

//////////////////////////////////////////////////////////////////////////////
// Hosted Mode
//
function __gwt_onUnloadHostedMode() {
    window.external.gwtOnLoad(null, null);
    if (__gwt_onUnloadHostedMode.oldUnloadHandler) {
        __gwt_onUnloadHostedMode.oldUnloadHandler();
    }
}

//////////////////////////////////////////////////////////////////////////////
// Bootstrap
//

/**
 * Waits until all startup preconditions are satisfied, then launches the 
 * user-defined startup code for each module.
 */
function __gwt_latchAndLaunch() {
  var ready = true;
  
  // Are there any compilations still pending?
  if (ready && !__gwt_moduleControlBlocks.isReady()) {
    // Yes, we're still waiting on one or more compilations.
    ready = false;
  }

  // Has the host html onload event fired?
  if (ready && !__gwt_isHostPageLoaded) {
    // No, the host html page hasn't fully loaded.
    ready = false;
  }
  
  // Are we ready to run user code?
  if (ready) {
    // Yes: run entry points.
    __gwt_moduleControlBlocks.run();
  } else {
    // No: try again soon.
    window.setTimeout(__gwt_latchAndLaunch, __gwt_retryWaitMillis);
  }
}

/**
 * Starts the module-loading sequence after meta tags have been processed and
 * the body element exists.
 */
function __gwt_loadModules() {
  // Make sure the body element exists before starting.
  if (!document.body) {
    // Try again soon.
    window.setTimeout(__gwt_loadModules, __gwt_retryWaitMillis);
    return;
  }

  // Inject a frame for each module.
  __gwt_moduleControlBlocks.injectFrames();

  // Try to launch module entry points once everything is ready.
  __gwt_latchAndLaunch();
}

/**
 * The very first thing to run, and it runs exactly once unconditionally.
 */
function __gwt_bootstrap() {
  // Hook onunload for hosted mode.
  if (__gwt_isHosted()) {
    __gwt_onUnloadHostedMode.oldUnloadHandler = window.onunload;
    window.onunload = __gwt_onUnloadHostedMode;
  }

  // Hook the current window onload handler.
  var oldHandler = window.onload;
  window.onload = function() {
    __gwt_isHostPageLoaded = true;
    if (oldHandler) {
      oldHandler();
    }
  };

  // Parse meta tags from host html.
  __gwt_processMetas();

  // Load any modules.
  __gwt_loadModules();
}

// Go.
__gwt_bootstrap();

