if(Limb == undefined) var Limb = {};
Limb.post_load_hooks = [];

if(typeof(HTTP_SHARED_PATH) == 'undefined')
  var HTTP_SHARED_PATH = './';

var LIMB_INCLUDE_PATH = [HTTP_SHARED_PATH + 'js', '.'];
var LIMB_DEBUG = false;

//do we really need this ???
var LOADING_STATUS_PAGE = HTTP_SHARED_PATH + 'loading.html';

function isset(obj)
{
  return typeof(obj) != undefined && obj != null;
}

Limb.namespace = function(name)
{
  parts = name.split('.');
  parts_string = '';
  for(var i=0; i<parts.length; i++)
  {
    if(!parts[i]) continue;
    parts_string += ('.' + parts[i]);
    exec = 'if(window' + parts_string + ' == undefined) window' + parts_string + ' = {};';
    eval(exec);
  }
}

Limb.inspectError = function(obj)
{
  var info = [];

  if(typeof obj=="string" || typeof obj=="number")
    return obj;
  else
  {
    for(property in obj)
      if(typeof obj[property] != "function")
        info.push(property + ' => ' +
          (typeof obj[property] == "string" ? '"' + obj[property] + '"' : obj[property]));
  }

  return ("'" + obj + "' #" + typeof obj +
    ": {" + info.join(", ") + "}");
}

Limb.require = function(pkgName)
{
  return Limb.Packages.require(pkgName);
}

Limb.Packages = {
  'loaded': [],

  require: function(pkgName)
  {
    if(this.loaded[pkgName])
      return;

    var bundle = new this.Bundle(this.getSourceFetcher(), pkgName);
    var source = bundle.getSource();

    this.Compiller.extract(source, bundle.getLoadedPackages());
    this.loaded[pkgName] = true;
    this.onPackageLoad(pkgName);
  },

  setIncludePath: function(path)
  {
    var fetcher = this.getSourceFetcher();
    fetcher.setIncludePath(path);
  },

  onPackageLoad: function(pkgName) {},

  flush: function()
  {
    this.loaded = [];
  },

  useSourceFetcher: function(fetcher)
  {
    this._fetcher = fetcher;
  },

  getSourceFetcher: function()
  {
    if(!this._fetcher)
      this._fetcher = new Limb.Packages.SourceFetcher();

    return this._fetcher;
  },

  getLoadedPackages: function()
  {
    return this.loaded;
  },

  _showSource: function(source) {
    encoded = source.replace(/>/g, '&gt;');
    encoded = encoded.replace(/</g, '&lt;');
    encoded = encoded.replace(/&/g, '&amp;');
    var w = window.open(null, 'debug');
    w.document.write('<html><body><pre>' + encoded + '<pre></body></html>');
  }
}

Limb.Packages.SourceFetcher = function(){}

Limb.Packages.SourceFetcher.prototype = {
  'fetched': [],
  setIncludePath: function(path)
  {
    this.inc_path = path;
  },

  getIncludePath: function()
  {
    return this.inc_path || ['.'];
  },

  get: function(url)
  {
    window.status = "fetching " + url + "...";

    var inc_path = this.getIncludePath();
    var result = null;

    for(var i=0;i<inc_path.length;i++)
    {
      var request = new Limb.Request();
      result = request.get(inc_path[i] + '/' + url);

      if(result)
        break;
    }
    window.status = "done";
    return result;
  },

  getOnce: function(url)
  {
    if(this.fetched[url])
      return '';

    var code = this.get(url);
    this.fetched[url] = 1;
    return code;
  }
}

