/**#@+
 @internalApi
 */
/*
 * Copyright (c) 2007, Business Geografic. All rights reserved.
 * Code licensed under a proprietary License
 */


/**
 * @author yves
 */

var Loader = null;
(function() {
  /**
   * Instance var
   * @ignore
   */
  var instance = null;

  /**
   * This create a loader utility
   * @class This class is used to load external dependencies, as javascript, css, plugin specific files, in minified versions or not
   */
  Loader = function() {
    if(Loader.caller != Loader.getInstance) {
      throw new Error("Unable to create the Loader. Use getInstance()");
    }
    this.init();
  };

  /**
   * Loader file path
   * @private
   * @type String
   * @default "bg/loader/Loader.js""
   */
  Loader.LOADER_FILE_PATH = "bg/loader/Loader.js";

  /**
   * Loader version
   * @private
   * @type String
   */
  Loader.VERSION = "4.5.2";

  /**
   * Get the instance of the Loader
   * @return {Loader} The unique loader instance
   */
  Loader.getInstance = function() {
    if(!instance) {
      instance = new Loader();
    }
    return instance;
  };

  Loader.prototype = {
    /** Indicate if the loader is initialized */
    initialized: false,
    /** Head element */
    head: null,
    /** Scripts elements */
    scripts: null,
    /** Base path of scripts */
    scriptPath: null,
    /** Stylesheets */
    styleSheets: null,
    /** Path of css */
    cssPath: null,
    /** Loaded ressources */
    loaded: [],
    /** Resources to load */
    toLoad: [],
    /** Current position inside the loading list */
    indexToLoad: null,
    /** Callback when all is loaded */
    onLoadComplete: null,
    /** Callback when a file is loaded */
    onFileLoad: null,
    /** Use of minified javascript and css files or not */
    useMinJsCss: true,
    /** Use to update value only for a loading part */
    oldUseMin: true,
    /**
     * Dependencies tree.
     * <p>A ressource must have a type (css, yuicss, js, loader), a path (if not loader)
     * and can have a requires list of nedded resources.</p>
     */
    dependencies: {
      "BG.edit": {
        type: "js",
        //alias: _("Edit.label"),
        path: "editConcat.js"
      },
      "BG.edit.*": {
        type: "loader",
        //alias: _("Edit.label"),
        requires: [
          "BG.menu.handleContextMenu",
          "BG.edit.PropertyDialog",
          "BG.edit.Config",
          "BG.edit.PropertyDialogModules",
          "BG.edit.MagnetManager",
          "BG.edit.EditContainer",
          "BG.edit.ContentPanel",
          "BG.edit.EditToolbar",
          "BG.edit.EditEngine",
          "BG.edit.AddedContentDialog",
          "BG.communication.formatter.EditFormatter",
          "BG.edit.EditManager"
        ]
      },
      "BG.menu.handleContextMenu": {
        type: "js",
        path: "bg/menu/handleContextMenu.js"
      },
      "BG.edit.PropertyDialog": {
        type: "js",
        path: "bg/edit/PropertyDialog.js"
      },
      "BG.edit.Config": {
        type: "js",
        path: "bg/edit/Config.js"
      },
      "BG.edit.PropertyDialogModules": {
        type: "js",
        path: "bg/edit/PropertyDialogModules.js"
      },
      "BG.edit.MagnetManager": {
        type: "js",
        path: "bg/edit/MagnetManager.js"
      },
      "BG.edit.EditContainer": {
        type: "js",
        path: "bg/edit/EditContainer.js"
      },
      "BG.edit.ContentPanel": {
        type: "js",
        path: "bg/edit/ContentPanel.js"
      },
      "BG.edit.EditToolbar": {
        type: "js",
        path: "bg/edit/EditToolbar.js"
      },
      "BG.edit.EditEngine": {
        type: "js",
        path: "bg/edit/EditEngine.js"
      },
      "BG.edit.AddedContentDialog": {
        type: "js",
        path: "bg/edit/AddedContentDialog.js"
      },
      "BG.communication.formatter.EditFormatter": {
        type: "js",
        path: "bg/communication/formatter/EditFormatter.js"
      },
      "BG.edit.EditManager": {
        type: "js",
        path: "bg/edit/EditManager.js"
      },
      "BG.All": {
        type: "js",
        path: "jsConcat.js"
      }
    },

    /**
     * Global initialization of the loader, set the differents path
     */
    init: function() {
      if(this.initialized) {
        return;
      }

      this.head = document.lastChild.firstChild;

      this.initScript();
      this.initCss();

      this.initialized = true;
    },
    /**
     * Init the script path
     */
    initScript: function() {
      this.scripts = document.getElementsByTagName('script');
      var script;
      var scriptSrc;
      for(var i = 0; i < this.scripts.length; i++) {
        script = this.scripts[i];
        scriptSrc = script.src;
        if(!(scriptSrc.match(/^(.*)\/loader.js(\?v=[a-z0-9\.]+)?$/i))) {
          continue;
        }
        this.scriptPath = RegExp.$1 + "/../../";
        break;
      }
    },
    /**
     * Init the css path
     */
    initCss: function() {
      this.styleSheets = document.styleSheets;
      var css;
      var cssHref;
      for(var i = 0; i < this.styleSheets.length; i++) {
        css = this.styleSheets[i];
        cssHref = css.href;
        if(cssHref === "") {
          continue;
        }
        this.cssPath = cssHref.replace(/[a-z0-9_-]+\.css/i, '');
        break;
      }
    },
    /**
     * Load needed deps for a resource
     * @param {String} name Name of the resource to load with it dependencies
     * @param {Function} callback Callback to call when the loading is finished
     * @param {Object} callbackFileLoad
     */
    loadDeps: function(name, callback, callbackFileLoad) {
      this.toLoad = [];
      this.onLoadComplete = callback;
      this.onFileLoad = callbackFileLoad;

      this.createDeps(name);
      this.cleanDeps();

      this.indexToLoad = 0;
      this.oldUseMin = this.useMinJsCss;
      this.loadNext();
    },

    /**
     * Load a specific ressource
     * @param {Array} deps Ressources to load
     * @param {Function} callback Callback to call when all is loaded
     * @param {Function} callbackFileLoad Callback to call when each file is loaded
     * @param {Bool} useMin Use min files or not
     */
    _loadDeps: function(deps, callback, callbackFileLoad, useMin) {
      this.oldUseMin = this.useMinJsCss;
      if(isset(useMin)) {
        this.useMinJsCss = useMin;
      }

      this.onLoadComplete = callback;
      this.onFileLoad = callbackFileLoad;
      this.toLoad = deps;
      this.indexToLoad = 0;
      this.loadNext();
    },

    /**
     * Load some dependencies for a plugin
     * @param {Array} deps Ressources to load, with an object for each element as :<ul>
     * <li>type : "js"/"css"</li>
     * <li>path : local path of the file to load</li>
     * <li>[name] : name to present, optional</li>
     * </ul>
     * @param {String} pluginName Name of the plugin
     * @param {Function} [callback] Callback to call when the load of all ressources is finished
     * @param {Function} [callbackFileLoad] Callback to call when a file is loaded
     * @param {Boolean} [useMin] Use min version of files or not
     */
    loadDepsForPlugin: function(deps, pluginName, callback, callbackFileLoad, useMin) {
      var _useMin;
      if(isset(useMin)) {
        _useMin = useMin;
      } else {
        _useMin = false;
      }
      var _deps = [];
      var dep;
      var newdep;
      var params = BG.application.Application.getInstance().getParameters().getPlugin(pluginName);
      var dir = params !== null ? (params.isExt ? "extensions" : "plugins") : "plugins";
      // update path
      for(var i = 0; i < deps.length; i++) {
        dep = deps[i];
        newdep = {
          "type": dep.type,
          "path": dir + "/" + pluginName + "/" + dep.path
        };
        if("name" in dep) {
          newdep.name = dep.name;
        }
        _deps.push(newdep);
      }
      this._loadDeps(_deps, callback, callbackFileLoad, _useMin);
    },

    /**
     * Get the depends for a ressource
     * @param {String} name Name of the element to load
     * @param {Bool} useMin Min files or not
     * @param {String} httpPath Path
     * @returns {Object} depends to load
     * @private
     */
    getDeps: function(name, useMin, httpPath) {
      var _old_use_min = this.useMinJsCss;
      this.useMinJsCss = useMin;
      this.createDeps(name);
      this.cleanDeps();

      var deps = [];

      var dep;
      var link;
      for(var i = 0; i < this.toLoad.length; i++) {
        dep = this.toLoad[i];
        if(httpPath) {
          if(dep.type == "js") {
            link = this.scriptPath + dep.path;
            if(this.useMinJsCss) {
              link = link.replace(/\.js/, '-min.js');
            }
          } else if(dep.type == "css" || dep.type == "yuicss" || dep.type == "plugincss") {
            if(dep.type == "css") {
              link = this.cssPath + dep.path;
              if(this.useMinJsCss) {
                link = link.replace(/\.css/, '-min.css');
              }
            } else {
              link = this.scriptPath + dep.path;
            }
          }
        } else {
          link = dep.path;
        }
        deps.push(link);
      }

      this.useMinJsCss = _old_use_min;

      return deps;
    },

    /**
     * Generate the dependencies list
     * @param {Object} name
     */
    createDeps: function(name) {
      var deps = this.dependencies[name];
      if(!deps) {
        throw new Error("Unable to find require " + name);
      }
      if(deps.requires) {
        for(var i = 0; i < deps.requires.length; i++) {
          this.createDeps(deps.requires[i]);
        }
      }
      if(deps.type != "loader") {
        var load = {
          type: deps.type,
          path: deps.path,
          name: name
        };
        this.toLoad.push(load);
      }

      //this.cleanDeps();
    },
    /**
     * Clean dependencies list to remove same elements
     */
    cleanDeps: function() {
      var deps = this.toLoad;
      this.toLoad = [];
      var _deps = {};
      var name;
      for(var i = 0; i < deps.length; i++) {
        name = deps[i].name;
        if(_deps[name]) {
          continue;
        }
        _deps[name] = name;
        this.toLoad.push(deps[i]);
      }
    },
    /**
     * Load the next resource from the list and call the callback when done
     */
    loadNext: function() {
      if(this.indexToLoad == this.toLoad.length) {
        this.useMinJsCss = this.oldUseMin;

        if(this.onLoadComplete) {
          this.onLoadComplete();
        }
        return;
      }
      var dep = this.toLoad[this.indexToLoad];
      log.log("try to load element " + this.indexToLoad + " : " + dep.type + " - " + dep.path);
      this.indexToLoad += 1;
      if(!dep) {
        return;
      }
      this.loaded.push(dep.name);

      var link;
      var script;
      var style;
      var name = dep.name;
      var alias = dep.alias;
      var self = this;
      var onContent = function() {
        if(self.onFileLoad) {
          self.onFileLoad(name, alias);
        }
        if(self.indexToLoad % 10 === 0) {
          var _self = self;
          var _onContent = function() {
            _self.loadNext();
          };
          setTimeout(_onContent, 500);
        } else {
          self.loadNext();
        }
      };
      if(dep.type == "js") {
        link = this.scriptPath + dep.path;
        script = document.createElement("script");
        script.type = "text/javascript";
        if(this.useMinJsCss) {
          link = link.replace(/\.js/, '-min.js');
        }
        script.src = link + "?v=" + Loader.VERSION;
        script.id = dep.name;

        script.onload = script.onreadystatechange = function() {
          if(script.readyState && script.readyState != "loaded" && script.readyState != "complete") {
            return;
          }
          script.onreadystatechange = script.onload = null;
          try {
            onContent();
          } catch(e) {
            self.loadNext();
          }
        };

        this.head.appendChild(script);
      } else if(dep.type == "css" || dep.type == "yuicss" || dep.type == "plugincss") {
        style = document.createElement("link");
        style.rel = "stylesheet";
        style.type = "text/css";
        if(dep.type == "css") {
          link = this.cssPath + dep.path;
          if(this.useMinJsCss) {
            link = link.replace(/\.css/, '-min.css');
          }
        } else {
          link = this.scriptPath + dep.path;
          if(this.useMinJsCss && dep.type == "plugincss") {
            link = link.replace(/\.css/, '-min.css');
          }
        }
        style.href = link + "?v=" + Loader.VERSION;
        style.id = dep.name;
        this.head.appendChild(style);
        // no time to wait for css...
        onContent();
      }
    }
  };

  /**
   * @ignore
   */
  var _isset = function(variable) {
    return ( (variable !== null) && (typeof(variable) != "undefined") );
  };
})();

/**#@-
 */

