var global = this;
var dom = (function() {
  var _i;

  function isFunction(value) {return 'function' == typeof value;}
  function isGenericObject(value) {return isObject(value) || isFunction(value);}
  function isObject(value) {return !!value && ('object' == typeof value);}
  function isUndefined(value) {return 'undefined' == typeof value;}

  _i = {
    isFunction : isFunction,
    isGenericObject : isGenericObject,
    isUndefined : isUndefined,
    core : (function() {
      return {
        getElementById : isGenericObject(document.getElementById)
                       ? function(id) {return document.getElementById(id);}
                       : isGenericObject(document.all)
                       ? function(id) {
                           var result = document.all[id];

                           if(result) {
                             if(isGenericObject(result.item)) {
                               for(var i = 0, n = result.length; i < n; ++i) {
                                 if(id == result[i].id) {return result[i];}
                               }
                             } else if(id == result.id) {
                               return result;
                             }
                           }
                           return null;
                         }
                       : function() {return null;},
        getElementsByTagName : (function() {
          function dummy() {return [];}
          function tryDOM(ancestor, tagName) {
            var elements = useDOM(ancestor, tagName);

            if('*' == tagName) {
              if(elements.length) {
                this.getElementsByTagName = useDOM;
              } else {
                elements = useTags(ancestor, tagName);

                if(elements.length) {this.getElementsByTagName = useTags;}
              }
            }
            return elements;
          }
          function useDOM(ancestor, tagName) {
            return ancestor.getElementsByTagName(tagName);
          }
          function useTags(ancestor, tagName) {
            return ('*' == tagName)
              ? ancestor.all
              : ancestor.all.tags(tagName);
          }
          return function(ancestor, tagName) {
            var replacement;

            if(isGenericObject(ancestor.getElementsByTagName)) {
              if(isGenericObject(ancestor.all) && isGenericObject(ancestor.all.tags)) {
                replacement = tryDOM;
              } else {
                replacement = useDOM;
              }
            } else if(isGenericObject(ancestor.all) && isGenericObject(ancestor.all.tags)) {
              replacement = useTags;
            } else {
              replacement = dummy;
            }
            this.getElementsByTagName = replacement;

            return this.getElementsByTagName(ancestor, tagName);
          };
        })()
      };
    })(),
    events : (function() {
      var DispatcherFactory = (function() {
        function Node(data) {
          var next = null;

          this.fire = function(element, event) {
            var performDefault = next
                               ? next.fire(element, event)
                               : true;

            return data.call(element, event) && performDefault;
          };

          this.add = function(listener) {
            if(data == listener) {return;}

            if(next) {
              next.add(listener);
            } else {
              next = new Node(listener);
            }
          };
          this.remove = function(listener) {
            if(data == listener) {
              return next;
            } else if(next) {
              next = next.remove(listener);
            }
            return this;
          };
        }

        function attachDispatcher(element, type) {
          var listener = element[type];

          if(isFunction(listener)) {
            if(this.constructor != listener.constructor) {
              this.add(listener);
            } else {
              if(this != listener) {
                listener.add(this);
              }
              return;
            }
          }
          element[type] = this;
        }
        function detachDispatcher(element, type) {
          var listener = element[type];

          if(this == listener) {
            element[type] = null;
          } else if(this.constructor == listener.constructor) {
            listener.remove(this);
          }
        }

        return {
          createDispatcher : function() {
            var list = null;

            function Dispatcher(event) {
              return list
                ? list.fire(this, event || global.event)
                : true;
            }
            Dispatcher.constructor = this;

            Dispatcher.add = function(listener) {
              if(list) {
                list.add(listener);
              } else {
                list = new Node(listener);
              }
            };
            Dispatcher.remove = function(listener) {
              if(list) {
                list = list.remove(listener);
              }
            };

            Dispatcher.attach = attachDispatcher;
            Dispatcher.detach = detachDispatcher;

            return Dispatcher;
          },
          isDispatcher : function(object) {
            return isFunction(object) && (this == object.constructor);
          }
        };
      })();

      return {
        addEventListener : function(eventTarget, type, listener, useCapture) {
          if(isFunction(eventTarget.addEventListener)) {
            eventTarget.addEventListener(type, listener, useCapture);
          } else {
            var dispatcher = eventTarget[type = 'on' + type];

            if(!DispatcherFactory.isDispatcher(dispatcher)) {
              dispatcher = DispatcherFactory.createDispatcher();
              dispatcher.attach(eventTarget, type);
            }
            dispatcher.add(listener, useCapture);
          }
        },
        removeEventListener : function(eventTarget, type, listener, useCapture) {
          if(isFunction(eventTarget.removeEventListener)) {
            eventTarget.removeEventListener(type, listener, useCapture);
          } else {
            var dispatcher = eventTarget[type = 'on' + type];

            if(DispatcherFactory.isDispatcher(dispatcher)) {
              dispatcher.remove(listener, useCapture);
            }
          }
        }
      };
    })(),
    html : (function() {
      return {
        getElementsByClassName : function(ancestor, className, tagName) {
          var classMatch = new RegExp('(^|\s+)' + className + '(\s+|$)'),
              collection = _i.core.getElementsByTagName(ancestor, isUndefined(tagName) ? '*' : tagName),
              elements   = [];

          for(var i = 0, n = collection.length; i < n; ++i) {
            if(classMatch.test(collection[i].className)) {
              elements.push(collection[i]);
            }
          }
          return elements;
        },
        ofClass : function(element, className) {
          return (new RegExp('(^|\s+)' + className + '(\s+|$)')).test(element.className);
        }
      };
    })(),
    style : (function() {
      return {
        getInline : function(element, property) {
          var value;

          if(element.style) {
            value = element.style[property];
          } else {
            value = element[' $style$' + property];
          }
          return value;
        },
        setInline : function(element, property, value) {
          if(element.style) {
            element.style[property] = value;
          } else {
            element[' $style$' + property] = value;
          }
        }
      };
    })()
  };
  return _i;
})();

if(!dom.isFunction(Function.prototype.call)) {
  Function.prototype.call = function(object) {
    var property = ' $call',
        result, undef;

    object = object || global;

    while(!dom.isUndefined(object[property])) {property += property;}
    object[property] = this;

    switch(arguments.length - 1) {
      case 0:
        result = object[property]();
        break;
      case 1:
        result = object[property](arguments[1]);
        break;
      default: alert('Too many arguments!');
    }
    object[property] = undef;

    return result;
  };
}
if(!dom.isFunction(Array.prototype.push)) {
  Array.prototype.push = function() {
    var i = this.length, j = 0, n = arguments.length;

    while(j < n) {this[i++] = arguments[j++];}
    return i;
  };
}