Limb.Packages.SourceSplitter = {
  collectBundleSource: function(source)
  {
    var chunks = this.splitSource(source);
  },

  splitSource: function(source)
  {
    if(!source) return;

    var chunks = source.split("\n");

    var res = [];
    var reg_syntax_regex = /^\s*Limb\.require\(\s*([^\)]+)\s*\);?\s*$/;
    var parenthesis = 0;
    var in_block_comment = false;
    for(var i=0;i<chunks.length;i++)
    {
      //single line comments //
      if(chunks[i].match(/^\s*\/\//))
        continue;

      //block comments /* */
      if(!in_block_comment && chunks[i].match(/^\s*\/\*/))
        in_block_comment = true;

      if(in_block_comment)
      {
        var pos = chunks[i].indexOf("*/");
        if(pos != -1)
        {
          chunks[i] = chunks[i].substring(pos + 2);//length of */
          in_block_comment = false;
        }
      }

      if(in_block_comment)
        continue;

      if(chunks[i].indexOf('{') != -1) parenthesis++;
      if(chunks[i].indexOf('}') != -1) parenthesis--;

      if(parenthesis == 0 && chunks[i].match(reg_syntax_regex))
        res[res.length] = {require: eval(RegExp.$1)};
      else
        res[res.length] = {code: chunks[i] + "\n"};

    }
    return res;
  },

  replaceRequireChunk: function(chunks, name, newChunk)
  {
    chunks[this.findRequireChunk(chunks, name)] = newChunk;
    return chunks;
  },

  findRequireChunk: function(chunks, name)
  {
    for(var i=0;i<chunks.length;i++)
    {
      if(typeof(chunks[i]['require']) != 'undefined' &&
         chunks[i]['require'] == name)
        return i;
    }

    return false;
  },

  _filterChunks: function(chunks, type)
  {
    if(!chunks)
      return '';

    var result = [];

    for(var i=0;i<chunks.length;i++)
    {
      if(!chunks[i][type])
        continue;

      result[result.length] = chunks[i][type]
    }
    return result;
  },

  requireChunksOnly: function(chunks)
  {
    return this._filterChunks(chunks, 'require');
  },

  codeChunksOnly: function(chunks)
  {
    return this._filterChunks(chunks, 'code');
  }
}

Limb.Packages.Bundle = function(fetcher, rootPackage)
{
  this.fetcher = fetcher;
  this.rootPackage = rootPackage;
  this._packages = [];
  this._loaded = [];
}

Limb.Packages.Bundle.prototype = {
  getSource: function()
  {
    return this._doCollectBundle(this.rootPackage);
  },

  _doCollectBundle: function(pkgName)
  {
    this._markAsLoading(pkgName);

    var pkgSource = this.fetcher.getOnce(this._getPackageUrl(pkgName));
    if(!pkgSource)
      return '';

    var chunks = Limb.Packages.SourceSplitter.splitSource(pkgSource);
    var dependencies = Limb.Packages.SourceSplitter.requireChunksOnly(chunks);

    for(var i=0;i<dependencies.length;i++)
    {
      if(this._isLoading(dependencies[i]))
        continue;

      var chunk = {};
      chunk.code = this._doCollectBundle(dependencies[i])
      chunks = Limb.Packages.SourceSplitter.replaceRequireChunk(chunks, dependencies[i], chunk);
    }

    return Limb.Packages.SourceSplitter.codeChunksOnly(chunks).join('');
  },

  _getPackageUrl: function(pkg)
  {
    return pkg.replace(/\./g, '/') + '.js';
  },

  getLoadedPackages: function()
  {
    return this._loaded;
  },

  _markAsLoading: function(pkgName)
  {
    this._packages[pkgName] = true;
    this._loaded[this._loaded.length] = pkgName;
  },

  _isLoading: function(pkgName)
  {
    if(typeof(this._packages[pkgName]) == 'undefined')
      return false;

    return true;
  }
}

Limb.Packages.Compiller = {
  extract: function(code, packages)
  {

    try
    {
      eval(code);
    }
    catch(e)
    {
      if(LIMB_DEBUG)
      {
        var w = window.open(null, 'eval_error', 'width=400,height=500,scrollbars=yes,resizable=yes');
        w.document.write('<html><body><small>' + Limb.inspectError(e) + '</small></body></html>');
      }
      return false;
    }

    for(var i=0;i<packages.length;i++)
    {
      try
      {
        pkg = eval(packages[i]);
      }
      catch(e)
      {
        pkg = {};
      }
    }
  },

  _initPackage: function(pkg)
  {
    if(typeof(pkg.__init__) == 'undefined')
      return;

    pkg.__init__();
  }
}

Limb.Request = function()
{
  try
  {
    this._req = new XMLHttpRequest();
  }
  catch(e)
  {
    try
    {
      this._req = new ActiveXObject("Microsoft.XMLHTTP");
    }
    catch(e)
    {
      this._req = null;
      throw new Error('Can\'t create XMLHttpRequest');
    }
  }
}

Limb.Request.prototype = {
  get: function (url)
  {
    if(!this._req)
      return;

    this._req.open("GET", url, false);
    try
    {
      this._req.send(null);
      if (this._req.status == 200 || this._req.status == 0)
      {
         return this._req.responseText;
      }
    }
    catch (e)
    {
      return null;
    }
  }
}

Limb.Packages.setIncludePath(LIMB_INCLUDE_PATH);

if(!isset(Limb.ON_LOAD_SET))
  Limb.ON_LOAD_SET = 0;

Limb.post_load_handler = function()
{
  for(var i=0;i<Limb.post_load_hooks.length;i++)
  {
    hook = Limb.post_load_hooks[i];
    if(typeof(hook) == 'function') hook();
  }
}

if(Limb.ON_LOAD_SET == 0) //protection from repeated setup.js includes
{
  //we can't use nice add event here, because it may be not loaded yet
  Limb.prev_window_on_load_handler = window.onload;
  window.onload = function()
  {
    if(typeof(Limb.prev_window_on_load_handler) == 'function')
      Limb.prev_window_on_load_handler();

    Limb.post_load_handler();
  }
  Limb.ON_LOAD_SET = 1;
}

