var Selector = Class.create();
Selector.prototype = {
  initialize: function(expression) {
    this.params = {classNames: []};
    this.expression = expression.toString().strip();
    this.parseExpression();
    this.compileMatcher();
  },
  
  parseExpression: function() {
    function abort(message) { throw 'Parse error in selector: ' + message; }
    
    if (this.expression == '')  abort('empty expression');
    if (this.expression == '*') return this.params.wildcard = true;
      
    var params = this.params, expr = this.expression, match, modifier, clause, rest;
    while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) {
      modifier = match[1], clause = match[2], rest = match[3];
      switch (modifier) {
        case '#':       params.id = clause; break;
        case '.':       params.classNames.push(clause); break;
        case undefined: params.tagName = clause.toUpperCase(); break;
        default:        abort(expr.inspect());
      }
      expr = rest;
    }
    
    if (expr.length > 0) abort(expr.inspect());
  },
  
  buildMatchExpression: function() {
    var params = this.params, conditions = [], clause;

    if (params.wildcard)
      return 'true';
   
    if (clause = params.id)
      conditions.push('element.id == ' + clause.inspect());
    if (clause = params.tagName)
      conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
    if ((clause = params.classNames).length > 0)
      for (var i = 0; i < clause.length; i++)
        conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')');
    
    return conditions.join(' && ');
  },

  compileMatcher: function() {
    this.match = eval('function(element) { if (!element.tagName) return false; \
      return ' + this.buildMatchExpression() + ' }');
  },
  
  findElements: function(scope) {
    var element;
    
    if (element = $(this.params.id))
      if (this.match(element))
        if (!scope || Element.childOf(element, scope))
          return [element];
    
    scope = (scope || document).getElementsByTagName(this.params.tagName || '*');
    
    var results = [];
    for (var i = 0; i < scope.length; i++)
      if (this.match(element = scope[i]))
        results.push(element);
    
    return results;
  },
  
  toString: function() {
    return this.expression;
  }
}

function $$() {
  return $A(arguments).map(function(expression) {
    return expression.strip().split(/\s+/).inject([null], function(results, expr) {
      var selector = new Selector(expr);
      return results.map(selector.findElements.bind(selector)).flatten();
    });
  }).flatten();
}

