(function() {
  var base_elements;
  var base_selector;
  var error;

  function _$(selectors) {
    base_elements = this.elements = [];
    this.selectors = selectors;

    error = this.error;

    for(var index = 0; index < selectors.length; index++) {
      var selector = selectors[index];

      if(this.isArray(selector)) {
        for(index in selector) this._addSelector(selector[index]);
      } else {
        this._addSelector(selector);
      }
    }

    return this;
  };

  _$.prototype = {
    //
    // Debugging -------------------------------------------------------------->
    //
    debugging: false,

    error: function(e, description) { if(this.debugging) alert("Error: " + e.description + "\n\n" + description) },

    //
    // Core ------------------------------------------------------------------->
    //
    _resetBaseElements: function() {
      base_elements = this.elements;

      return this;
    },

    _setElements: function(element_set) {
      base_elements = this.elements = element_set;

      return this;
    },

    _addSelector: function(selector) {
      switch(typeof selector) {
        case 'object':
          this._addElement(selector);
          break;
        case 'string':
          base_selector = selector;
          this._setElements( this._getElements(selector) );
          break;
      }

      return this;
    },

    _addDocument: function() {
      if(this.elements.length < 1) this._addElement(document);

      return this;
    },

    _addElements: function(elements) {
      for(index in elements) this._addElement(elements[index]);

      return this;
    },

    _addElement: function(element) {
      if(typeof element == 'object') this.elements.push(element);

      this._resetBaseElements();

      return this;
    },

    _multiReturn: function(set) { return (set.length > 1) ? set : set[0] },

    each: function(func) {
      var count = (this.elements.length < 1) ? 1 : this.elements.length;

      for(var index = 0; index < count; index++) try { func.call(this, this.elements[index]) } catch(e) { error(e, "Iterator failed for: " + func) }

      return this;
    },

    length: function() { return this.elements.length },

    first: function() { return this.elements[0] },

    isBlank: function(value) { return (value == null || value == undefined) },

    type: function() {
      var types = [];

      this.each(function(element) {
        try {
          types.push(element.nodeName.toLowerCase());
        } catch(e) { error(e) }
      });

      return this._multiReturn(types);
    },

    //
    // Events ----------------------------------------------------------------->
    //
    on: function(event, func) {
      var listenTo = function(element, event) {
	  
		if (element.type === "checkbox" && event === "blur")
		  event = "click";

        // Mozilla ->
        if( window.addEventListener ) {
          if(event == 'ready') event = 'DOMContentLoaded';

          element.addEventListener(event, func, true);

        // IE ->
        } else if(window.attachEvent) {
          if(event == 'ready') {
            element.attachEvent('onreadystatechange', function() {
              if(element.readyState === 'complete') {
                element.detachEvent('onreadystatechange', arguments.callee);
                func.call(element, window.event);
              }
            });
          } else {
            element.attachEvent('on'+event, function() {
              func.call(element, window.event);
            });
          }
        }
      }

      this.each(function(element) { try { listenTo(element, event) } catch(e) { error(e) } });
    },

    ready: function(func) { this.on('ready', func) },

    blur: function(func) { this.on('blur', func) },

    focus: function(func) { this.on('focus', func) },

    click: function(func) { this.on('click', func) },

    change: function(func) { this.on('change', func) },

    keyup: function(func) { this.on('keyup', func) },

    mouseOver: function(func) { this.on('mouseover', func) },

    mouseOut: function(func) { this.on('mouseout', func) },

    submit: function(func) { this.on('submit', func) },

    //
    // Manipulation ----------------------------------------------------------->
    //
    isArray: function(object) { return ( object != undefined && object.constructor == Array ) },

    remove: function() {
      this.each(function(element) {
        var parent_node = $(element).parent();

        // Cleanup, try to remove the aQ container where it is found
        if( parent_node.getClass() == 'aq-injection' ) {
          element = parent_node.first();
        }

        if(element.parentNode) element.parentNode.removeChild(element);
      });

      return this;
    },

    stripTags: function(html) {
      if( this.isBlank(html) ) var html = this.html();

      var text = html.replace(/<\/?[^>]+(>|$)/g, "");
      return this.removeWhiteSpace(text);
    },

    removeWhiteSpace: function(text) {
      return text.replace(/^\s*|\s*$/g,'')
    },

    removeClass: function(klass) {
      try {
        this.each(function(element) {
          if(element.className){
            classNames = element.className.split(' ');
            newClassNames = [];
            for(i=0;i < classNames.length;i++){
              if(klass != classNames[i]) newClassNames.push(classNames[i]);
            }
            element.className = newClassNames.join(' ');
          }
        });
      } catch(e) {
        this.error(e);
      }

      return this;
    },

    addClass: function(klass) {
      try {
        this.removeClass(klass);
        this.each(function(element) { element.className += " " + klass });
      } catch(e) {
        this.error(e);
      }

      return this;
    },

    hasClass: function(klass) {
      var result = false;

      try {
        this.each(function(element) {
          var element = $(element);		  
          if( element.array.indexOf(element.getClass(), klass) >= 0 ) result = true;
        });
      } catch(e) {
        this.error(e);
      }

      return result;
    },

    getClass: function(index) {
      var classes = [];

      try {
        this.each(function(element) {
          var element_classes = element.className.split(" ");

          for(i in element_classes) {
            if(element_classes[i].length > 0) classes.push(element_classes[i]);
          }
        });

        classes = this._multiReturn( classes );

        return (this.isArray(classes) && index >= 0) ? classes[index] : classes;
      } catch(e) {
        this.error(e);
      }
    },

    hide: function() { this.each(function(element) { element.style.display = 'none' }) },

    show: function() { this.each(function(element) { element.style.display = '' }) },

    style: function(style) { this.each(function(element) { element.setAttribute('style', style) }) },

    attr: function(attribute, value) {
      var attributes = [];
      var no_value   = this.isBlank(value);

      this.each(function(element) {
        try {
          if(no_value) {
            attributes.push( element.getAttribute(attribute) );
          } else {
            element.setAttribute(attribute, value);
          }
        } catch(e) { error(e) }
      });

      return this._multiReturn(attributes);
    },

    value: function(value) {
      var values    = [];
      var no_value  = this.isBlank(value);

      this.each(function(element) {
        try {
          if(no_value) {
            values.push( element.value );
          } else {
            element.value = value;
          }
        } catch(e) { error(e) }
      });

      return value ? this : this._multiReturn(values);
    },

    text: function() {
      if(this.type() == "select") {
        return this.first().options[this.first().selectedIndex].text
      }
    },

    uniqueValues: function() {
      var values = this.value().sort();

      for(index in values) {
        if(values[index-1] == values[index]) values.splice(index, 1);
      }

      return values;
    },

    checked: function() {
      var checks = [];

      this.each(function(element) {
        checks.push(element.checked);
      });

      return this._multiReturn(checks);
    },

    createElement: function(html) { alert("Not implemented") },

    after: function(new_element) { alert("Not implemented") },

    before: function(new_element) { alert("Not implemented") },

    prepend: function(html) {
      this.each(function(element) {
        var fragment  = document.createDocumentFragment();
        var container = document.createElement('span');

        container.className = 'aq-injection';
        container.innerHTML = html;

        fragment.appendChild( container );
        element.insertBefore( fragment, element.firstChild );
      })
    },

    append: function(html) {
      this.each(function(element) {
        var fragment  = document.createDocumentFragment();
        var container = document.createElement('span');

        container.setAttribute('class', 'aq-injection');
        container.innerHTML = html;

        fragment.appendChild( container );
        element.appendChild( fragment );
      });

      return this;
    },

    html: function(html) {
      var element = this.first();

      if(element == null) return null;

      if(html == null) {
        return element.innerHTML;
      } else {
        element.innerHTML = html;
      }

      return this;
    },

    //
    // Traversing ------------------------------------------------------------->
    //
    matches: function(selector) {
      switch(selector.split('')[0]) {
        case '.':
          return this.hasClass( selector.substr(1) );
          break;
        case '#':
          return this.attr('id') == selector.substr(1);
          break;
        default:
          return this.type() == selector;
          break;
      }
    },

    visible: function() {
        var width = this.first().offsetWidth, height = this.first().offsetHeight;

        if (width === 0 && height === 0) return false;
        if (this.first().style.display === "none") return false;

        return true;
    },

    parent: function() { return $(this.first().parentNode) },

    parents: function(selector) {
      var matches = [];

      this.each(function(element) {
        var parent = $(element).parent();

        if( parent.matches(selector) ) {
          matches.push(parent);
        } else {
          matches.push( parent.parents(selector) );
        }
      })

      return this._multiReturn( this.array.flatten( matches ) );
    },

    parentWithSelector: function(selector) {
      var matches = [], cur = this.first().parentNode;
      while(cur) {
        if (cur.nodeName.toLowerCase() == selector) {
          matches.push($(cur));
        }
        cur = cur.parentNode;
      }
      if(matches.length == 1) {
        matches = matches[0];
      }
      return matches;
    },

    positionOf: function(target) {
      return this.elements.indexOf( target.elements[0] );
    },

    find: function(selector) { return $(this._getElements(selector)) },

    // TODO: Make this nice...
    _collect: function(collection1, collection2) {
      var collection = [];

      for( var i = 0; i < collection1.length; i++){
	 if(typeof collection1[i] == "object") collection.push(collection1[i]);
      }
      
      for( var i = 0; i < collection2.length; i++){
	 if(typeof collection2[i] == "object") collection.push(collection2[i]);
      }

      return collection;

    },

    // TODO: Break this out into a few methods, tidy it up...
    _getElements: function(selector) {
      var matched_elements = [];
      var selectors        = selector.split(/,\s+/);

      for(index in selectors) {
        var selector    = selectors[index];
        if(selector != undefined){
          var new_matches = [];

          switch(selector.split('')[0]) {
            case '.':
              new_matches = this._getElementsByClass(selector.substr(1));
              break;
            case '#':
              new_matches = this._getElementById(selector.substr(1));
              break;
            default:
              new_matches = this._getElementsByTagName(selector);
              break;
          }
        }
        matched_elements = this._collect(matched_elements, new_matches);
      }

      return matched_elements;
    },
	
	_getElementsByTagName: function(selector) {
      var matched_elements = [];

      this.each(function(element) {
        var matched = ( element || document ).getElementsByTagName(selector);

		for(var i = 0; i < matched.length; i++)
			matched_elements.push(matched[i]);

      });
	
	  return matched_elements;	
	},

    _getElementById: function(id) {
      var matched_elements = [];

      this.each(function(element) {
        var matched = ( element || document ).getElementById(id);

        if(matched) matched_elements.push(matched);
      });

      return matched_elements;
    },

    _getElementsByClass: function(klass, scope) {
      var matched_elements = [];

      if(scope == null) scope = '*';

      this.each(function(element) {
        var all_elements = ( element || document ).getElementsByTagName(scope);
        var pattern      = new RegExp("(^|\\s)"+klass+"(\\s|$)");

        for(index in all_elements) {
          var node = all_elements[index];

          if(typeof node == 'undefined') continue;

          if(pattern.test(node.className)) matched_elements.push(node);
        }
      });

      return matched_elements;
    },

    //
    // Misc ------------------------------------------------------------------->
    //
    contains: function(string) {
      var matcher = new RegExp(string, 'ig');
      var result  = false;

      this.each(function(element) {
        if(matcher.test( $(element).html() )) result = true;
      });

      return result;
    },

    doesNotContain: function(string) { return !this.contains(string) },

    //
    // Math ------------------------------------------------------------------->
    //
    isOdd: function(number) { return ((number*1) % 2) ? true : false },

    isEven: function(number) { return this.isOdd(number) ? false : true },

    //
    // Client detection ------------------------------------------------------->
    //
    isIE:      function() { return /*@cc_on!@*/false },
    isIE6:     function() { return /msie|MSIE 6/.test(navigator.userAgent) },
    isFirefox: function() { return /Firefox/i.test(navigator.userAgent) },

    //
    // Array ------------------------------------------------------------------>
    //

    array: {

      // TODO: Make this work in IE!
      flatten: function(array) {
        var flat = [];

        if( !$().isArray(array) ) return array;

        for (index in array){
          var type = Object.prototype.toString.call(array[index]).split(' ').pop().split(']').shift().toLowerCase();

          if (type) {
            flat = flat.concat(/^(array|collection|arguments|object)$/.test(type) ? flatten(array[index]) : array[index]);
          }
        }

        return flat;
      },

      compare: function(first, second) {
        if(first.length != second.length) return false;

        for(index in first) {
          if(first[index] != second[index]) return false;
        }

        return true;
      },

      indexOf: function(haystack, needle) {
        return this.index(haystack, needle);
      },

      index: function(haystack, needle) {
        for(var index in haystack){
          if(haystack[index] == needle) return index;
        }

        return -1;
      },

      compact: function(array) {
        var new_array = [];

        for(index in array) {
          if(array[index].length > 0) new_array.push( array[index] );
        }

        return new_array;
      }

    }
  };

  window.$ = function() { return new _$(arguments) };

})();


