/**
 * Warning: Severe criminal and/or civil penalties for misuse of this code
 * (c) 2010-2011 Priacta Inc. All Rights Reserved.
 * Contains TRADE SECRET and PROPRIETARY information
 * ONLY Priacta, Inc. or its CURRENT contractors or employees may use this code
 * If you cease to work for Priacta, you must delete all personal/local copies
 * Priacta will prosecute any non-approved use, disclosure, or possession of this code to the full extent allowed by law
 */

/*
 * by Petko D. Petkov; pdp (architect)
 * http://www.gnucitizen.org
 * http://www.gnucitizen.org/projects/jquery-include/
 */

(function ($) {

    function _includeLog(url, status) {
        // Uncomment to debug the loader
//        try
//        {
//            throw new Error('Include ('+status+'): '+url);
//        }
//        catch (e)
//        {
//            if (console.log)
//            {
//                console.log(e);
//            }
//        }
    }

    // converts @js->scriptIncludeBase
    function _expandUrlBase(url) {
        
        var cachebuster = '';
        var url_param_join = url.indexOf("?") == -1 ? '?' : '&';
        if ($.include.cachebuster != undefined){
            cachebuster = url_param_join+$.include.cachebuster
        }
        // replace the base, if present
        if (url.substr(0, 4).toLowerCase() == '@js/'){return jQuery.scriptIncludeBase + url.substr(4) + cachebuster;}
        else if (url.substr(0, 7).toLowerCase() == '@style/'){return jQuery.styleIncludeBase + url.substr(7) + cachebuster;}
        else if (url.substr(0, 8).toLowerCase() == '@styles/'){return jQuery.styleIncludeBase + url.substr(8) + cachebuster;}
        else {return url + cachebuster;}
    }
    
    // converts scriptIncludeBase->@js
    // TODO: If scriptIncludeBase == styleIncludeBase, detect which to use in some other way
    function _reduceUrlBase(url_in) {
        
        var cachebuster = false;
        if ($.include.cachebuster != undefined){
            // Escape special characters
            var escaped = $.include.cachebuster.replace(/[-\[\]\/\{\}\(\)\*\+\?\.\,\\\^\$\|\#]/g, "\\$&");
            cachebuster = new RegExp('[\\?\\&]'+escaped+'', 'g');
        }

        if (cachebuster)
        {
            // cut out the cache buster if present
            url_in = url_in.replace(cachebuster, '');
        }

        // terminate early if it already has a reduced base
        if (url_in.substr(0, 1) == '@'){return url_in;}

        var url = url_in;
        var haveHost = false;

        // cut out the http/https, if present
        if (url.substr(0, 7).toLowerCase() == 'http://'){url = url.substr(7); haveHost = true;}
        else if (url.substr(0, 8).toLowerCase() == 'https://'){url = url.substr(8); haveHost = true;}
        
        if (haveHost)
        {
            // cut out the host
            if (url.substr(0, document.location.host.length).toLowerCase() == document.location.host.toLowerCase())
            {
                url = url.substr(document.location.host.length);
            }
            else
            {
                // wrong host. return the original
                return url_in;
            }
        }

        if (url.substr(0, jQuery.scriptIncludeBase.length).toLowerCase() == jQuery.scriptIncludeBase.toLowerCase())
        {
            url = '@js/'+url.substr(jQuery.scriptIncludeBase.length);
        }
        else if (url.substr(0, jQuery.styleIncludeBase.length).toLowerCase() == jQuery.styleIncludeBase.toLowerCase())
        {
            url = '@style/'+url.substr(jQuery.styleIncludeBase.length);
        }
        
        return url;
    }
    
    jQuery.extend({
    
      /*
       * paths used for including
       */
      scriptIncludeBase: '/framework/js/', // TODO: Autodetect TODO: Provide a good way to set this
      styleIncludeBase: '/framework/styles/', // TODO: Autodetect TODO: Provide a good way to set this
    
      /*
       * included scripts
       */
      includedScripts: {},
    
      /*
       * requirements handlers
       */
      requirementsHandlers: {},
      
      /*
       * used to keep track of which scripts are waiting
       * for files so that they can fire their callbacks
       * to finish the loading process.
       */
      waitingIncludedScripts: {},
    
      /*
       * include timer
       */
      includeTimer: null,
      
      // loadWaitFor is similar to jQuery.ready(), but it is specialized for handling
      // dependency requirements. Using jQuery.ready() for dependency management has
      // two major problems:
      // 1. It can allow the top level jQuery.ready() handler to fire before
      //    all levels of dependencies are loaded.
      // 2. It slows down the loading because every level of dependencies
      //    must wait for all includes in the previous level, rather than
      //    only waiting for the relevant includes.
      //
      // Use loadWaitFor instead to manage load order dependencies in files. loadWaitFor
      // can be called before jQuery.ready(), but never before the specified scripts
      // have completely loaded. If the loadWaitFor callback includes new scripts, those
      // scripts will continue to delay jQuery.ready()
      //
      // If the requirement shouldn't delay any file, pass null to delayfile. Normally
      // you will want to pass the name of the current file, to prevent callbacks from
      // being 
      loadWaitFor: function(thisfile, urls, callback) {
          if (typeof urls == 'string') {urls = [urls];}
          
          // Make sure that thisfile is reduced to the base url
          if (thisfile !== null)
          {
              thisfile = _reduceUrlBase(thisfile);
          }
          
          function array_remove(array, from, to) {
              var rest = array.slice((to || from) + 1 || array.length);
              array.length = from < 0 ? array.length + from : from;
              return array.push.apply(array, rest);
          }
    
          // build a list of URLs that still need to be added
          var waitingFor = [];
          var a;
          for(a = 0; a < urls.length; a++)
          {
              var url = _reduceUrlBase(urls[a]);
              if (typeof jQuery.includedScripts[url] != 'undefined' && jQuery.includedScripts[url]) {
                  continue; // already done. No need to wait.
              }
              waitingFor.push(url);
          }

          _includeLog(thisfile, 'wait started');

          if (waitingFor.length == 0) {
              // Already included. Just fire the callback and return.
              _includeLog(thisfile, 'wait completed');
              callback();
              return;
          }
          
          var handler = {
              callback: callback,
              fired: false,
              thisfile: thisfile,
              waitingFor: waitingFor,
              satisfy: function(url){
                  url = _reduceUrlBase(url);
                  var a;
                  for(a = 0; a < this.waitingFor.length; a++)
                  {
                      if (url == this.waitingFor[a] ||
                         (typeof jQuery.includedScripts[this.waitingFor[a]] != 'undefined' &&
                                 jQuery.includedScripts[this.waitingFor[a]]))
                      {
                          // Script was included. Remove it from the waiting for list.
                          array_remove(this.waitingFor, a);
                          a--;
                      }
                  }
                  
                  // if this was the last one, fire the callback
                  if (waitingFor.length == 0)
                  {
                      if (!this.fired)
                      {
                          _includeLog(thisfile, 'wait completed');
                          this.fired = true;
                          callback();
                      }
                      else
                      {
                          // The callback was already called. Don't bother with it again.
                          _includeLog(thisfile, 'DOUBLE WAIT COMPLETED?!?!');
                          return; // nothing to do
                      }
                      
                      // resolve loadWaitingFor conditions on this file if needed
                      if (this.thisfile !== null)
                      {
                          if (typeof jQuery.waitingIncludedScripts[this.thisfile] != 'undefined')
                          {
                              jQuery.waitingIncludedScripts[this.thisfile]--;
                              if (jQuery.waitingIncludedScripts[this.thisfile] == 0)
                              {
                                  // done waiting
                                  jQuery.satisfyLoadWaitFor(this.thisfile);
                              }
                          }
                      }
                  }
                  else
                  {
                      _includeLog(thisfile, 'waiting for '+waitingFor.length);
                  }
              }
          };

          // Increment the waitingIncludedScripts entry for this file if needed
          if (thisfile !== null)
          {
              if (typeof jQuery.waitingIncludedScripts[thisfile] == 'undefined')
              {
                  jQuery.waitingIncludedScripts[thisfile] = 1;
              }
              else
              {
                  jQuery.waitingIncludedScripts[thisfile]++;
              }
          }
    
          // add the handler to each URL in the waitingFor list
          var a;
          for(a = 0; a < waitingFor.length; a++)
          {
              var url = waitingFor[a];
              if (typeof jQuery.requirementsHandlers[url] == 'undefined') {jQuery.requirementsHandlers[url] = [];}
              jQuery.requirementsHandlers[url].push(handler);
          }
      },
      
      satisfyLoadWaitFor: function(url) {
          url = _reduceUrlBase(url);

          // Can this be important sometimes?
//          // Skip if we already handled this file
//          if (typeof jQuery.includedScripts[url] != 'undefined' &&
//              jQuery.includedScripts[url] == true)
//          {
//              return; // already included. No more work needed.
//          }
              
          if (typeof jQuery.waitingIncludedScripts[url] == 'undefined' ||
              jQuery.waitingIncludedScripts[url] == 0)
          {

//            if (jQuery.includedScripts[url])
//            {
//                // Double inclusion???!!!
//                _includeLog(url, 'DOUBLE LOADED?!?!');
//            }
              
              jQuery.includedScripts[url] = true;
              _includeLog(url, 'loaded');
              
              // Not waiting, or done waiting. Fire any callbacks and mark as included.
              if (typeof jQuery.requirementsHandlers[url] != 'undefined')
              {
                  // Let each handler figure it out for itself
                  var a;
                  for(a = 0; a < jQuery.requirementsHandlers[url].length; a++)
                  {
                      jQuery.requirementsHandlers[url][a].satisfy(url);
                  }
              }
          }
      },
      
      /*
       * include
       */
      include: function (url, type, media_or_attributes_for_css) {
        if (typeof url == 'undefined') {
          return;
        }

        // normalize the url bases
        url = _reduceUrlBase(url);

        if (typeof jQuery.includedScripts[url] != 'undefined') {
          return; // already included
        }
    
        if (typeof type == 'undefined')
        {
          if (new RegExp('\/*.css$', 'gi').test(url))
          {
            type = 'css';
          }
          else
          {
            type = 'js';
          }
        }

        _includeLog(url, 'include');

        if (type == 'css')
        {
          var attributes = media_or_attributes_for_css;
          if (typeof attributes == 'string') {
            attributes = {media: attributes};
          }
          if (typeof attributes == 'undefined') {
            attributes = {};
          }
          
          var fileref=document.createElement("link");
          fileref.setAttribute("rel", "stylesheet");
          fileref.setAttribute("type", "text/css");
          fileref.setAttribute("href", _expandUrlBase(url));
          if (typeof attributes.media !== 'undefined') {
            fileref.setAttribute("media", attributes.media);
          }
          document.getElementsByTagName("head")[0].appendChild(fileref);
          jQuery.includedScripts[url] = true;
        }
        else
        {
          var script = document.createElement('script');
        
          script.type = 'text/javascript';
          var onerror = function (msg, error_url, linenumber) {
    
//              // log the error to console if we have one
//              if (typeof console != 'undefined')
//              {
//                  console.error('Error loading %s@%i: %s', url, linenumber, msg);
//              }
    
              // if we had any requirements, let them continue now. This will probably
              // result in errors, but that is better than deadlock
              
              // Give precedence to the onerror value. (?)
              // Otherwise use the url value from the closure.
              var satisfy_url = (typeof error_url == 'undefined' ? url : error_url);
              
              jQuery.satisfyLoadWaitFor(satisfy_url);
              
              return false; // Do NOT block further handling
          };
          var onload = function () {

            // Wait a moment to give the loaded script a chance to
            // execute (in case it hasn't already).
            setTimeout(function(){
                // if we had any requirements, satisfy them
                jQuery.satisfyLoadWaitFor(url);
            }, 0);
              
            /* An alternate solution that we explored
            // To ensure that this executes AFTER the
            // script has really loaded and parsed, we
            // set the include in a new inline script.
            // This works because parsing and execution
            // of scripts is synchronous, so the loaded
            // script will block execution of this one
            // until it is parsed.
            if (!jQuery.msie)
            {
              //works in modern DOM aware browsers
              var loadedscript = document.createElement("script");
              loadedscript.appendChild( document.createTextNode("jQuery.includedScripts['"+url+"'] = true;") );
              document.getElementsByTagName('head')[0].appendChild(loadedscript);
            }
            else
            {
              //in IE, you can’t do the above (it will just ignore the code), you need to do:
              var loadedscript = document.createElement("script");
              loadedscript.text = "jQuery.includedScripts['"+url+"'] = true;";
              document.getElementsByTagName('head')[0].appendChild(loadedscript);
            }
            */
          };
          script.onload = onload;
          if ( document.addEventListener ) {
              script.addEventListener('load', onload, false);
              script.addEventListener('error', onerror, false);
          // If IE event model is used
          } else if ( document.attachEvent ) {
              script.attachEvent('onload', onload);
              script.attachEvent('onerror', onerror);
          }
          script.onreadystatechange = function () {
            if (script.readyState == 'complete' || // the right way
                script.readyState == 'loaded') { // the IE way
  //            jQuery.includedScripts[url] = true;
        
              if (typeof onload == 'function') {
                onload.apply(jQuery(script), arguments);
              }
            }
          };
          script.async = 'async'; // probably not needed, but basically, yes, we don't care when the script finishes loading
          script.src = _expandUrlBase(url);
        
          jQuery.includedScripts[url] = false;

          // AFTER we set the entry in the included scripts list but BEFORE
          // we actually append the element to the document, we need to make
          // sure that the ready state will be held until all the scripts
          // have loaded.
          if (!jQuery.includeTimer) {

            // reload and block the ready event until the scripts are done loading
            jQuery.holdReady(true);

            jQuery.includeTimer = window.setInterval(function () {
              jQuery.checkIncludeReady();
            }, 100);
          }

          document.getElementsByTagName('head')[0].appendChild(script);
        }
      },
      
      checkIncludeReady: function(){
        for (var script in jQuery.includedScripts) {
          if (typeof script != 'undefined' && jQuery.includedScripts[script] == false) {
            if (!jQuery.includeTimer) {

              // reload and block the ready event until the scripts are done loading
              jQuery.holdReady(true);

              jQuery.includeTimer = window.setInterval(function () {
                jQuery.checkIncludeReady();
              }, 10);
            }
            return;
          }
        }

//        if ( typeof php != "undefined" && typeof php.isLoading != "undefined" && php.isLoading)
//        {
//          if (!jQuery.includeTimer) {
//
//              // reload and block the ready event until the scripts are done loading
//              jQuery.holdReady(true);
//
//              jQuery.includeTimer = window.setInterval(function () {
//                jQuery.checkIncludeReady();
//              }, 10);
//          }
//          return;
//        }
        
        // don't need the timer anymore    
        window.clearInterval(jQuery.includeTimer);
        jQuery.includeTimer = null;
        
        // release the hold
        jQuery.holdReady(false);
      }
    });

//    if (typeof jQuery._onReady == 'undefined') // see if our fix is in...
//    {
//      // rebind the new extended ready event
//      if ( document.addEventListener ) {
//        window.addEventListener( "load", jQuery.ready, false );
//      // If IE event model is used
//      } else if ( document.attachEvent ) {
//        window.attachEvent( "onload", jQuery.ready );
//      }
//    }

  $(function(){
    /** 
     * Find all initial loaded JavaScript and StyleSheet files and 
     * store them in $.includedScripts.
     */
    $.grep($('head script, head link[type="text/css"]'), function (elem) {
      var uriAttr = !!elem.src ? 'src' : 'href';
      
      if(typeof elem[uriAttr] != 'undefined' && !!elem[uriAttr].length) {
        $.includedScripts[_reduceUrlBase(elem[uriAttr])] = true;
      } else { return; }
    });
  
  });
})(jQuery);

