// [BLite]
// http://code.google.com/p/blite/
//
// Authors:
// (c) Petr Kobalicek 2008-2009, <http://www.kobalicek.com>
// (c) Robert Nyman <http://www.robertnyman.com/dlite>
//
// Licence: MIT <http://www.opensource.org/licenses/mit-license.php>
//
// Goal of BLite library is to provide multibrowser functionality and powerful
// object oriented design for web developers. This library was designed to be
// as small as possible and to be very interesting for people with object
// oriented experiences.
//
// Object oriented model was inspired in qooxdoo <http://www.qooxdoo.org/>.
//
// BLite is based on dLite library by Robert Nyman but totally reworked and now
// it probably not contains dLite code anymore.

// ===========================================================================
// [BLite]
// ===========================================================================

var BLite = {};

// ===========================================================================
// [BLite - Wrap]
// ===========================================================================

(function(){

// Shortcuts. These shortcuts are used in library itself, but not set as 
// globals. Global is only BLite namespace.
var B = BLite, Dom;

// ===========================================================================
// [Native]
// ===========================================================================

// These prototypes are provided by the Mozilla foundation and
// are distributed under the MIT license.
// http://www.ibiblio.org/pub/Linux/LICENSES/mit.license

// This section adds standard Array functions to all browsers.

// Provides safe including functions into prototypes. These functions are known
// and another javascript library can include them too. So include functions 
// only if they are not in prototype already.
var $extend = function(o, name, fn)
{
  if (!o.prototype[name]) o.prototype[name] = fn;
};

// Array.every()
$extend(Array, "every", function(fn /*, thisp*/)
{
  if (typeof fn !== "function") throw new TypeError();

  for (var i = 0, len = this.length, thisp = arguments[1]; i < len; i++)
  {
    if (i in this && !fn.call(thisp, this[i], i, this)) return false;
  }

  return true;
});

// Array.filter()
$extend(Array, "filter", function(fn /*, thisp*/)
{
  if (typeof fn !== "function") throw new TypeError();

  var res = [];

  for (var i = 0, len = this.length, thisp = arguments[1]; i < len; i++)
  {
    if (i in this)
    {
      var val = this[i]; // in case fn mutates this
      if (fn.call(thisp, val, i, this)) res.push(val);
    }
  }

  return res;
});

// Array.forEach()
$extend(Array, "forEach", function(fn /*, thisp*/)
{
  if (typeof fn !== "function") throw new TypeError();

  for (var i = 0, len = this.length, thisp = arguments[1]; i < len; i++)
  {
    if (i in this) fn.call(thisp, this[i], i, this);
  }
});

//! @brief Helper method used by Array.
$rnd = function(x)
{
  return (x < 0) ? Math.ceil(x) : Math.floor(x);
}

// Array.indexOf()
$extend(Array, "indexOf", function(elt, from)
{
  var len = this.length;
  from = from ? $rnd(from) : 0;
  if (from < 0) from += len;

  for (; from < len; from++)
  {
    if (from in this && this[from] === elt) return from;
  }
  return -1;
});

// Array.lastIndexOf()
$extend(Array, "lastIndexOf", function(elt, from)
{
  var len = this.length;

  if (isNaN(from))
  {
    from = len - 1;
  }
  else
  {
    from = $rnd(from);
    if (from < 0)
      from += len;
    else if (from >= len)
      from = len - 1;
  }

  for (; from >= 0; from--)
  {
    if (from in this && this[from] === elt) break;
  }
  return from;
});

// Array.map()
$extend(Array, "map", function(fn, thisp)
{
  if (typeof fn !== "function") throw new TypeError();

  var i = 0, len = this.length;
  var res = new Array(len);

  for (; i < len; i++) 
  {
    if (i in this) res[i] = fn.call(thisp, this[i], i, this);
  }

  return res;
});

// Array.some()
$extend(Array, "some", function(fn, thisp)
{
  if (typeof fn !== "function") throw new TypeError();

  for (var i = 0, len = this.length; i < len; i++) 
  {
    if (i in this && fn.call(thisp, this[i], i, this)) return true;
  }

  return false;
});

// ===========================================================================
// [BLite.Object]
// ===========================================================================

// cache variables. IE will create and alloc new String object always where
// code contains "", so this is like preallocation.
var _s_ = "_", _s_get = "get", _s_set = "set";

var _create = function()
{
  return function f() { arguments.callee.base.apply(this, arguments); };
};

// tries to find base method/member.
var _findBase = function(obj, key)
{
  var p = obj;
  while ((p = p.prototype))
  {
    if (p[key]) return p[key];
    if (!(p = p.superclass)) break;
  }
};

// tries to find property.
var _findProperty = function(obj, key)
{
  var p;
  do {
    if ((p = obj._properties) && (p = p[key])) return p;
  } while ((obj = obj.base));
};

// adds property into object prototype.
var _addProperty = function(obj, key, property)
{
  var _key = _s_ + key,
      Key = key.charAt(0).toUpperCase() + key.substring(1, key.length),
      getterKey = "get" + Key,
      setterKey = "set" + Key,
      apply = property.apply,
      event = property.event,
      p = obj.prototype;

  // define getter
  p[getterKey] = function() { return this[_key]; };

  // define setter
  p[setterKey] = (!apply && !event)
  ? function(value)
    {
      // optimized for maximum speed
      this[_key] = value;
      return this;
    }
  : function(value)
    {
      var old;
      if ((old = this[_key]) !== value)
      {
        this[_key] = value;
        if (apply) this[apply](value, old);
        if (event) this.fireDataEvent(event, value, old);
      }
      return this;
    };
};

// adds singleton methods into object prototype.
var _makeSingleton = function(obj)
{
  obj.$$instance = null;
  obj.getInstance = function()
  {
    return obj.$$instance || (obj.$$instance = new obj);
  };
};

// makes autosingleton from object (initialize it when DOM is ready)
var _makeAutoSingleton = function(obj)
{
  _makeSingleton(obj);
  B.Dom.ready(function() { obj.getInstance(); });
};

// property set...() method wrapper.
var _set = function()
{
  var a = arguments;
  if (a.length === 1)
  {
    var p = a[0];
    for (var key in p)
    {
      var Key = key.charAt(0).toUpperCase() + key.substring(1, key.length);
      this[_s_set + Key](p[key]);
    }
  }
  else
  {
    var key = a[0],
        Key = key.charAt(0).toUpperCase() + key.substring(1, key.length);
    this[_s_set + Key](a[1]);
  }
  return this;
};

// property get...() method wrapper.
var _get = function(key)
{
  var Key = key.charAt(0).toUpperCase() + key.substring(1, key.length);
  return this["get" + Key]();
};

// base method wrapper.
var _base = function(args, varags)
{
  if (arguments.length === 1)
    return args.callee.base.call(this);
  else
    return args.callee.base.apply(this, Array.prototype.slice.call(arguments, 1));
};

// self method wrapper.
var _self = function(args)
{
  return args.callee.self;
};

//! Creates new object.
//!
//! @note This method is mapped to @c BLite.Object.define.
var _define = function(classname, map)
{
  // variables from map
  var base = map.extend || Object,
      classtype = map.type || "",
      construct = map.construct,
      destruct = map.destruct,
      members = map.members || {},
      statics = map.statics || {},
      properties = map.properties || {};

  if (destruct) map.members["destruct"] = destruct;

  if (!classname) throw new Error("Invalid class name");

  // Create namespace
  var parts = classname.split("."), part, type;
  var cur = window, i = 0, len = parts.length;

  for (;;)
  {
    part = parts[i];
    type = typeof cur[part];

    // last part (class) ?
    if (++i >= len) break;

    // namespace can exists
    if (type === "object" || type === "function")
      cur = cur[part];
    // only undefined and nulls are allowed (correct)
    else if (type === "undefined" || type === "null")
      cur = cur[part] = {};
    // error
    else
      throw new Error('Part "' + part + '" of class "' + classname + '" is not object.');
  }

  // Create constructor
  var o = construct || _create(), p, f = new Function;

  f.prototype = base.prototype;
  o.prototype = p = new f;

  // Copy members
  for (var key in members)
  {
    var f = members[key];
    if (f instanceof Function)
    {
      f.base = _findBase(base, key);
      f.self = o;
    }
    p[key] = f;
  }

  // Copy statics
  for (var key in statics)
  {
    var f = statics[key];
    if (f instanceof Function)
    {
      f.self = o;
    }
    o[key] = f;
  }

  // Add properties
  var prop;

  o._properties = properties;
  for (var key in properties)
  {
    if ((prop =_findProperty(base, key)))
      throw new Error('Property "' + key + '" in "' + classname + '" already defined in base class');

    _addProperty(o, key, properties[key])
  }

  // Finalize
  o.classname   = p.classname   = classname;
  o.basename    = p.basename    = parts.slice(0, len-1).join(".");
  o.constructor = p.constructor = o;
  o.superclass  = p.superclass  = base;

  p.set = _set;
  p.get = _get;

  o.base = base;
  o.self = o;

  p.base = _base;
  p.self = _self;

  // Save class object namespace (or to window)
  cur[part] = o;

  // Singleton
  if (classtype === "singleton") _makeSingleton(o);
  if (classtype === "autosingleton") _makeAutoSingleton(o);

  return o;
};

// make object by our internal _define. _define() will be public as 
// BLite.Object.define().
_define("BLite.Object",
{
  //! @brief Constructor.
  construct: function()
  {
    B.Object.$$registry[(this._uid = B.Object.generateUid())] = this;
    this._listeners = {};
  },

  members:
  {
    //! @brief Dispose (destroy) object.
    //!
    //! This method calls class destructors and disposes object from BLite
    //! object registry.
    dispose: function()
    {
      var d = this.prototype.destruct;
      while (d) d.call(this); d = d.base;

      var registry = B.Object.$$registry;
      if (registry[this._uid]) delete registry[this._uid];

      delete this._uid;
      delete this._listeners;
    },

    //! @brief Return object UID (Unique Identifier).
    //!
    //! UIDs are automatically generated by BLite for each new object. Two
    //! objects never have same UID.
    //!
    //! @return {String} Object UID.
    getUid: function()
    {
      return this._uid;
    },

    //! @brief Add event listener.
    //! @param name {String} Name of event to listen.
    //! @param func {Function} Listener function (event handler).
    //! @param context {Object} Listener context (this argument).
    //! @param once {Boolean} Should be event fired only once?
    //! @return {Object} Returns itself.
    addListener: function(name, func, context, once)
    {
      var listeners = this._listeners[name] || (this._listeners[name] = []);
      listeners.push({func: func, context: context, once: once || false});

      // chaining
      return this;
    },

    //! @brief Remove event listener.
    //! @param name {String} Name of event to listen.
    //! @param func {Function} Listener function (event handler).
    //! @param context {Object} Listener context (this argument).
    //! @return {Boolean} Whether listener was removed.
    removeListener: function(name, func, context)
    {
      var listeners;
      if ((listeners = this._listeners[name]))
      {
        for (var i = 0, len = listeners.length; i++; i < len)
        {
          var listener = listeners[i];
          if (listener.func === func && listener.context === context)
          {
            listener.func = null;
            listener.context = null;
            listeners.splice(i, 1);
            return true;
          }
        }
      }
      return false;
    },

    //! @brief Test in object contains listener for event @a name.
    //! @param name {String} Listener name to test.
    //! @return {Boolean} Whether object contains listener @a name.
    hasListener: function(name)
    {
      var listeners;
      if ((listeners = this._listeners[name]) && listeners.length)
        return true;
      else
        return false;
    },

    //! @brief Method used to dispatch event.
    //! @param e {BLite.Event.Base} Event to dispatch
    //! @param recycle {Boolean} Should be event pooled?
    //!
    //! If @a recycle parameter is @c true, event object @a e is pooled using
    //! BLite.Pool.recycle(e) code.
    dispatchEvent: function(e, recycle)
    {
      var listeners, l, name = e._name;
      if ((listeners = this._listeners[name]) && listeners.length)
      {
        for (var i = 0; i < listeners.length;)
        {
          l = listeners[i];
          l.func.call(l.context, e);

          if (l.once)
          {
            l.func = null;
            l.context = null;
            listeners.splice(i, 1);
          }
          else
            i++;
        }
        l = null;
      }
      if (recycle) B.Pool.recycle(e);
    },

    //! @brief Fire @c BLite.Event.Base event (no parameters).
    fireEvent: function(name)
    {
      if (this.hasListener(name))
      {
        var e = B.Pool.create(B.Event.Base);
        e._name = name;
        this.dispatchEvent(e, true);
      }
    },

    //! @brief Fire @c BLite.Event.Dom filled with @a domEvent.
    //! @param name {String} Name of event to fire.
    //! @param domEvent {DOMEvent} Original DOM event to wrap.
    fireDomEvent: function(name, domEvent)
    {
      if (this.hasListener(name))
      {
        var e = B.Pool.create(B.Event.Dom);
        e._name = name;
        e._domEvent = domEvent;
        this.dispatchEvent(e, true);
      }
    },

    //! @brief Fire @c BLite.Event.Data filled with @a data and @a old.
    //! @param name {String} Name of event to fire.
    //! @param data {var} Data.
    //! @param old {var} Old data (optional).
    fireDataEvent: function(name, data, old)
    {
      if (this.hasListener(name))
      {
        var e = B.Pool.create(B.Event.Data);
        e._name = name;
        e._data = data;
        e._old = old;
        this.dispatchEvent(e, true);
      }
    }
  },

  statics:
  {
    //! @brief Define new class.
    //! @param classname {String} Name of class with all namespaces.
    //! @param map {Object} Dictionary.
    //!
    //! TODOC
    define: _define,

    //! @brief Object registry (UID to object mapping).
    //! @internal.
    $$registry: {},

    //! @brief Object UID counter.
    //! @internal.
    $$uid: 0,

    //! @brief Generate new UID string
    //! @return {String} New UID.
    generateUid: function()
    {
      return (this.$$uid++).toString(36);
    },

    //! @brief Return object from UID.
    //! @return {Object} Object or null.
    getByUid: function(uid)
    {
      return this.$$registry[uid];
    }
  }
});

// ===========================================================================
// [BLite.Event.Base]
// ===========================================================================

// Base event for all BLite events (not DOM events).
B.Object.define("BLite.Event.Base",
{
  extend: B.Object,

  properties:
  {
    //! @brief Name of event.
    name: {}
  }
});

// ===========================================================================
// [BLite.Event.Dom]
// ===========================================================================

// Wrapper for DOM event.
B.Object.define("BLite.Event.Dom",
{
  extend: B.Event.Base,

  properties:
  {
    //! @brief Wrapped DOM event.
    domEvent: {}
  }
});

// ===========================================================================
// [BLite.Event.Data]
// ===========================================================================

// Object data event.
B.Object.define("BLite.Event.Data",
{
  extend: B.Event.Base,

  properties:
  {
    //! @brief Data.
    data: {},

    //! @brief Old data
    //!
    //! Used if event notifies about property/data change.
    oldData: {}
  }
});

// ===========================================================================
// [BLite.Browser]
// ===========================================================================

// Browser detection. There is only important detection for browsers, not
// browser versions and other stuff.

var $OPERA = !!(window.opera);
var $IE = !!(window.attachEvent && !$OPERA);

// [Features Detection]
var $detectCanvas = function()
{
  var c = Dom.CANVAS();
  var hasCanvas = !!(c.getContext);
  var hasImageData = hasCanvas && !!(c.getContext("2d").getImageData);

  B.Browser.canvas = function() { return hasCanvas; };
  B.Browser.canvasImageData = function() { return hasImageData; };

  return B.Browser;
};

//! @brief Client browser and features detection.
B.Object.define("BLite.Browser",
{
  statics:
  {
    // [Browser Detection]
    
    //! @brief {Boolean} IE browser.
    IE: $IE,

    //! @brief {Boolean} Opera browser.
    OPERA: $OPERA,

    //! @brief Checks if <canvas> element is supported.
    //!
    //! @return {Boolean} Whether browser supports <canvas> tag.
    canvas: function()
    {
      return $detectCanvas().canvas();
    },

    //! @brief Checks if <canvas> element contains getImageData() 
    //! and setImageData() method.
    //!
    //! @return {Boolean} Whether canvas supports getImageData()/setImageData().
    canvasImageData: function()
    {
      return $detectCanvas().canvasImageData();
    }
  }
});

// ===========================================================================
// [BLite.Dom - Private Section]
// ===========================================================================

// DOM Ready private variables and functions are created here, because BLite
// Object implementation needs them in some cases ("autosigleton" object type)

var $domReady = 0;
var $domReadyTimer = null;
var $domReadyFuncs = [];

var $domOnReady = function()
{
  if ($domReady++) return;
  $domReadyExec();
};

var $domReadyExec = function()
{
  for (var i=0, len=$domReadyFuncs.length; i < len; i++) $domReadyFuncs[i](B);
  $domReadyFuncs = [];
};

if (document.addEventListener)
{
  document.addEventListener("DOMContentLoaded", $domOnReady, false);
}
if (/KHTML|WebKit|iCab/i.test(navigator.userAgent))
{
  $domReadyTimer = setInterval(function()
  {
    if (/loaded|complete/i.test(document.readyState)) $domReady++;
    if ($domReady)
    {
      clearInterval($domReadyTimer);
      $domReadyTimer = null;
      $domReadyExec();
    }
  }, 10);
}

window.onload = $domOnReady;

// DOM Builder private variables and functions

// Ignore these atributes in dom builder
var $domIgnore =
{
  "class": 1,
  "Class": 1,
  "className": 1,
  "styles": 1,
  "events": 1,
  "as": 1,
  "text": 1,
  "html": 1,
  "children": 1
};

// Replace these attributes in dom builder
var $domFix =
{
  "for": "htmlFor",
  "For": "htmlFor",
  "colspan": "colSpan",
  "rowspan": "rowSpan",
  "cellspacing": "cellSpacing",
  "cellpadding": "cellPadding"
};

// Create shortcuts for DIV, A, FORM, etc...
var $domTags =
[
  "a", "br", "button", "canvas", "caption", "div", "em", "fieldset",
  "form", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "iframe", "img",
  "input", "label", "legend", "li", "ol", "optgroup", "option", "p",
  "pre", "select", "span", "strong", "table", "tbody", "td", "textarea",
  "tfoot", "th", "thead", "tr", "tt", "ul"
];

// Internal for each function
var $domForEachAttr = function(attributes, fn)
{
  for (var attr in attributes)
  {
    if ($domIgnore[attr]) continue;
    var value = attributes[attr];
    attr = $domFix[attr] || attr;
    fn(attr, (value instanceof Boolean) ? attr : value);
  }
};

// Cache for cloneNode()
var $domCache = {};
// private unique ID generator counter
var $domCounter = 0;
// object building stack
var $domStack = [];
// last object in stack (optimization)
var $o = null;

var $_domArraySyntax = /([^\[]*)\[([0-9]*)\]$/;

// Private method that creates a new TAG element
var $domCreate = function(tag, args, start)
{
  var a = args,
      e,
      attributes = a[start] || {},
      events = attributes["events"] || null,
      className = attributes["class"] || attributes["Class"] || attributes["className"] || null,
      styles = attributes["styles"] || null,
      _as = attributes["as"] || null,
      text = attributes["text"] || null,
      html = attributes["html"] || null;

  if ($IE && tag === "input")
  {
    // IE <input> support
    var sb = ['<', tag];
    $domForEachAttr(attributes, function(attr, value)
    {
      sb.push(' ', attr, '="', B.Html.escape(value), '"');
    });
    if (className) sb.push(' class="' + B.Html.escape(className) + '"');
    sb.push('>');
    e = document.createElement(sb.join(""));
  }
  else
  {
    e = ($domCache[tag] || ($domCache[tag] = document.createElement(tag))).cloneNode(false);

    $domForEachAttr(attributes, function(attr, value) { e.setAttribute(attr, value); });
    if (className) e.className = className;
  }

  Dom.setStyles(e, styles);
  for (var key in events) Dom.addListener(e, key, events[key], $o);

  // object assignment
  if ($o && _as && _as.length > 0)
  {
    var m;
    // array syntax
    if ((m = _as.match($_domArraySyntax)))
    {
      var _name = m[1];
      // Create array if needed
      if (!($o[_name] instanceof Array)) $o[_name] = [];

      // Add to the end - "[]" syntax
      if (m[2] === "")
        $o[_name].push(e)
      // Add to the specific position - "[number]" syntax
      else
        $o[_name][parseInt(m[2])] = e;
    }
    // direct syntax
    else
      $o[_as] = e;
  }

  if (html)
    Dom.setHtml(e, html);
  else if (text)
    Dom.setText(e, text);

  if (attributes.children) Dom.append(e, attributes.children);
  return e;
};

// This method can be used to support new tags like DIV, A, IMG, etc...
var $domDefine = function(tag)
{
  Dom[tag.toUpperCase()] = function() { return $domCreate(tag, arguments, 0); };
};

var $domGetElementsByClassName = null;

if (document.getElementsByClassName)
{
  $domGetElementsByClassName = function (className, tag, elm)
  {
    elm = elm || document;
    var elements = elm.getElementsByClassName(className),
      nodeName = (tag)? new RegExp("\\b" + tag + "\\b", "i") : null,
      returnElements = [],
      current;
    for (var i=0, il=elements.length; i<il; i++)
    {
      current = elements[i];
      if(!nodeName || nodeName.test(current.nodeName))
      {
        returnElements.push(current);
      }
    }
    return returnElements;
  };
}
else if (document.evaluate)
{
  $domGetElementsByClassName = function(className, tag, elm)
  {
    tag = tag || "*";
    elm = elm || document;
    var classes = className.split(" "),
      classesToCheck = "",
      xhtmlNamespace = "http://www.w3.org/1999/xhtml",
      namespaceResolver = (document.documentElement.namespaceURI === xhtmlNamespace)? xhtmlNamespace : null,
      returnElements = [],
      elements,
      node;

    for (var j=0, jl=classes.length; j<jl; j++)
    {
      classesToCheck += "[contains(concat(' ', @class, ' '), ' " + classes[j] + " ')]";
    }

    try {
      elements = document.evaluate(".//" + tag + classesToCheck, elm, namespaceResolver, 0, null);
    }
    catch (ex) {
      elements = document.evaluate(".//" + tag + classesToCheck, elm, null, 0, null);
    }

    while ((node = elements.iterateNext())) returnElements.push(node);
    return returnElements;
  };
}
else
{
  $domGetElementsByClassName = function(className, tag, elm)
  {
    tag = tag || "*";
    elm = elm || document;

    var classes = className.split(" "),
      classesToCheck = [],
      elements = (tag === "*" && elm.all)? elm.all : elm.getElementsByTagName(tag),
      current,
      returnElements = [],
      match;

    for (var k=0, kl=classes.length; k<kl; k++)
    {
      classesToCheck.push(new RegExp("(^|\\s)" + classes[k] + "(\\s|$)"));
    }

    for (var l=0, ll=elements.length; l<ll; l++)
    {
      current = elements[l];
      match = false;

      for (var m=0, ml=classesToCheck.length; m<ml; m++)
      {
        match = classesToCheck[m].test(current.className);
        if (!match) break;
      }

      if (match) returnElements.push(current);
    }
    return returnElements;
  };
}

// Events private variables and functions

var $euid = 0;

// ===========================================================================
// [BLite.Dom]
// ===========================================================================

Dom = B.Object.define("BLite.Dom",
{
  statics:
  {
    // --------------------------------------------------------------------------
    // [Builder]
    // --------------------------------------------------------------------------

    //! @brief Begin of using DOM builder.
    //!
    //! @note This is not needed if you are not using "as" attribute.
    begin: function(context)
    {
      $domStack.push(($o = context));
      return this;
    },

    //! @brief End of using DOM builder.
    //!
    //! Internally calls $domBuild() private function to generate hashes for
    //! "as" atributes.
    end: function()
    {
      if ($domStack.length) $o = $domStack[--$domStack.length] || null;
      return this;
    },

    //! @brief Create text node containing @a text.
    TEXT: function(text)
    {
      return document.createTextNode(text)
    },

    // --------------------------------------------------------------------------
    // [Id]
    // --------------------------------------------------------------------------

    //! @brief Return ID for element @a e.
    //!
    //! If element @a e has no ID, the unique ID will be created.
    id: function(e)
    {
      return e.id || (e.id = "BLite-UID-" + ($domCounter++).toString(36));
    },

    // --------------------------------------------------------------------------
    // [Elements by ID or Class]
    // --------------------------------------------------------------------------

    elm: function(e)
    {
      if (typeof e === "string")
        return document.getElementById(e);
      else
        return e;
    },

    elmsByClass: $domGetElementsByClassName,

    // --------------------------------------------------------------------------
    // [Html / Text]
    // --------------------------------------------------------------------------

    getHtml: function(e)
    {
      return e.innerHTML;
    },

    setHtml: function(e, html)
    {
      e.innerHTML = html;
    },

    getText: function(e)
    {
      if (e)
      {
        return e.innerText || e.textContent || "";
      }
    },

    setText: function(e, text)
    {
      if (e)
      {
        if ($IE)
          e.innerText = text;
        else
          e.textContent = text;
      }
    },

    // --------------------------------------------------------------------------
    // [Manipulation]
    // --------------------------------------------------------------------------

    // Returns first child in 'e'. Optionaly second parameter
    // can be TAG that will be used to filter result.
    first: function(e, tag)
    {
      if (e && (e = e.firstChild) && tag && e.nodeName !== tag.toUpperCase()) e = Dom.next(e, tag);
      return e;
    },

    // Returns last child in 'e'. Optionaly second parameter
    // can be TAG that will be used to filter result.
    last: function(e, tag)
    {
      if (e && (e = e.lastChild) && tag && e.nodeName !== tag.toUpperCase()) e = Dom.prev(e, tag);
      return e;
    },

    // Parent
    parent: function(e, tag, stop)
    {
      if (e) while (e != stop && (e = e.parentNode)) { if (!tag || e.nodeName === tag.toUpperCase()) break; }
      if (e == stop) return null;
      return e;
    },

    // Returns previous element
    prev: function(e, tag, stop)
    {
      if (e) while (e != stop && (e = e.previousSibling)) { if (!tag || e.nodeName === tag.toUpperCase()) break; }
      if (e == stop) return null;
      return e;
    },

    // Returns next element
    next: function(e, tag, stop)
    {
      if (e) while (e != stop && (e = e.nextSibling)) { if (!tag || e.nodeName === tag.toUpperCase()) break; }
      if (e == stop) return null;
      return e;
    },

    // Create a document element and assign optional attributes that can
    // contain styles and events.
    //
    // @param tag
    // @param attr
    // @param children
    create: function()
    {
      return $domCreate(arguments[0], arguments, 1);
    },

    // Appends 'children' to 'e'
    append: function(e, ch)
    {
      var i, len, c;
      if (!(ch instanceof Array)) ch = [ch];

      for (i = 0, len = ch.length; i < len; i++) { c = ch[i]; e.appendChild(typeof c === "string" ? Dom.TEXT(c) : c); }
      return this;
    },

    insertBefore: function(refNode, newNode)
    {
      refNode.parentNode.insertBefore(newNode, refNode);
    },

    insertAfter: function(refNode, newNode)
    {
      refNode.parentNode.insertBefore(newNode, refNode.nextSibling || null);
    },

    remove: function(e, ch)
    {
      e.removeChild(ch);
    },

    // Prepends 'children' to 'e'
    prepend: function(e, ch)
    {
      var i, c;
      if (!(ch instanceof Array)) ch = [ch];

      for (i = ch.length - 1; i > 0; i--) { c = ch[i]; e.prependChild(typeof c === "string" ? Dom.TEXT(c) : c); }
      return this;
    },

    // Returns true if child is in 'e'. Scan is deep.
    childIn: function(e, ch)
    {
      while (ch)
      {
        if (ch == e) return true;
        ch = ch.parentNode;
      }
      return false;
    },

    clear: function(e)
    {
      if ($IE)
        while (e.childNodes.length) e.removeChild(e.childNodes[0]);
      else
        e.innerHTML = "";
    },

    // --------------------------------------------------------------------------
    // [CSS]
    // --------------------------------------------------------------------------

    setClass: function(elm, className)
    {
      if (elm) elm.className = className;
    },

    getClass: function(elm)
    {
      return elm ? elm.className : "";
    },

    hasClass: function(elm, className)
    {
      if(!elm || !elm.className) return false;
      var eClassName = elm.className;

      return (eClassName.length > 0 && (eClassName == className ||
        new RegExp("(^|\\s)" + className + "(\\s|$)").test(eClassName)));
    },

    addClass: function(elm, className)
    {
      var currentClass = elm.className;
      if (!new RegExp(("(^|\\s)" + className + "(\\s|$)"), "i").test(currentClass)) {
        elm.className = currentClass + (currentClass.length? " " : "") + className;
      }
    },

    removeClass: function(elm, className)
    {
      var classToRemove = new RegExp(("(^|\\s)" + className + "(\\s|$)"), "i");
      elm.className = elm.className.replace(classToRemove, function (match) {
        var retVal = "";
        if (new RegExp("^\\s+.*\\s+$").test(match)) {
          retVal = match.replace(/(\s+).+/, "$1");
        }
        return retVal;
      }).replace(/^\s+|\s+$/g, "");
    },

    // --------------------------------------------------------------------------
    // [Styles]
    // --------------------------------------------------------------------------

    setStyle: function(elm, style, value)
    {
      if (elm) elm.style[style] = value;
    },
    
    setStyles: function(elm, styles)
    {
      for (var style in styles) { Dom.setStyle(elm, style, styles[style]); }
    },

    getStyle: function(elm, style)
    {
      return elm ? elm.style[style] : undefined;
    },

    // --------------------------------------------------------------------------
    // [Events]
    // --------------------------------------------------------------------------

    addListener: function(elm, type, func, context, once)
    {
      var euid = $euid++;
      // if (once) handler.$$once = true;
      // if (!handler.$$euid) handler.$$euid = euid;
      if (!elm.$$events) elm.$$events = {};

      // set window context if it's not given      
      context = context || window;

      var handlers = elm.$$events[type];
      if (!handlers)
      {
        handlers = elm.$$events[type] = {};
        if (elm["on" + type])
        {
          handlers[0] = {
            func: elm["on" + type],
            context: context,
            once: false
          }
        };
      }

      var hobj = {
        func: func,
        context: context,
        once: once || false,
        euid: euid
      };

      handlers[euid] = hobj;
      elm["on" + type] = Dom.handleEvent;
      elm = null;
    },

    removeListener: function(elm, type, func, context, all)
    {
      var events;

      // set window context if it's not given      
      context = context || window;

      if ((events = elm.$$events) && (events = events[type]))
      {
        for (var key in events)
        {
          var hobj = events[key];
          if (hobj.func === func && hobj.context === context)
          {
            // If we are removing currently dispatched event,
            // set once attribute instead of removing it here
            if (Dom.$$calling === hobj)
              hobj.once = true;
            else
              delete events[key];

            if (!all) break;
          }
        }

        // if this was last event, remove handlers completely
        for (var key in events) { return; }
        delete elm.$$events[type];
        elm["on" + type] = null;
      }
    },

    handleEvent: function(e)
    {
      var e = e || window.event;
      var eTarget = e.target || e.srcElement || document;
      while (eTarget.nodeType !== 1 && eTarget.parentNode) eTarget = eTarget.parentNode;
      e.eventTarget = eTarget;

      // Keyboard
      e.key = e.keyCode ? e.keyCode : e.charCode;
      e.ctrl = e.ctrlKey;
      e.shift = e.shiftKey;

      if (e.key)
      {
        switch (e.key)
        {
          case 63232:
            e.key = 38;
            break;
          case 63233:
            e.key = 40;
            break;
          case 63235:
            e.key = 39;
            break;
          case 63234:
            e.key = 37;
            break;
        }
      }

      // Mouse event
      if (e.offsetX === undefined)
      {
        var et = eTarget;
        var x = 0, y = 0;

        while (et.offsetParent)
        {
          x += et.offsetLeft;
          y += et.offsetTop;
          et = et.offsetParent;
        };
        e.offsetX = e.pageX - x;
        e.offsetY = e.pageY - y;
      }

      var self = this;
      var handlers = this.$$events[e.type];
      var toDelete = {};

      for (var i in handlers)
      {
        var handler = handlers[i];

        Dom.$$calling = handler;
        handler.func.call(handler.context, e);
        delete Dom.$$calling;

        if (handler.once)
        {
          delete handler.func;
          delete handler.context;
          toDelete[handler.euid] = true;
        }
      }

      for (var i in toDelete)
      {
        delete self.$$events[e.type][i];
      }
    },

    eventTarget: function(e, tag)
    {
      var elm = e.eventTarget;
      if (tag && elm.nodeName !== tag.toUpperCase()) elm = Dom.parent(elm, tag);
      return elm;
    },

    relatedTarget: function(e, tag)
    {
      var elm = e.relatedTarget || e.fromElement;
      if (tag && e.nodeName !== tag.toUpperCase()) elm = Dom.parent(elm, tag);
    },

    resolveTextNode: function(elm)
    {
      try { if (elm && elm.nodeType == 3) return elm.parentNode; } catch(ex) { }
      return elm;
    },

    preventDefault: function(evt)
    {
      if (evt.preventDefault)
        evt.preventDefault();
      else
        evt.returnValue = false;
    },

    stopPropagation: function(evt)
    {
      if (evt.stopPropagation)
        evt.stopPropagation();
      else
        evt.cancelBubble = true;
    },

    // --------------------------------------------------------------------------
    // [Ready]
    // --------------------------------------------------------------------------

    ready: function(fn)
    {
      if ($domReady)
        fn();
      else
        $domReadyFuncs.push(fn);
    },

    // --------------------------------------------------------------------------
    // [Misc
    // --------------------------------------------------------------------------

    // Extracts action in element class string.
    //
    // For example if element has class "menu action-show", this function
    // will return "show".
    act: function(s, prefix)
    {
      if (s)
      {
        var p = prefix || "action-", start = s.indexOf(p), end;
        if (start != -1)
        {
          start += p.length;
          end = s.indexOf(" ", start);
          return s.substring(start, (end === -1) ? s.length : end);
        }
      }
      return null;
    }
  }
});

// Create DOM shortcuts
for(var i = $domTags.length; i;) $domDefine($domTags[--i]);

// ===========================================================================
// [BLite.Array]
// ===========================================================================

B.Object.define("BLite.Array",
{
  statics:
  {
    indexOf: function(a, cond, context)
    {
      for (var i = 0, len = a.length; i < len; i++)
        if (cond.call(context, a[i])) return i;
      return -1;
    }
  }
});

// ===========================================================================
// [BLite.Pool]
// ===========================================================================

B.Object.define("BLite.Pool",
{
  statics:
  {
    $$clazz: {},

    create: function(clazz)
    {
      var pool = this.$$clazz[clazz.classname];

      if (pool && pool.length)
        return pool.pop();
      else
        return new clazz();
    },

    recycle: function(inst)
    {
      var pool, classname = inst.classname;

      if (typeof inst.recycle === "function") obj.recycle();

      if ((pool = this.$$clazz[classname]) === undefined)
      {
        this.$$clazz[classname] = pool = [];
      }

      pool.push(inst);
    },

    cleanup: function(classname)
    {
      var a;
      if ((a = this.$$clazz[classname]))
      {
        for (var i = 0, len = a.legth; i < len; i++)
        {
          if (a[i] instanceof B.Object) a[i].dispose();
          a[i] = null;
        }
        delete this.$$clazz[classname]
      }
    }
  }
});

// ===========================================================================
// [BLite.Timer]
// ===========================================================================

B.Object.define("BLite.Timer",
{
  statics:
  {
    create: function(fn, context, interval, once)
    {
      var f = function() { fn.call(context); };

      if (once)
        return setTimeout(f, interval);
      else
        return setInterval(f, interval);
    },

    destroy: function(id)
    {
      if (id != null) clearInterval(id)
    }
  }
});

// ===========================================================================
// [BLite.Html]
// ===========================================================================

B.Object.define("BLite.Html",
{
  statics:
  {
    escape: function(str)
    {
      return str.replace(/&/g, "&amp;").
                 replace(/>/g, "&gt;").
                 replace(/</g, "&lt;").
                 replace(/\"/g, "&quot;");
    }
  }
});

// ===========================================================================
// [BLite.Url]
// ===========================================================================

B.Object.define("BLite.Url",
{
  statics:
  {
    //! Creates url address from url and GET parameters.
    make: function(url, params)
    {
      var a = [];

      for (var name in params)
      {
        var param = params[name];
        a.push(
          encodeURIComponent(name) +
          "=" +
          encodeURIComponent(param));
      }

      if (a.length)
        url += ((url.indexOf("?") === -1) ? "?" : "&") + a.join("&");

      return url;
    }
  }
});

// ===========================================================================
// [BLite.Request]
// ===========================================================================

B.Object.define("BLite.Request",
{
  statics:
  {
    __xmlHttpTry:
    [
      function() { return new XMLHttpRequest(); },
      function() { return new ActiveXObject("Msxml2.XMLHTTP"); },
      function() { return new ActiveXObject("Microsoft.XMLHTTP"); },
      function() { return new ActiveXObject("Msxml2.XMLHTTP.4.0"); }
    ],

    __xmlHttpGet: function()
    {
      var fn = B.Request.__xmlHttpTry;
      for (var i = 0; i < fn.length; i++)
      {
        try { return fn[i](); }
        catch (ex) {}
      }
      return null;
    },

    __encodeURI: function(map)
    {
      var a = [];

      for (var name in map)
      {
        var param = map[name];
        if (!(param instanceof Array)) param = [param];

        for (var i = 0, len = param.length; i < len; i++)
          a.push(encodeURIComponent(name) + "=" + encodeURIComponent(param[i]));
      }

      return a.join("&");
    },

    send: function(opt)
    {
      var url = opt.url;
      var method = opt.method || "GET";
      var data = opt.data || "";
      var req = B.Request.__xmlHttpGet();
      var context = opt.context || document;

      req.open(method.toUpperCase(), url, true);
      if (method === "POST")
      {
        req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
      }

      var onreadystatechange = function()
      {
        if (req.readyState == 4)
        {
          var status = "";

          try { status = req.status; }
          catch (ex) {};

          if (status == 200 || status == 304 || req.responseText == null)
          {
            if (opt.success) opt.success.call(context, req, opt.args);
          }
          else
          {
            if (opt.failure) opt.failure.call(context, req, opt.args);
          }
        }
      }

      req.onreadystatechange = onreadystatechange;
      req.send((typeof data === "string") ? data : B.Request.__encodeURI(data));

      return req;
    }
  }
});

// ===========================================================================
// [BLite.Json]
// ===========================================================================

// Json module is based on the YAHOO Json module.
//
// Copyright (c) 2008, Yahoo! Inc. All rights reserved.
// Code licensed under the BSD License:
// http://developer.yahoo.net/yui/license.txt

//! Provides methods to encode / decode JSON.
B.Object.define("BLite.Json",
{
  statics:
  {
    //! First step in the validation.  Regex used to replace all escape
    //! sequences (i.e. "\\", etc) with '@' characters (a non-JSON character).
    //!
    //! @type {RegExp}
    _ESCAPES: /\\["\\\/bfnrtu]/g,

    //! Second step in the validation.  Regex used to replace all simple
    //! values with ']' characters.
    //!
    //! @type {RegExp}
    _VALUES: /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,

    //! Third step in the validation.  Regex used to remove all open square
    //! brackets following a colon, comma, or at the beginning of the string.
    //!
    //! @type {RegExp}
    _BRACKETS: /(?:^|:|,)(?:\s*\[)+/g,

    //! Final step in the validation.  Regex used to test the string left after
    //! all previous replacements for invalid characters.
    //!
    //! @type {RegExp}
    _INVALID: /^[\],:{}\s]*$/,

    //! Regex used to replace special characters in strings for JSON
    //! stringification.
    //!
    //! @type {RegExp}
    _SPECIAL_CHARS: /["\\\x00-\x1f\x7f-\x9f]/g,

    //! Regex used to reconstitute serialized Dates.
    //!
    //! @type {RegExp}
    _PARSE_DATE: /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/,

    //! Character substitution map for common escapes and special characters.
    //!
    //! @type {Object}
    _CHARS: {
      '\b': '\\b',
      '\t': '\\t',
      '\n': '\\n',
      '\f': '\\f',
      '\r': '\\r',
      '"' : '\\"',
      '\\': '\\\\'
    },

    //! Traverses nested objects, applying a filter or mutation function to
    //! each value.  The value returned from the function will replace the
    //! original value in the key:value pair.  If the value returned is
    //! undefined, the key will be omitted from the returned object.
    //!
    //! @param data {MIXED} Any JavaScript data
    //! @param filter {Function} filter or mutation function
    //! @return {MIXED} The results of the filtered data
    _applyFilter : function (data, filter)
    {
      var walk = function (k,v)
      {
        var i, n;
        if (v && typeof v === 'object')
        {
          for (i in v)
          {
            if (Object.prototype.call(v, i))
            {
              n = walk(i, v[i]);
              if (n === undefined)
                delete v[i];
              else
                v[i] = n;
            }
          }
        }
        return filter(k, v);
      };

      if (typeof filter === "function") walk('', data);

      return data;
    },

    //! Four step determination whether a string is valid JSON.  In three steps,
    //! escape sequences, safe values, and properly placed open square brackets
    //! are replaced with placeholders or removed.  Then in the final step, the
    //! result of all these replacements is checked for invalid characters.
    //! @param str {String} JSON string to be tested
    //! @return {boolean} is the string safe for eval?
    isValid : function (str)
    {
      return this._INVALID.test(str.
             replace(this._ESCAPES,'@').
             replace(this._VALUES,']').
             replace(this._BRACKETS,''));
    },

    //! Serializes a Date instance as a UTC date string.  Used internally by
    //! encode. Override this method if you need Dates serialized in a
    //! different format.
    //! @param d {Date} The Date to serialize
    //! @return {String} stringified Date in UTC format YYYY-MM-DDTHH:mm:SSZ
    dateToString: function(d)
    {
      function _zeroPad(v)
      {
        return v < 10 ? '0' + v : v;
      }

      return '"' + d.getUTCFullYear() + '-' +
        _zeroPad(d.getUTCMonth() + 1) + '-' +
        _zeroPad(d.getUTCDate())      + 'T' +
        _zeroPad(d.getUTCHours())     + ':' +
        _zeroPad(d.getUTCMinutes())   + ':' +
        _zeroPad(d.getUTCSeconds())   + 'Z"';
    },

    //! Reconstitute Date instances from the default JSON UTC serialization.
    //! Reference this from a decode filter function to rebuild Dates during the
    //! decode operation.
    //! @param str {String} String serialization of a Date
    //! @return {Date}
    stringToDate : function (str)
    {
      if (this._PARSE_DATE.test(str))
      {
        var d = new Date();

        d.setUTCFullYear(RegExp.$1, (RegExp.$2|0)-1, RegExp.$3);
        d.setUTCHours(RegExp.$4, RegExp.$5, RegExp.$6);

        return d;
      }
    },

    //! Decode a JSON string, returning the native JavaScript representation.
    //! Only minor modifications from http://www.json.org/json.js.
    //! @param s {string} JSON string data
    //! @param filter {function} (optional) function(k,v) passed each key value pair of object literals, allowing pruning or altering values
    //! @return {MIXED} the native JavaScript representation of the JSON string
    //! @throws SyntaxError
    decode: function(s, filter)
    {
      // Ensure valid JSON
      if (this.isValid(s))
        // Eval the text into a JavaScript data structure, apply any
        // filter function, and return
        return this._applyFilter(eval('(' + s + ')'), filter);
      else
        // The text is not JSON parsable
        throw new SyntaxError("BLite.Json.decode");
    },

    //! Converts an arbitrary value to a JSON string representation.
    //! Cyclical object or array references are replaced with null.
    //! If a whitelist is provided, only matching object keys will be included.
    //! If a depth limit is provided, objects and arrays at that depth will
    //! be stringified as empty.
    //! @param o {MIXED} any arbitrary object to convert to JSON string
    //! @param w {Array} (optional) whitelist of acceptable object keys to include
    //! @param d {number} (optional) depth limit to recurse objects/arrays (practical minimum 1)
    //! @return {string} JSON string representation of the input
    encode: function(o, w, d)
    {
      var J      = B.Json,
          m      = J._CHARS,
          str_re = this._SPECIAL_CHARS,
          pstack = []; // Processing stack used for cyclical ref protection

      // escape encode special characters
      var _char = function(c)
      {
        if (!m[c])
        {
          var a = c.charCodeAt();
          m[c] = '\\u00' + Math.floor(a / 16).toString(16) + (a % 16).toString(16);
        }
        return m[c];
      };

      // Enclose the escaped string in double quotes
      var _string = function(s)
      {
        return '"' + s.replace(str_re, _char) + '"';
      };

      // Use the configured date conversion
      var _date = J.dateToString;

      // Worker function.  Fork behavior on data type and recurse objects and
      // arrays per the configured depth.
      var _stringify = function(o, w, d)
      {
        var t = typeof o,
            i, len, j, // array iteration
            k, v,      // object iteration
            vt,        // typeof v during iteration
            a;         // composition array for performance over string concat

        // String
        if (t === 'string') return _string(o);

        // native boolean and Boolean instance
        if (t === 'boolean' || o instanceof Boolean) return String(o);

        // native number and Number instance
        if (t === 'number' || o instanceof Number) return isFinite(o) ? String(o) : 'null';

        // Date
        if (o instanceof Date) return _date(o);

        // Array
        if (o instanceof Array)
        {
          // Check for cyclical references
          for (i = pstack.length - 1; i >= 0; --i)
          {
            if (pstack[i] === o) return 'null';
          }

          // Add the array to the processing stack
          pstack[pstack.length] = o;

          a = [];

          // Only recurse if we're above depth config
          if (d > 0)
          {
            for (i = o.length - 1; i >= 0; --i)
            {
              a[i] = _stringify(o[i],w,d-1) || 'null';
            }
          }

          // remove the array from the stack
          pstack.pop();

          return '[' + a.join(',') + ']';
        }

        // Object
        if (t === 'object')
        {
          // Test for null reporting as typeof 'object'
          if (!o) return 'null';

          // Check for cyclical references
          for (i = pstack.length - 1; i >= 0; --i)
          {
            if (pstack[i] === o) return 'null';
          }

          // Add the object to the  processing stack
          pstack[pstack.length] = o;

          a = [];
          // Only recurse if we're above depth config
          if (d > 0)
          {
            // If whitelist provided, take only those keys
            if (w)
            {
              for (i = 0, j = 0, len = w.length; i < len; ++i)
              {
                if (typeof w[i] === 'string')
                {
                  v = _stringify(o[w[i]],w,d-1);
                  if (v) a[j++] = _string(w[i]) + ':' + v;
                }
              }
            }
            // Otherwise, take all valid object properties
            // omitting the prototype chain properties
            else
            {
              j = 0;
              for (k in o)
              {
                if (typeof k === 'string' && Object.prototype.hasOwnProperty.call(o, k))
                {
                  v = _stringify(o[k],w,d-1);
                  if (v) a[j++] = _string(k) + ':' + v;
                }
              }
            }
          }

          // Remove the object from processing stack
          pstack.pop();

          return '{' + a.join(',') + '}';
        }

        return undefined; // invalid input
      };

      // Default depth to POSITIVE_INFINITY
      if (d === undefined || d < 0) d = Infinity;

      // process the input
      return "" + _stringify(o, w, d);
    }
  }
});

// ===========================================================================
// [BLite.Cookie]
// ===========================================================================

B.Object.define("BLite.Cookie",
{
  statics:
  {
    set: function(name, value, days, path)
    {
      var s = [name + "=" + escape(value)];

      if (path === undefined) path = "/";

      if (days)
      {
        var date = new Date();
        date.setTime(date.getTime()+(days*86400000));
        s.push("expires=" + date.toGMTString());
      }

      if (path)
      {
        s.push("path=" + path);
      }

      document.cookie = s.join("; ");
    },

    get: function(name)
    {
      var c = B.Cookie.all()[name];
      return (c === undefined) ? null : c;
    },

    del: function(name)
    {
      B.Cookie.set(name,"", -1);
    },

    all: function()
    {
      var result = {};
      var ca = document.cookie.split(';');

      for (var i = 0; i < ca.length; i++)
      {
        var pair = ca[i].split("=");
        result[pair[0].replace(/^\s*/, '').replace(/\s*$/, '')] = unescape(pair[1]);
      }
      return result;
    }
  }
});

// ===========================================================================
// [BLite.$]
// ===========================================================================

B.$ = Dom.elm;

// ===========================================================================
// [BLite - End of Wrap]
// ===========================================================================

})();

