

// Underscore.js
// (c) 2009 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the terms of the MIT license.
// Portions of Underscore are inspired by or borrowed from Prototype.js,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore/

(function() {

  /*------------------------- Baseline setup ---------------------------------*/

  // Establish the root object, "window" in the browser, or "global" on the server.
  var root = this;

  // Save the previous value of the "_" variable.
  var previousUnderscore = root._;

  // If Underscore is called as a function, it returns a wrapped object that
  // can be used OO-style. This wrapper holds altered versions of all the
  // underscore functions. Wrapped objects may be chained.
  var wrapper = function(obj) { this._wrapped = obj; };

  // Establish the object that gets thrown to break out of a loop iteration.
  var breaker = typeof StopIteration !== 'undefined' ? StopIteration : '__break__';

  // Create a safe reference to the Underscore object for reference below.
  var _ = root._ = function(obj) { return new wrapper(obj); };

  // Export the Underscore object for CommonJS.
  if (typeof exports !== 'undefined') exports._ = _;

  // Current version.
  _.VERSION = '0.4.5';

  /*------------------------ Collection Functions: ---------------------------*/

  // The cornerstone, an each implementation.
  // Handles objects implementing forEach, arrays, and raw objects.
  _.each = function(obj, iterator, context) {
    var index = 0;
    try {
      if (obj.forEach) {
        obj.forEach(iterator, context);
      } else if (obj.length) {
        for (var i=0, l = obj.length; i<l; i++) iterator.call(context, obj[i], i, obj);
      } else {
        for (var key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) {
          iterator.call(context, obj[key], key, obj);
        }
      }
    } catch(e) {
      if (e != breaker) throw e;
    }
    return obj;
  };

  // Return the results of applying the iterator to each element. Use JavaScript
  // 1.6's version of map, if possible.
  _.map = function(obj, iterator, context) {
    if (obj && obj.map) return obj.map(iterator, context);
    var results = [];
    _.each(obj, function(value, index, list) {
      results.push(iterator.call(context, value, index, list));
    });
    return results;
  };

  // Reduce builds up a single result from a list of values. Also known as
  // inject, or foldl. Uses JavaScript 1.8's version of reduce, if possible.
  _.reduce = function(obj, memo, iterator, context) {
    if (obj && obj.reduce) return obj.reduce(_.bind(iterator, context), memo);
    _.each(obj, function(value, index, list) {
      memo = iterator.call(context, memo, value, index, list);
    });
    return memo;
  };

  // The right-associative version of reduce, also known as foldr. Uses
  // JavaScript 1.8's version of reduceRight, if available.
  _.reduceRight = function(obj, memo, iterator, context) {
    if (obj && obj.reduceRight) return obj.reduceRight(_.bind(iterator, context), memo);
    var reversed = _.clone(_.toArray(obj)).reverse();
    _.each(reversed, function(value, index) {
      memo = iterator.call(context, memo, value, index, obj);
    });
    return memo;
  };

  // Return the first value which passes a truth test.
  _.detect = function(obj, iterator, context) {
    var result;
    _.each(obj, function(value, index, list) {
      if (iterator.call(context, value, index, list)) {
        result = value;
        _.breakLoop();
      }
    });
    return result;
  };

  // Return all the elements that pass a truth test. Use JavaScript 1.6's
  // filter(), if it exists.
  _.select = function(obj, iterator, context) {
    if (obj.filter) return obj.filter(iterator, context);
    var results = [];
    _.each(obj, function(value, index, list) {
      iterator.call(context, value, index, list) && results.push(value);
    });
    return results;
  };

  // Return all the elements for which a truth test fails.
  _.reject = function(obj, iterator, context) {
    var results = [];
    _.each(obj, function(value, index, list) {
      !iterator.call(context, value, index, list) && results.push(value);
    });
    return results;
  };

  // Determine whether all of the elements match a truth test. Delegate to
  // JavaScript 1.6's every(), if it is present.
  _.all = function(obj, iterator, context) {
    iterator = iterator || _.identity;
    if (obj.every) return obj.every(iterator, context);
    var result = true;
    _.each(obj, function(value, index, list) {
      if (!(result = result && iterator.call(context, value, index, list))) _.breakLoop();
    });
    return result;
  };

  // Determine if at least one element in the object matches a truth test. Use
  // JavaScript 1.6's some(), if it exists.
  _.any = function(obj, iterator, context) {
    iterator = iterator || _.identity;
    if (obj.some) return obj.some(iterator, context);
    var result = false;
    _.each(obj, function(value, index, list) {
      if (result = iterator.call(context, value, index, list)) _.breakLoop();
    });
    return result;
  };

  // Determine if a given value is included in the array or object,
  // based on '==='.
  _.include = function(obj, target) {
    if (_.isArray(obj)) return _.indexOf(obj, target) != -1;
    var found = false;
    _.each(obj, function(value) {
      if (found = value === target) _.breakLoop();
    });
    return found;
  };

  // Invoke a method with arguments on every item in a collection.
  _.invoke = function(obj, method) {
    var args = _.rest(arguments, 2);
    return _.map(obj, function(value) {
      return (method ? value[method] : value).apply(value, args);
    });
  };

  // Convenience version of a common use case of map: fetching a property.
  _.pluck = function(obj, key) {
    return _.map(obj, function(value){ return value[key]; });
  };

  // Return the maximum item or (item-based computation).
  _.max = function(obj, iterator, context) {
    if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
    var result = {computed : -Infinity};
    _.each(obj, function(value, index, list) {
      var computed = iterator ? iterator.call(context, value, index, list) : value;
      computed >= result.computed && (result = {value : value, computed : computed});
    });
    return result.value;
  };

  // Return the minimum element (or element-based computation).
  _.min = function(obj, iterator, context) {
    if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
    var result = {computed : Infinity};
    _.each(obj, function(value, index, list) {
      var computed = iterator ? iterator.call(context, value, index, list) : value;
      computed < result.computed && (result = {value : value, computed : computed});
    });
    return result.value;
  };

  // Sort the object's values by a criteria produced by an iterator.
  _.sortBy = function(obj, iterator, context) {
    return _.pluck(_.map(obj, function(value, index, list) {
      return {
        value : value,
        criteria : iterator.call(context, value, index, list)
      };
    }).sort(function(left, right) {
      var a = left.criteria, b = right.criteria;
      return a < b ? -1 : a > b ? 1 : 0;
    }), 'value');
  };

  // Use a comparator function to figure out at what index an object should
  // be inserted so as to maintain order. Uses binary search.
  _.sortedIndex = function(array, obj, iterator) {
    iterator = iterator || _.identity;
    var low = 0, high = array.length;
    while (low < high) {
      var mid = (low + high) >> 1;
      iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
    }
    return low;
  };

  // Convert anything iterable into a real, live array.
  _.toArray = function(iterable) {
    if (!iterable) return [];
    if (_.isArray(iterable)) return iterable;
    return _.map(iterable, function(val){ return val; });
  };

  // Return the number of elements in an object.
  _.size = function(obj) {
    return _.toArray(obj).length;
  };

  /*-------------------------- Array Functions: ------------------------------*/

  // Get the first element of an array. Passing "n" will return the first N
  // values in the array. Aliased as "head".
  _.first = function(array, n) {
    return n ? Array.prototype.slice.call(array, 0, n) : array[0];
  };

  // Returns everything but the first entry of the array. Aliased as "tail".
  // Especially useful on the arguments object. Passing an "index" will return
  // the rest of the values in the array from that index onward.
  _.rest = function(array, index) {
    return Array.prototype.slice.call(array, _.isUndefined(index) ? 1 : index);
  };

  // Get the last element of an array.
  _.last = function(array) {
    return array[array.length - 1];
  };

  // Trim out all falsy values from an array.
  _.compact = function(array) {
    return _.select(array, function(value){ return !!value; });
  };

  // Return a completely flattened version of an array.
  _.flatten = function(array) {
    return _.reduce(array, [], function(memo, value) {
      if (_.isArray(value)) return memo.concat(_.flatten(value));
      memo.push(value);
      return memo;
    });
  };

  // Return a version of the array that does not contain the specified value(s).
  _.without = function(array) {
    var values = _.rest(arguments);
    return _.select(array, function(value){ return !_.include(values, value); });
  };

  // Produce a duplicate-free version of the array. If the array has already
  // been sorted, you have the option of using a faster algorithm.
  _.uniq = function(array, isSorted) {
    return _.reduce(array, [], function(memo, el, i) {
      if (0 == i || (isSorted ? _.last(memo) != el : !_.include(memo, el))) memo.push(el);
      return memo;
    });
  };

  // Produce an array that contains every item shared between all the
  // passed-in arrays.
  _.intersect = function(array) {
    var rest = _.rest(arguments);
    return _.select(_.uniq(array), function(item) {
      return _.all(rest, function(other) {
        return _.indexOf(other, item) >= 0;
      });
    });
  };

  // Zip together multiple lists into a single array -- elements that share
  // an index go together.
  _.zip = function() {
    var args = _.toArray(arguments);
    var length = _.max(_.pluck(args, 'length'));
    var results = new Array(length);
    for (var i=0; i<length; i++) results[i] = _.pluck(args, String(i));
    return results;
  };

  // If the browser doesn't supply us with indexOf (I'm looking at you, MSIE),
  // we need this function. Return the position of the first occurence of an
  // item in an array, or -1 if the item is not included in the array.
  _.indexOf = function(array, item) {
    if (array.indexOf) return array.indexOf(item);
    for (var i=0, l=array.length; i<l; i++) if (array[i] === item) return i;
    return -1;
  };

  // Provide JavaScript 1.6's lastIndexOf, delegating to the native function,
  // if possible.
  _.lastIndexOf = function(array, item) {
    if (array.lastIndexOf) return array.lastIndexOf(item);
    var i = array.length;
    while (i--) if (array[i] === item) return i;
    return -1;
  };

  /* ----------------------- Function Functions: -----------------------------*/

  // Create a function bound to a given object (assigning 'this', and arguments,
  // optionally). Binding with arguments is also known as 'curry'.
  _.bind = function(func, context) {
    var args = _.rest(arguments, 2);
    return function() {
      return func.apply(context || root, args.concat(_.toArray(arguments)));
    };
  };

  // Bind all of an object's methods to that object. Useful for ensuring that
  // all callbacks defined on an object belong to it.
  _.bindAll = function() {
    var context = Array.prototype.pop.call(arguments);
    _.each(arguments, function(methodName) {
      context[methodName] = _.bind(context[methodName], context);
    });
  };

  // Delays a function for the given number of milliseconds, and then calls
  // it with the arguments supplied.
  _.delay = function(func, wait) {
    var args = _.rest(arguments, 2);
    return setTimeout(function(){ return func.apply(func, args); }, wait);
  };

  // Defers a function, scheduling it to run after the current call stack has
  // cleared.
  _.defer = function(func) {
    return _.delay.apply(_, [func, 1].concat(_.rest(arguments)));
  };

  // Returns the first function passed as an argument to the second,
  // allowing you to adjust arguments, run code before and after, and
  // conditionally execute the original function.
  _.wrap = function(func, wrapper) {
    return function() {
      var args = [func].concat(_.toArray(arguments));
      return wrapper.apply(wrapper, args);
    };
  };

  // Returns a function that is the composition of a list of functions, each
  // consuming the return value of the function that follows.
  _.compose = function() {
    var funcs = _.toArray(arguments);
    return function() {
      for (var i=funcs.length-1; i >= 0; i--) {
        arguments = [funcs[i].apply(this, arguments)];
      }
      return arguments[0];
    };
  };

  /* ------------------------- Object Functions: ---------------------------- */

  // Retrieve the names of an object's properties.
  _.keys = function(obj) {
    return _.map(obj, function(value, key){ return key; });
  };

  // Retrieve the values of an object's properties.
  _.values = function(obj) {
    return _.map(obj, _.identity);
  };

  // Extend a given object with all of the properties in a source object.
  _.extend = function(destination, source) {
    for (var property in source) destination[property] = source[property];
    return destination;
  };

  // Create a (shallow-cloned) duplicate of an object.
  _.clone = function(obj) {
    if (_.isArray(obj)) return obj.slice(0);
    return _.extend({}, obj);
  };

  // Perform a deep comparison to check if two objects are equal.
  _.isEqual = function(a, b) {
    // Check object identity.
    if (a === b) return true;
    // Different types?
    var atype = typeof(a), btype = typeof(b);
    if (atype != btype) return false;
    // Basic equality test (watch out for coercions).
    if (a == b) return true;
    // One of them implements an isEqual()?
    if (a.isEqual) return a.isEqual(b);
    // Both are NaN?
    if (_.isNumber(a) && _.isNumber(b) && isNaN(a) && isNaN(b)) return true;
    // If a is not an object by this point, we can't handle it.
    if (atype !== 'object') return false;
    // Nothing else worked, deep compare the contents.
    var aKeys = _.keys(a), bKeys = _.keys(b);
    // Different object sizes?
    if (aKeys.length != bKeys.length) return false;
    // Recursive comparison of contents.
    for (var key in a) if (!_.isEqual(a[key], b[key])) return false;
    return true;
  };

  // Is a given array or object empty?
  _.isEmpty = function(obj) {
    return (_.isArray(obj) ? obj : _.values(obj)).length == 0;
  };

  // Is a given value a DOM element?
  _.isElement = function(obj) {
    return !!(obj && obj.nodeType == 1);
  };

  // Is a given value a real Array?
  _.isArray = function(obj) {
    return Object.prototype.toString.call(obj) == '[object Array]';
  };

  // Is a given value a Function?
  _.isFunction = function(obj) {
    return Object.prototype.toString.call(obj) == '[object Function]';
  };

  // Is a given value a String?
  _.isString = function(obj) {
    return Object.prototype.toString.call(obj) == '[object String]';
  };

  // Is a given value a Number?
  _.isNumber = function(obj) {
    return Object.prototype.toString.call(obj) == '[object Number]';
  };

  // Is a given variable undefined?
  _.isUndefined = function(obj) {
    return typeof obj == 'undefined';
  };

  /* -------------------------- Utility Functions: -------------------------- */

  // Run Underscore.js in noConflict mode, returning the '_' variable to its
  // previous owner. Returns a reference to the Underscore object.
  _.noConflict = function() {
    root._ = previousUnderscore;
    return this;
  };

  // Keep the identity function around for default iterators.
  _.identity = function(value) {
    return value;
  };

  // Break out of the middle of an iteration.
  _.breakLoop = function() {
    throw breaker;
  };

  // Generate a unique integer id (unique within the entire client session).
  // Useful for temporary DOM ids.
  var idCounter = 0;
  _.uniqueId = function(prefix) {
    var id = idCounter++;
    return prefix ? prefix + id : id;
  };

  // Return a sorted list of the function names available in Underscore.
  _.functions = function() {
    var functions = [];
    for (var key in _) if (Object.prototype.hasOwnProperty.call(_, key)) functions.push(key);
    return _.without(functions, 'VERSION', 'prototype', 'noConflict').sort();
  };

  // JavaScript templating a-la ERB, pilfered from John Resig's
  // "Secrets of the JavaScript Ninja", page 83.
  _.template = function(str, data) {
    var fn = new Function('obj',
      'var p=[],print=function(){p.push.apply(p,arguments);};' +
      'with(obj){p.push(\'' +
      str
        .replace(/[\r\t\n]/g, " ")
        .split("<%").join("\t")
        .replace(/((^|%>)[^\t]*)'/g, "$1\r")
        .replace(/\t=(.*?)%>/g, "',$1,'")
        .split("\t").join("');")
        .split("%>").join("p.push('")
        .split("\r").join("\\'")
    + "');}return p.join('');");
    return data ? fn(data) : fn;
  };

  /*------------------------------- Aliases ----------------------------------*/

  _.forEach  = _.each;
  _.foldl    = _.inject       = _.reduce;
  _.foldr    = _.reduceRight;
  _.filter   = _.select;
  _.every    = _.all;
  _.some     = _.any;
  _.head     = _.first;
  _.tail     = _.rest;
  _.methods  = _.functions;

  /*------------------------ Setup the OOP Wrapper: --------------------------*/

  // Helper function to continue chaining intermediate results.
  var result = function(obj, chain) {
    return chain ? _(obj).chain() : obj;
  };

  // Add all of the Underscore functions to the wrapper object.
  _.each(_.functions(), function(name) {
    wrapper.prototype[name] = function() {
      Array.prototype.unshift.call(arguments, this._wrapped);
      return result(_[name].apply(_, arguments), this._chain);
    };
  });

  // Add all mutator Array functions to the wrapper.
  _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
    wrapper.prototype[name] = function() {
      Array.prototype[name].apply(this._wrapped, arguments);
      return result(this._wrapped, this._chain);
    };
  });

  // Add all accessor Array functions to the wrapper.
  _.each(['concat', 'join', 'slice'], function(name) {
    wrapper.prototype[name] = function() {
      return result(Array.prototype[name].apply(this._wrapped, arguments), this._chain);
    };
  });

  // Start chaining a wrapped Underscore object.
  wrapper.prototype.chain = function() {
    this._chain = true;
    return this;
  };

  // Extracts the result from a wrapped and chained object.
  wrapper.prototype.value = function() {
    return this._wrapped;
  };

})();

/*jslint evil: true, forin: true,*/
if(!window.console){
    window.console = {log: function(){}};
}
(function(){
    var S = {_: this._.noConflict(), $: jQuery};
    var _ = S._;

	S.callOnLoad = function(f){
		S.$(document).ready(f);
	};
        
    S.lambda = {
        empty: function(){},
        not: function(x){ return !x;},
        constant: function(c){ return function(){ return c;};}
    };
    
    S.mixin = function(dest, o){
        if(!dest){
            dest = {};
        }
        if(!o){
            return dest;
        }
        for(var k in o){
            if(!(k in dest)){
                dest[k] = o[k];
            }
        }
        return dest;
    };
    
    function typeofPredicate(type, proto){
        return function(x){
            return typeof(x) === type || (x && proto && x instanceof proto);
        };
    }
    
    S.isFunction = typeofPredicate('function'); 
    S.isUndefined = typeofPredicate('undefined');   

    S.isBoolean = typeofPredicate('boolean', Boolean);
    S.isNumber = typeofPredicate('number', Number); 
    S.isString = typeofPredicate('string', String);
    S.isArray = typeofPredicate('array', Array);

    S.isObject = function(x){
        return x && typeof(x) == 'object';
    };
    
    S.isDefined = function(x){
        return typeof(x) != 'undefined';
    };
    
    /* ducktyping: */
    S.isElement = _.isElement;
    
    S.isArrayLike = function(x){
        return S.isObject(x) && S.isNumber(x.length);
    };
    
    S.isPlainObject = function(x){
        return x && x.constructor === Object;
    };
    
    S.isInteger = function(x){
        return parseInt(x) === x;
    };
    
    S.isFloat = function(x){
        return parseFloat(x) === x;
    };
            
    var boundMethodRepr = function(){
        return '<bound method '+S.repr(this.im_func)+' self='+this.im_self+'>';
    };
    var boundFunctionRepr = function(){
        return '<bound function' + S.repr(this.im_func) + ' self='+this.im_self+'>';
    };
    
    S.bind = function(self, f){
        if(S.isString(f)){
            f = self[f];
        }
        var bound = function(){
            return f.apply(self, arguments);
        };
        bound.im_self = self;
        bound.im_func = f;      
        bound.__repr__ = f.im_class ? boundMethodRepr : boundFunctionRepr;
        return bound;
    };
    
    S.args = function(args, offset, defaults){
        if(!defaults){
            return Array.prototype.slice.call(args, offset);
        }
        var len = Math.max(defaults.length, args.length);
        var a = [];
        for(var i=offset;i<len;i++){
            a[i] = S.isDefined(args[i]) ? args[i] : defaults[i];
        }
        return a;
    };
    
    S.getCaller = function(func){
        return func.arguments.callee.caller;
    };
    
    S.getTraceback = function(offset){
        var func = arguments.callee.caller;
        var stack = [];
        var nativeStack = null;
        offset = offset || 0;
        try{
            throw new Error();
        }
        catch(e){
            nativeStack = e.stack.split("\n").slice(2);
        }
        for(var k=0;k<offset;k++){
            func = S.getCaller(func);
        }
        for(var i=offset;i<nativeStack.length;i++){
            if(!func){
                break;
            }
            var fileAndLine = ['', 'unknown', 0];
            try{
                fileAndLine = nativeStack[i].split('@')[1].match(/^(.*):(\d+)$/);
            }
            catch(ignore){
            }
            stack.push({
                'function': func,
                'arguments': func.arguments,
                'file': fileAndLine[1],
                'line': fileAndLine[2]
            });
            func = S.getCaller(func);           
        }
        return stack;
    };
    
    S.logTraceback = function(traceback){
        if(!traceback || S.isInteger(traceback)){
            traceback = S.getTraceback(traceback ? traceback + 1 : 1);
        }
        var args = [];
        var format = [];
        var skipNext = 
        _(traceback).reverse().each(function(frame){
            if(skipNext){
                skipNext = false;
                return;
            }
            var func = frame['function'];
            var funcName = S.repr(func);
            var info = '';
            args.push(frame.arguments);
            if(func.im_self){
                skipNext = true;                
                funcName =  S.repr(func.im_func);
                info = ' bound to %o';
                args.push(func.im_self);
            }           
            format.push('\t' + funcName + '(%o)' + info + '\n\t   in ' + frame.file + ' at line ' + frame.line);
        });     
        console.log.apply(console, [format.reverse().join('\n')].concat(args.reverse()));
    };
    
    S.trace = function(){
        S.logTraceback(1);
    };
    
    S.partial = function(f){
        var args = S.args(arguments, 1);
        return function(){
            return f(args.concat(arguments));
        };
    };  
        
    S.remove = function(a, o){
        var i = _.indexOf(a, o);
        if(i >= 0){
            a.splice(i, 1);
        }
        return i >= 0;
    };
    
    S.removeAll = function(a, p, callback){
        var modified = false;
        for(var i=a.length-1;i>=0;i--){
            if((S.isFunction(p) && p(a[i])) || a[i] in p){
                var ai = a[i];
                a.splice(i, 1);
                if(callback){
                    callback(ai, i);
                }
                modified = true;                
            }           
        }
        return modified;
    };
    
    S.equal = function(a, b){
        if(a === b){
            return true;
        }
        var eq = '__equals__';
        if(S.isObject(a) && S.isObject(b) && (a[eq] || b[eq])){
            return a[eq] && a[eq](b) || b[eq] && b[eq](a);
        }
        if(S.isArray(a) && S.isArray(b)){
            if(a.length != b.length){
                return false;
            }
            for(var i=0;i<a.length;i++){
                if(!S.equal(a[i], b[i])){
                    return false;
                }
            }
            return true;
        }
        return a == b;
    };
    
    S.string = {
        startsWith: function(str, prefix){
            return str.indexOf(prefix) === 0;
        },
        
        empty: S.lambda.not,
        
        blank: function(str){
            return /^\s*$/.test(str); 
        },
        strip: function(str){
            return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
        },
        stripTags: function(str){
            return str.replace(/<.*?>/g, '');
        },
        trim: function(str){
            return S.string.strip(str);
        },
        reEscape: function(str){
            return str.replace('(','\\(').replace(')','\\)');
        },
        contains: function(str, substr){
            return str.indexOf(substr) >= 0;
        },
        capfirst: function(str){
            return str.charAt(0).toUpperCase() + str.substring(1);
        },
        times: function(str, n){
            var r = '';
            for(var i=0;i<n;i++){
                r += str;
            }
            return r;
        },
        pad: function(str, len, pad){
            pad = pad || '0';
            str = String(str);
            while(str.length < len){
                str = pad + str;
            }
            return str;
        },
        
        smartSplit: function(str){
            var result = [];
            console.log(str);
            var rest = str.replace(/"(([^"\\]|\\\\|\\")*)"|'(([^'\\]|\\\\|\\')*)'|([^\s]+)/g, function(match, dq, _dq, sq, _sq, uq){ //"
                var part = dq || sq || uq;              
                result.push(part.replace('\\"', '"').replace("\\'", "'").replace('\\\\', '\\'));
                return '';
            });
            console.log(rest);
            return result;
        },
        
        format: function(str, args){
            var result = str;
            var getter = args;
            if(S.isObject(args)){
                getter = function(name){
                    return args[name];
                };
            }
            if(S.isArray(args)){
                var index = 0;
                result = str.replace(/(%+)s/g, function(match, p){
                    if(!(p.length % 2)){
                        return p + 's';
                    }
                    return p.substring(1) + args[index++];
                });
            }
            else if(S.isFunction(getter)){
                result = str.replace(/(%+)\(([^)]+)\)s/g, function(match, p, name){
                    if(!(p.length % 2)){
                        return p + '(' + name + ')s';
                    }
                    var parts = name.split('?');
                    if(parts.length == 3){
                        var val = getter(parts[1]);
                        if(val){
                            return p.substring(1) + parts[0] + val + parts[2];
                        }
                        return '';
                    }
                    return p.substring(1) + getter(name);
                });
            }
            return result.replace(/%%/g,'%');
        },
        
        firstDiffIndex: function(a, b){
            var len = Math.min(a.length, b.length);
            for(var i=0;i<len;i++){
                if(a.charAt(i) != b.charAt(i)){
                    return i;
                }
            }
            return -1;
        }
    };  
        
    S.repr = function(x, depth){
        depth = S.isDefined(depth) ? depth : 3;
        if(S.isUndefined(x)){
            return "undefined";
        }
        if(x === null){
            return 'null';
        }
        if(S.isFunction(x.__repr__)){
            return x.__repr__();
        }
        if(S.isArray(x) || S.isArrayLike(x)){
            if(!depth){
                return '[...]';
            }
            return '[' + _.map(S.copy(x), function(x){ S.repr(x, depth - 1);}).join(', ') + ']';
        }       
        if(S.isObject(x)){
            if(S.isPlainObject(x)){
                if(!depth){
                    return '{...}';
                }
                var props = [];
                for(var p in x){
                    props.push(p + ': ' + S.repr(x[p], depth - 1));
                }
                return '{' + props.join(', ') + '}';
            } 
            return x.toString();
        }
        if(S.isString(x)){
            return '"' + x.replace('"', '\\"') + '"';
        }
        if(S.isFunction(x)){
            return "<anonymous function>";
        }
        return String(x);
    };
    
    S.toQueryString = function(o){
        var p = [];
        for(var k in o){
            p.push(k + '=' + encodeURIComponent(o[k]));
        }
        return p.join('&');
    };
    
    function reprDefinedObject(){
        var module = this.__module__;
        return module ? module + '.' + this.__name__ : this.__name__;
    }

    S.def = function(path, obj){
        var scope = window;
        path = path.split('.');
        for(var i=0;i<path.length-1;i++){
            var name = path[i];
            if(!S.isDefined(scope[name])){
                scope[name] = {};               
            }
            scope[name].__name__ = name;
            scope[name].__module__ = path.slice(0, i).join('.');
            if(!scope[name].__repr__){
                scope[name].__repr__ = reprDefinedObject;
            }
            scope = scope[name];
        }
        var objName = _.last(path);
        scope[objName] = obj;
        obj.__name__ = objName;
        obj.__module__ = path.slice(0, -1).join('.');
        if(!obj.__repr__){
            obj.__repr__ = reprDefinedObject;
        }
        return obj;
    };
            
    S.Class = {
        objectPrototype: {
            call: function(klass, method, args){
                args = S.isDefined(args) ? args : arguments.callee.caller.arguments;
                return klass.prototype[method].apply(this, args || []);
            },
            init: function(klass, args){
                args = S.isDefined(args) ? args : arguments.callee.caller.arguments;
                klass.prototype.__init__.apply(this, args || []);
            },
            __getattr__: function(name, defaultValue){
                return S.getattr(this, name, defaultValue);
            },
            instanceOf: function(klass){
                return this.prototype.constructor.isSubclassOf(klass);
            },
            __repr__: function(){
                return '<' + this.constructor.getName() + ' instance>';
            }
        },
        methods: {
            isSubclassOf: function(c){
                if(c === this){
                    return true;
                }
                return _.any(this.__parents__, function(p){
                    return p.isSubclassOf(c);
                });
            },
            isSuperclassOf: function(c){
                return c.isSubclassOf(this);
            },
            getName: function(){
                var module = this.__module__;
                var name = this.__name__ || 'anonymous';
                return module ? module + '.' + name : name;
            },
            __repr__: function(){
                var name = this.__name__;
                if(!name){
                    return '<anonymous class>';
                }
                var module = this.__module__;
                return "<class '" + this.getName() + "'>";
            }
        },
        methodMethods: {
            __repr__: function(){
                return S.string.format('<method %s.%s>', [this.im_class.getName(), this.__name__]);
            }
        },
        define: function(path){         
            var args = S.args(S.copy(arguments), 1);
            var c = this.create.apply(this, args);
            S.def(path, c);
        },
        create: function(){
            var parents = [];
            var a0 = arguments[0];
            var a1 = arguments[1];
            if(S.isFunction(a0)){
                parents = [a0];
            }
            else if(S.isArray(a0)){
                parents = a0;
            }
            var properties = (parents.length > 0) ? a1 : a0;
            var staticProperties = (parents.length > 0) ? arguments[2] : a1;
                        
            var c = function(){
                this.__init__.apply(this, arguments);
            };          

            if(!parents.length){
            	_.extend(c, S.Class.methods);
            	_.extend(c.prototype, S.Class.objectPrototype);
            }

            for(var i=0;i<parents.length;i++){
                var p = parents[i];
                _.extend(c.prototype, p.prototype);
                for(var staticProperty in p){
                    if(staticProperty != 'prototype'){
                        var val = p[staticProperty];
                        c[staticProperty] = S.isFunction(val) ? val : S.copy(val);
                    }
                }               
            }
            
            for(var name in properties){
                var value = properties[name];
                if(S.isFunction(value)){
                    value.__name__ = name;
                    value.im_class = c;
                    _.extend(value, S.Class.methodMethods);
                }
                c.prototype[name] = value;
            }
            
            if(staticProperties){
                _.extend(c, staticProperties);
            }
            
            if(!c.prototype.__init__){
                c.prototype.__init__ = S.lambda.empty;
            }
            c.prototype.constructor = c;
            c.__parents__ = parents;
            if(c.prototype.__meta__){
                c.prototype.__meta__(c);
            }
            return c;
        }
    };
    
    S.Module = {
        define: function(path, members){
            members = members || {};
            for(var name in members){
                S.def(path + '.' + name, members[name]);
            }
            return S.lookup(path);
        }
    };
    
    S.getattr = function(o, p, d){
        if(p == 'this'){
            return o;
        }
        var getter = 'get' + S.string.capfirst(p);
        if(S.isFunction(o[getter])){
            return o[getter].apply(o);
        }
        if(S.isDefined(o[p])){
            return o[p];
        }
        return d;
    };
    
    S.setattr = function(o, p, v){
        var setter = 'set' + S.string.capfirst(p);
        if(S.isFunction(o[setter])){
            o[setter].apply(o, [v]);
        }
        else{           
            o[p] = v;
        }
    };
    
    S.hasattr = function(o, p){
        return S.isDefined(o[p]);
    };
    
    S.configure = function(o, options){
    	if(options){
	    	_.each(S.args(arguments, 2), function(p){
	    		var v = options[p];
	    		if(S.isDefined(v)){
	    			S.setattr(o, p, v);
	    		}
	    	});    		
    	}
    };
    
    S.getter = function(p){
        return function(){
            return this[p];
        };
    };
    S.setter = function(p){
        return function(x){
            this[p] = x;
        };
    };
    S.alias = function(name){
        return function(){
            this[name].apply(this, arguments);
        };
    };
    
    S.dict = function(){
        var d = {};
        var pairs = arguments;
        for(var i=0;i<pairs.length;i++){
            d[pairs[i][0]] = pairs[i][1];
        }
        return d;
    };
    
    S.lookup = function(path, object, separator){
        object = object || window;
        path = path.split(separator || '.');
        for(var i=0;i<path.length;i++){
            object = object[path[i]];
            if(!S.isDefined(object)){
                return /* undefined */;
            }
        }
        return object;
    };

    S.define = function(path, value, object, separator){
        object = object || window;
        path = path.split(separator || '.');
        for(var i=0;i<path.length-1;i++){
            var p = path[i];
            if(!S.isDefined(object[p])){
                object[p] = {};
            }
            object = object[p];
        }
        object[_.last(path)] = value;
    };      
    
    S.copy = function(x){
        var c;
        if(S.isArrayLike(x)){
            c = [];
            for(var i=0;i<x.length;i++){
                c.push(x[i]);
            }
            return c;
        }
        if(S.isObject(x)){
            if(S.isFunction(x.__copy__)){
                return x.__copy__();
            }
            return _.clone(x);
        }
        // should be immutable (function, number, string, boolean, null)
        return x;
    };
    
    // experimental
    S.deepcopy = function(x, memo){
        if(S.isArray(x)){
            return _.map(x, S.deepcopy);
        }
        if(S.isObject(x)){          
            // don't copy elements
            if(S.isElement(x)){
                return x;
            }
            memo = memo || {};
            var hash = S.hash(x);
            if(_.include(memo, hash)){
                return memo[hash];
            }
            // custom copy() method
            if(S.isFunction(x.__deepcopy__)){
                return x.__deepcopy__(memo);
            }
            var c = {};
            for(var k in x){
                c[k] = S.deepcopy(x[k], memo);
            }
            return c;
        }
        return x;
    };
    
    S.round = function(x, precision){
        precision = precision || 1;
        return precision * Math.round(x / precision);
    };
    
    S.log = function(msg, args){
        if(!S.isString(msg)){
            return console.log.apply(console, _.map(arguments, S.repr));
        }
        var argc = arguments.length;
        if(argc == 1){
            return console.log(msg);
        }
        if(argc > 2){
            args = Array.prototype.slice.call(arguments, 1);
        }
        return console.log(S.string.format(msg, args));
    };
        
    S.cookies = {
        parse: function(cookieString){
            cookieString = cookieString || window.document.cookie || "";
            var pairs = cookieString.split('; ');
            var cookies = {};
            _.each(pairs, function(p){
                var c = p.split('=');
                cookies[c[0]] = c[1];
            });     
            return cookies;
        }
    };
    
    S.Module.define('shrubbery', S);
        
})();


(function(){
var S = shrubbery;
var _ = S._;

S.meta = {
	property: function(c, name, writeable){
		var capName = S.string.capfirst(name);
		var proto = c.prototype;
		if(writeable !== false){
			proto['set' + capName] = S.setter(name);
		}
		proto['get' + capName] = S.getter(name);
	},
	observableProperty: function(c, name, writeable){
		var capName = S.string.capfirst(name);
		if(writeable !== false){
			c.prototype['set' + capName] = function(value){
				var old = this['get' + capName]();
				if(old != value){
					this[name] = value;
					this.fireEvent('PropertyChange', name, value, old);
				}					
			};
		}
		c.prototype['get' + capName] = S.getter(name);			
	},
	delegate: function(c, d, methods){
		_.each(methods, function(m){
			c.prototype[m] = function(){
				var o = this[d];
				o[m].apply(o, arguments);
			};
		});
	}
};
})();

(function(S){
	var Ss = S.string;
	
	S.Class.define('shrubbery.EventManager', {
		__init__: function(type){
			this.type = type;
			this.listeners = [];
			this.method = 'on' + type;
		},
		addListener: function(listener){
			this.listeners.push(listener);
		},
		removeListener: function(listener){
			var listeners = this.listeners;
			for(var i=listeners.length-1;i>=0;i--){
				if(listeners[i] === listener){
					delete listeners[i];
					break;					
				}
			}
		},
		fireEvent: function(source, args){
			var type = args[0];
			args[0] = source;
			var listeners = this.listeners;
			for(var i=0;i<listeners.length;i++){
				var listener = listeners[i];
				if(S.isFunction(listener)){
					listener.apply(null, args);
				}
				else{
					if(S.isObject(listener)){
						var method = listener[this.method];
						if(S.isFunction(method)){
							method.apply(listener, args);
						}
					}
				}
			}
		}
	});
	
	function createProxyMethod(type){
		return function(){
			var args = S.args(arguments);
			args.splice(0, 0, type);
			this.fireEvent.apply(this, args);
		};
	}
		
	S.Class.define('shrubbery.Observable', {
		__init__: function(){
			if(!this.events){
				this.events = {};
			}
		},
		addEvents: function(){
			var args = arguments;
			var events = this.events;
			for(var i=0;i<args.length;i++){
				if(!S.isDefined(events[args[i]])){
					events[args[i]] = true;
				}
			}
		},
		addProxyEvents: function(){
			var args = arguments;
			this.addEvents.apply(this, args);
			for(var i=0;i<args.length;i++){
				var method = 'on' + args[i];
				if(!S.isDefined(this[method])){
					this[method] = createProxyMethod(args[i]);
				}
			}
		},
		addListener: function(type, listener){
			if(S.isFunction(type.test)){
				for(var e in this.events){
					if(type.test(e)){
						this.addListener(e, listener);
					}
				}
				return;
			}
			var m = this.events[type];
			if(m){
				if(S.isArray(type)){
					for(var i=0;i<type.length;i++){
						this.addListener(type[i], listener);
					}
					return;
				}
				if(!S.isObject(m)){
					m = new S.EventManager(type);
					this.events[type] = m;
				}				
				m.addListener(listener);
			}
		},
		removeListener: function(type, listener){
			var m = this.events[type]; 
			if(S.isObject(m)){
				m.removeListener(listener);
			}
		},
		fireEvent: function(type){
			var m = this.events[type];
			if(S.isObject(m)){
				m.fireEvent(this, arguments);
			}
		},
		fire: function(type, args){
			var m = this.events[type];
			if(S.isObject(m)){				
				m.fireEvent(this, [type].concat(args));
			}
		}
	});

})(shrubbery);


(function(){
	var S = shrubbery;
	
	var getConfig = function(obj, key, d){
		var config = obj.config;
		if(S.isObject(config) && config.hasOwnProperty(key)){
			return config[key];
		}
		return d;
	};
	
	S.Configurable = S.Class.create({	
		getConfig: function(key, d){
			var value = getConfig(this, key, d);
			if(!S.isDefined(value)){
				value = this.constructor.getConfig(key);
			}
			return value;
		},
		applyConfig: function(options){
			for(var key in options){
				if(this.getConfig(key)){
					this.setConfig(key, options[key]);
				}
			}
		},
		updateConfig: function(key, f){
			this.setConfig(key, f(this.getConfig(key)));
		},
		appendConfig: function(key, a){
			this.updateConfig(key, function(value){
				if(S.isArray(value)){
					if(S.isArray(a)){
						return value.concat(a);
					}
					value.push(a);
					return value;
				}
				if(S.isArray(a)){
					a.unshift(value);
					return a;
				}
				return [value, a];
			});
		},
		setConfig: function(key, value){
			if(S.isDefined(value)){
				if(!S.isDefined(this.config)){
					this.config = {};
				}
				this.config[key] = value;
			}
			else if(S.isDefined(this.config)){
				delete this.config[key];
			}
		}
	},/* static */{
		getConfig: function(key, d){
			var value = getConfig(this, key, d);			
			if(!S.isDefined(value)){	
				for(var i=0;i<this.__parents__.length;i++){	
					var p = this.__parents__[i];
					if(S.isFunction(p.getConfig)){
						value = p.getConfig(key);
						if(S.isDefined(value)){
							break;
						}						
					}											
				}
			}
						
			return value;
		}
	});
})();


(function(){
/*global shrubbery*/
var S = shrubbery;
S.$StopIteration = new Error('$StopIteration');

S.Class.define('shrubbery.Iterator', {
	__init__: function(next){
		if(next){
			this._next = next;
		}
	},
	next: function(){
		return this._next.call(this);
	},
	__iter__: function(){
		return this;
	},
	map: function(func, self){
		return new S.Iterator(S.bind(this, function(){
			return func.apply(self, this.next());
		}));
	},
	filter: function(p){
		return new S.Iterator(S.bind(this, function(){
			var next = this.next();
			while(!p(next)){
				next = this.next();
			}
			return next;			
		}));
	},	
	each: function(func, self){
		try{
			while(true){
				func.call(self, this.next());
			}
		}
		catch(e){
			if(e !== S.$StopIteration){
				throw e;
			}
		}
	},
	list: function(){
		var list = [];
		this.each(function(item){
			list.push(item);
		});
		return list;
	},
	count: function(){
		var n = 0;
		this.each(function(item){
			n++;
		});
		return n;
	}
}, {
	chain: function(){
		var iters = _.map(arguments, S.iter);
		var i = 0;
		return new S.Iterator(function(){
			if(i == iters.length){
				throw S.$StopIteration;
			}
			try{
				return iters[i].next();
			}
			catch(e){
				if(e !== S.$StopIteration){
					throw e;
				}
				i++;
				return this.next();
			}			
		});
	},
	zip: function(){
		var iters = S.iter(arguments).map(S.iter).list();
		return new S.Iterator(function(){
			return _.map(iters, function(iter){
				iter.next();
			});
		});
	},
	repeat: function(x, n){
		var i = 0;
		return new S.Iterator(function(){
			if(i == n){
				throw S.$StopIteration;
			}
			return x;
		});
	},
	cycle: function(x){
		var iter = S.iter(x);
		return new S.Iterator(function(){
			try{
				return iter.next();
			}
			catch(e){
				if(e === S.$StopIteration){
					iter = S.iter(x);
					return iter.next();
				}
			}
		});
	}
});

S.isIterable = function(obj){
	return obj && (S.isArrayLike(obj) || S.isFunction(obj.__iter__));
};

S.iter = function(obj){
	if(S.isFunction(obj.__iter__)){
		return obj.__iter__();
	}
	if(S.isArrayLike(obj)){
		var i = 0;
		return new S.Iterator(function(){
			if(i < obj.length){
				return obj[i++];
			}
			throw S.$StopIteration;			
		});
	}
};

})();
(function(){
var S = shrubbery;
var _ = S._;
	
S.Class.define('shrubbery.Collection', {
	__init__: function(data){
		this.data = data || [];
	},
	__iter__: function(){
		return S.iter(this.data);
	},
	contains: function(x){
		return _.include(this.data, x);
	},
	add: function(x){
		this.data.push(x);
	},
	remove: function(x){
		S.remove(this.data, x);
	},
	removeAll: function(x){
		return S.removeAll(this.data, x);
	},
	each: function(f, scope){
		_.each(this.data, f, scope);
	}
});
	
})();


(function(){
	var S = shrubbery;
	var _ = S._;
		
	S.Class.define('shrubbery.List', S.Collection, {
		first: function(){
			return this.data[0];
		},
		last: function(){
			return this.data[this.data.length-1];
		},
		insert: function(index, item){
			this.data.splice(index, 0, item);
		},
		indexOf: function(item){
			return _.indexOf(this.data, item);
		},
		map: function(f){
			return new this.constructor(_.map(this.data, f));
		}
	});
	
	S.meta.delegate(S.List, 'data', ['push', 'pop', 'shift', 'unshift', 'forEach']);
	
	S.Class.define('shrubbery.ObservableList', [S.List, S.Observable], {
		__init__: function(data){			
			this.init(S.List, arguments);
			this.init(S.Observable);
			this.itemListenerTypes = [];
			this.itemListener = {};
			this.addEvents('Insert', 'Delete');
			for(var i=0;i<this.data.length;i++){
				this.fireInsert(this.data[i], i);
			}			
		},
		addItemEvent: function(type){			
			if(_.include(this.itemListenerTypes, type)){
				return;
			}
			var itemEventType = 'Item' + type;
			this.addEvents(itemEventType);
			// FIXME
			this.itemListener['on' + type] = S.bind(this, function(a0, a1, a2, a3, a4){
				this.fireEvent(itemEventType, a0, a1, a2, a3, a4);
			});
			this.itemListenerTypes.push(type);
			_.each(this.data, function(item){
				item.addListener(type, this.itemListener);
			}, this);
		},
		fireDelete: function(item, index){
			var types = this.itemListenerTypes;
			for(var i=0;i<types.length;i++){
				item.removeListener(types[i], this.itemListener);
			}
			this.fireEvent('Delete', item, index);
		},
		
		fireInsert: function(item, index){
			var types = this.itemListenerTypes;
			for(var i=0;i<types.length;i++){
				item.removeListener(types[i], this.itemListener);
			}
			this.fireEvent('Insert', item, index);
		},		
		
		add: S.alias('push'),
		push: function(item){
			this.data.push(item);
			this.fireInsert(item, this.data.length - 1);
		},
		pop: function(){
			var item = this.data.pop();
			this.fireDelete(item, this.data.length);
			return item;
		},
		shift: function(){
			var item = this.data.shift();
			this.fireDelete(item, 0);
			return item;
		},
		unshift: function(item){
			this.data.unshift(item);
			this.fireInsert(item, 0);
		},
		insert: function(index, item){
			this.data.splice(index, 0, item);
			this.fireInsert(item, index);
		},
		remove: function(item){
			var index = this.indexOf(item);
			this.data.splice(index, 1);
			this.fireDelete(item, index);
		},
		removeAll: function(p){
			return S.removeAll(this.data, p, function(item, index){
				this.fireDelete(item, index);
			});
		},
		addAll: function(collection){
			S.iter(collection).each(S.bind(this, function(item){
				this.push(item);
			}));
		}
	});
	
})();


(function(S){

S.Module.define('shrubbery.error');

S.Class.define('shrubbery.error.Interceptor', S.Observable, {
	__init__: function(name){
		this.init(S.Observable);
		this.name = name;
		this.addEvents('Exception');
		var globalInterceptor = S.error.globalInterceptor;
		
		if(globalInterceptor){
			this.addListener('Exception', globalInterceptor);
		}
	},
	
	intercept: function(label, f, scope, args, fin){
		try{
			return f.apply(scope, args);
		}
		catch(e){
			this.fireEvent('Exception', label, e, f);
		}
		finally{
			if(fin){
				fin();
			}
		}
	},
	
	intercepted: function(label, f, scope, fin){
		return S.bind(this, function(){
			return this.intercept(label, f, scope, arguments, fin);
		});
	},
	
	onException: function(source, label, e, f){
		this.fireEvent('Exception', label, e, f);
	},
	
	toString: function(){
		return 'Interceptor ' + this.name;
	}
});
	
S.Class.define('shrubbery.error.Exception', {
	__init__: function(msg){
		var args = null;
		if(arguments.length > 1){
			args = S.args(arguments, 1);
			if(args.length == 1 && S.isObject(args[0])){
				args = args[0];
			}
			msg = S.string.format(msg, args);
		}
		this.msg = msg;
		this.args = args;
	},
	toString: function(){
		return 'shrubbery.Exception: ' + this.msg;
	}
});

var Se = S.error;
Se.globalInterceptor = new Se.Interceptor('shrubbery.error');

})(shrubbery);
(function(){
	var S = shrubbery;
	
    var safariKeys = {
        63234 : 37, // left
        63235 : 39, // right
        63232 : 38, // up
        63233 : 40, // down
        63276 : 33, // page up
        63277 : 34, // page down
        63272 : 46, // delete
        63273 : 36, // home
        63275 : 35  // end
    };	
	
    S.event = {
    	keys: {
		  BACKSPACE: 8,
		  TAB:       9,
		  RETURN:   13,
		  ESCAPE:   27,
		  LEFT:     37,
		  UP:       38,
		  RIGHT:    39,
		  DOWN:     40,
		  DELETE:   46,
		  SPACE:    32,
		  HOME:     36,
		  END:      35,
		  PAGEUP:   33,
		  PAGEDOWN: 34,
		  INSERT:   45    	
    	},
    	
    	errors: new S.error.Interceptor('shrubbery.event'),
    	    	
    	addSaveListener: function(el, type, scope, f){
			if(scope && S.isString(f)){
				f = scope[f];
			}
			return this.addListener(el, type, null, S.event.errors.intercepted(type+'-event', f, scope));
    	},
    	
		addListener: function(el, type, scope, f){
			if(scope && S.isString(f)){
				f = scope[f];
			}			
			var callback = function(e){
				f.apply(scope, [e, el, type]);
			};
			S.$(el).bind(type, callback);
			return {
				type: type,
				callback: callback,
				element: el
			};
		},
		
		removeListener: function(handle){
			var el = handle.element;
			S.$(el).unbind(handle.type, handle.callback);
		},
		
		stop: function(e){
			e.preventDefault();
			e.stopPropagation();
		},
		
		getTarget: function(e){
			return e.target;
		},
				    
		pointer: function(e){
			return new S.Position(e.pageX, e.pageY);
		},	
		pointerX: function(event){
			return event.pageX;
		},
		pointerY: function(event){
			return event.pageY;
		},
		
		relatedTarget: function(e){
			return e.relatedTarget;
		},
		// from ext
        isNavKeyPress : function(e){
			var k = this.getKeyCode(e);
			return (k >= 33 && k <= 40) || k == this.keys.RETURN || k == this.keys.TAB || k == this.keys.ESC;
        },
        getKeyCode: function(e){
			var k = e.keyCode;
			return S.$.browser.safari ? (safariKeys[k] || k) : k;
        },
        getCharCode: function(e){
        	return e.charCode || e.keyCode;
        }
    };    		

})();
(function(){
	var S = shrubbery;
		
	S.i18n = {
		weekDayNames: ['Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag','Sonntag'],
		monthNames: ['Januar','Februar','M\u00E4rz','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'],
		yes: 'Ja',
		no: 'Nein',
		ok: 'Ok',
		cancel: 'Abbrechen',
		close: 'Schlie\u00DFen',

		getWeekDayName: function(d){
			if(d.getDay){
				d = d.getDay();
			}
			return this.weekDayNames[d];
		},
		getWeekDayAbbreviation: function(d){
			return this.getWeekDayName(d).substring(0,2);
		},		
		getMonthName: function(m){
			if(m.getMonth){
				m = m.getMonth();
			}
			return this.monthNames[m];
		},
		getMonthAbbreviation: function(m){
			return this.getMonthName(m).substring(0,3);
		}
	};	
	
})();
(function(){
var S = shrubbery;
S.Module.define('shrubbery.date', {
	now: new Date(),
	daysInMonth: [31,28,31,30,31,30,31,31,30,31,30,31],
	isThisYear: function(d){
		return d && this.now.getFullYear() == d.getFullYear();
	},
	isThisMonth: function(d){
		return d && this.now.getMonth() == d.getMonth() && this.isThisYear(d);
	},
	isToday: function(d){
		return d && d.getDate() == this.now.getDate() && this.isThisMonth(d);
	},
	// FIXME: fails on 01.jan
	isYesterday: function(d){
		return d && d.getDate() + 1 == this.now.getDate() && this.isThisMonth(d);
	},
	// FIXME: fails on 31.dec
	isTomorrow: function(d){
		return d && d.getDate() - 1 == this.now.getDate() && this.isThisMonth(d);
	},
	isLeapYear: function(d){
		var y = d.getFullYear();
		return !(y & 3 || !(y % 100 || !(y % 400 || !y)));
	},
	getDaysInMonth: function(d){
		var m = d.getMonth();
		return (this.daysInMonth[m] + ((m == 1 && this.isLeapYear(d)) ? 1 : 0));
	},
	getFirstDayOfMonth: function(d){
		return (d.getDay() + 36 - d.getDate()) % 7;
	},
	dateEqual: function(a, b){
		if(!a || !b){
			return false;
		}
		return a.getDay() == b.getDay() && a.getMonth() == b.getMonth() && a.getYear() == b.getYear();
	},
	getDay: function(d){
		if(d){
			if(d.getDate){
				return d.getDate();
			}
			else if(d.getDay){
				return d.getDay();
			}
		}
		return 0;
	},
	formatMappings: {
		'd': function(d){ return S.string.pad(S.date.getDay(d), 2);},
		'F': function(d){ return S.i18n.getMonthName(date);},
		'G': function(d){ return d.getHours();},			
		'H': function(d){ return S.string.pad(d.getHours(), 2);},
		'i': function(d){ return S.string.pad(d.getMinutes(), 2);},
		'j': function(d){ return d.getDay();},
		'm': function(d){ return S.string.pad(d.getMonth()+1, 2);},
		'M': function(d){ return S.i18n.getMonthName(date).substring(0,3);},
		'n': function(d){ return d.getMonth();},
		's': function(d){ return S.string.pad(d.getSeconds(), 2);},
		't': function(d){ return this.getDaysInMonth(d);},
		'U': function(d){ return d.getTime();},
		'y': function(d){ return d.getYear();},
		'Y': function(d){ return d.getFullYear();}
	},
	format: function(fstr, d){
		if(!d){
			return '';
		}
		return fstr.replace(/%([dFGHijmMnstUyY%])/g, function(match, f){
			return f == '%' ? '%' : S.date.formatMappings[f](d);
		});
	},
	isoDateFormat: function(d, withTime){
		return S.date.format(withTime ? '%Y-%m-%dT%H:%i%sZ' : '%Y-%m-%d', d);
	},
	parseISOTime: function(str, offset, y, m, d){
		
	},
	parseISODate: function(str, offset){					
		if(!S.isString(str)){
			return null;
		}
		offset = offset || 0;
		var h=0;
		var m=0;
		var s=0;
		if(str.length > 10){
			h = parseInt(str.substring(offset+11, offset+13), 10) || 0;
			m = parseInt(str.substring(offset+14, offset+16), 10) || 0;
			s = parseInt(str.substring(offset+17, offset+19), 10) || 0;
		}
		return new Date(
			parseInt(str.substring(offset, offset+4), 10), 
			parseInt(str.substring(offset+5, offset+7), 10) - 1, 
			parseInt(str.substring(offset+8, offset+10), 10),
			h, m, s
		);			
	},
	parseHRDate: function(str){
		var match = str.match(/^\s*(\d{1,2})\.(\d{1,2})?\.(\d{0,4})([, ]+(\d{1,2})([:.](\d{2}))?( *uhr)?)?/i);
		if(match){
			var day = parseInt(match[1], 10);
			var month = parseInt(match[2], 10) - 1;
			var year = parseInt(match[3] || S.date.now.getFullYear(), 10);
			var hasTime = !!match[4];
			var hour = parseInt(match[5] || 0, 10);
			var minute = parseInt(match[7] || 0, 10);
			if(year < 100){
				year += 2000;
			}
			return [new Date(year, month, day, hour, minute, 0), hasTime];
		}
		else{
			return [null, false];
		}
	},
	formatInterval: function(t){
		var result = [];
		var s = t % 60;
		t = (t - s) / 60;
		var m = t % 60;
		t = (t - s) / 60;
		var h = t % 60;
		if(h){
			result.push(h+'h');
		}
		if(m){
			result.push(m+'min');
		}
		if(s){
			result.push(s+'s');
		}
		return result.join(' ');
	},
	parseHRInterval: function(str){
		var match = str.match(/^\s*((\d+)h)?\s*((\d+)(m(in)?|'))?\s*((\d+)(s(ec)?|"))?/);
		if(match){		
			var hours = parseInt(match[2], 10) || 0;
			var minutes = parseInt(match[4], 10) || 0;
			var seconds = parseInt(match[8], 10) || 0;
			var total = (hours*60 + minutes)*60 + seconds;
			return total;
		}
		return null;
	}
});


S.Class.define('shrubbery.date.DateTimeRange', {
	__init__: function(start, end, hasStartTime, hasEndTime){
		this.range = true;
		if(!S.isDefined(end)){
			var str = start;
			var parts = str.split('-');
			var info = S.date.parseHRDate(parts[0]);
			this.start = info[0];
			this.startTime = info[1];
			if(parts.length == 2){
				info = S.date.parseHRDate(parts[1]);
				this.end = info[0];
				this.endTime = info[1];
				if(!this.end){
					var match = parts[1].match(/\s*(\d{1,2})([:.](\d{2}))?(uhr)?\s*/i);
					if(match && match[1]){
						var h = parseInt(match[1], 10);
						var m = parseInt(match[3], 10) || 0;
						this.end = new Date(this.start.getFullYear(), this.start.getMonth(), this.start.getDate(), h, m);
						this.endTime = true;
					}
				}
			}
			else{
				this.range = false;
			}
		}
		else{
			this.start = start;
			this.end = end;
			this.startTime = S.isDefined(hasStartTime) ? hasStartTime : true;
			this.endTime = S.isDefined(hasEndTime) ?  hasEndTime : true;
		}
	},
	isRange: S.getter('range'),
	getStartDate: S.getter('start'),
	getEndDate: S.getter('end'),
	hasStartTime: S.getter('startTime'),
	hasEndTime: S.getter('endTime'),
	hasEndDate:function(){
		return !!this.end;
	},
	toString: function(){
		var start = S.date.format('%d.%m.%Y', this.start);
		var str = start;
		if(this.hasStartTime()){
			str += S.date.format(', %H:%i Uhr', this.start);
		}
		if(this.end){
			str += ' - ';
			var end = S.date.format('%d.%m.%Y', this.end);
			if(start != end){
				str += end + (this.hasEndTime() ? ', ' : '');
			}
			if(this.hasEndTime()){
				str += S.date.format('%H:%i Uhr', this.end);
			}
		}
		return str;
	}
});
	
})();
(function(){
	var S = shrubbery;
	var _ = S._;
	S.html = {
		id: function(el){
			if(!el.id){
				el.id = _.uniqueId('shrub_');
			}
			return el.id;
		},

		create: function(cls, content, attributes){
			var el = this.createElement('div', content, attributes);
			if(cls){
				if(S.isArray(cls)){
					cls = cls.join(' ');
				}			
				el.className = cls;
			}
			return el;
		},
		
		escape: function(str){
			return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
		},

		createElement: function(tagName, content, attributes){
			var el = document.createElement(tagName);
			if(content){
				if(S.isString(content)){
					el.innerHTML = content;
				}
				else if(S.isArray(content)){
					for(var i=0;i<content.length;i++){
						var c = this.toElement(content[i]);
						if(!c || !S.isDefined(c.nodeType)){
							throw new Exception("invalid element");
						}
						el.appendChild(c);
					}
				}
				else if(S.isDefined(content.nodeType)){
					el.appendChild(content);
				}
				else{
					el.appendChild(this.toElement(content));
				}
			}
			if(attributes){
				if(S.isString(attributes)){
					el.className = attributes;
				}
				else{
					for(var attr in attributes){
						el[attr] = attributes[attr];
					}
				}
			}
			return el;
		},	
		
		append: function(parent, cls, content){
			return parent.appendChild(this.create.call(this, cls, content));
		},
		
		update: function(el, content){
			S.html.clear(el);
			if(content){
				if(S.isString(content)){
					el.innerHTML = content;
				}
				else{
					el.appendChild(this.toElement(content));
				}			
			}
		},		
		
		hasClass: function(el, cls){
			return ((" "+el.className+" ").indexOf(" "+cls+" ") >= 0);
		},
	
		addClass: function(el){
			for(var i=1;i<arguments.length;i++){
				var cls = arguments[i];
				if(S.isArray(cls)){
					S.html.addClass.apply(this, [el].concat(cls));
					continue;
				}
				if((" "+el.className+" ").indexOf(" "+cls+" ") < 0){
					el.className = el.className + (el.className ? ' ' : '') + cls;
				}
			}
		},

		removeClass: function(el, cls){
			var t = S.string.trim((" " + el.className + " ").replace(" " + cls + " ", " "));
			if(el.className != t){
				el.className = t; 
			}
		},
		
		toggleClass: function(el, cls, condition){
			if(!S.isDefined(condition)){
				condition = !S.html.hasClass(el, cls);
			}
			this[condition ? "addClass" : "removeClass"](el, cls);
		},
		
		setClass: function(el, cls, set){
			if(set === false){
				this.removeClass(el, cls);
			}
			else{
				this.addClass(el, cls);
			}
		},
		
		hide: function(el){
			el.style.display = 'none';
		},
	
		show: function(el, display){
			el.style.display = display || '';
		},
		
		getStyle: function(el, style){
			return S.$(el).css(style);
		},		
		
		setStyle: function(el, style, value){
			S.$(el).css(style, value);
		},
		
		setWidth: function(el, w, unit){
			this.setStyle(el, 'width', w + (unit || 'px'));
		},
		
		setHeight: function(el, h, unit){
			this.setStyle(el, 'height', h + (unit || 'px'));
		},
		
		remove: function(el){
			el.parentNode.removeChild(el);
		},
		
		replace: function(el, node){
			el.parentNode.replaceChild(node, el);
		},
		
		clear: function(el){
			el.innerHTML = '';
		},
		
		getViewportOffset: function(){
			return {
				x: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
				y: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
			};
		},
		
		contains: function(parent, child){
			var el = child;
			while(el){
				if(el === parent){
					return true;
				}
				el = el.parentNode;
			}
			return false;
		},
		
		toElement: function(el, cls){
			if(!el){
				return;
			}
			if(S.isString(el)){
				el = S.$(el).get(0);
			}
			else if(S.isFunction(el.toElement)){
				el = el.toElement();
			}
			else if(S.isFunction(el)){
				el = this.toElement(el());
			}
			if(S.isElement(el) && cls){
				this.addClass(el, cls);
			}			
			return el;
		}
	};	
	
})();
(function(){

var S = shrubbery;
var Sh = S.html;

S.Position = S.Class.create({
	__init__: function(x, y){
		this.x = x;
		this.y = y;
	},
	plus: function(pos){
		return new S.Position(this.x + pos.x, this.y + pos.y);
	},
	minus: function(pos){
		return new S.Position(this.x - pos.x, this.y - pos.y);
	}
});

S.Dimensions = S.Class.create({
	__init__: function(w, h){
		this.width = w;
		this.height = h;
	}
});

S.Box = S.Class.create({
	__init__: function(x, y, w, h){
		this.x = x;
		this.y = y;
		this.width = w;
		this.height = h;
	},
	containsPosition: function(pos){
		return (pos.x >= this.x) && (pos.x <= this.x + this.width) && (pos.y >= this.y) && (pos.y <= this.y + this.height);
	},
	contains: function(box){
		return this.containsPosition(box) && box.x + box.width <= this.x + this.width && box.y + box.height <= this.y +this.height;
	},
	intersectsWith: function(box){
		var t = this.x > box.x;
		var a = t ? box : this, b = t ? this : box;
		if(a.x + a.width > b.x){
			t = this.y > box.y;
			a = t ? box : this;
			b = t ? this : box;
			return a.y + a.height > b.y;
		}
		return false;			
	},
	intersect: function(box){
		var t = this.x > box.x;
		var a = t ? box : this, b = t ? this : box;
		if(a.x + a.width > b.x){
			var x = b.x, w = Math.min(a.x + a.width - b.x, b.width);
			t = this.y > box.y;
			a = t ? box : this;
			b = t ? this : box;
			if(a.y + a.height > b.y){
				var y = b.y, h = Math.min(a.y + a.height - b.y, b.height);
				return new S.Box(x, y, w, h);
			}
		}
		return null;
	}
});

S.isPosition = function(o){
	return S.isObject(o) && S.isDefined(o.x) && S.isDefined(o.y);
};
S.isDimension = function(o){
	return S.isObject(o) && S.isDefined(o.width) && S.isDefined(o.height);
};
S.isBox = function(o){
	return S.isPosition(o) && S.isDimension(o);
};

function px2int(px){
	return parseInt(px, 10);
}

S.box = {
	/* viewport */
	getViewportDimensions: function(){
		return S.box.getDimensions(window);
	},
	getViewportBox: function(el){
		return new S.Box(el.scrollTop, el.scrollLeft, el.offsetWidth, el.offsetHeight);
	},
	getBox: function(el){
		return new S.Box(el.offsetLeft, el.offsetTop, el.offsetWidth, el.offsetHeight);
	},
	
	getAbsoluteBox: function(el){
		var pos = this.getAbsolutePosition(el);
		return new S.Box(pos.x, pos.y, el.offsetWidth, el.offsetHeight);
	},
	
	getOffsetParent: function(el){
		if(el.offsetParent){
			return el.offsetParent;
		}
		while(el != document.body){
			el = el.parentNode;
			if(Sh.getStyle(el, 'position') != 'static'){
				return el;
			}
		}
		return el;
	},
	
	getAbsolutePosition: function(el) {
		var x = 0, y = 0;		
		while(el){
			x += el.offsetLeft || 0;
			y += el.offsetTop  || 0;
			el = el.offsetParent;
		}
		return new S.Position(x, y);
	},		
	
	/* content */
	getContentDimensions: function(el){
		var w = el.offsetWidth - Sh.getStyle(el, 'paddingLeft') - Sh.getStyle(el, 'paddingRight');
		var h = el.offsetHeight - Sh.getStyle(el, 'paddingTop') - Sh.getStyle(el, 'paddingBottom');
		return new S.Dimensions(w, h);
	},
	getContentWidth: function(el){
		return this.getWidth(el);
	},
	getContentHeight: function(el){
		return this.getHeight(el);
	},
	/* bounding */
	getDimensions: function(el){
		var j = S.$(el);
		return new S.Dimensions(j.width(), j.height());
	},
	setDimensions: function(el, w, h){
		if(S.isObject(w)){
			h = w.height;
			w = w.width;
		}
		this.setWidth(el, w);
		this.setHeight(el, h);	
	},
	getWidth: function(el){
		return el.offsetWidth;
	},
	getHeight: function(el){
		return el.offsetHeight;
	},
	setWidth: function(el, width){
		Sh.setStyle(el, 'width', width + 'px');
	},
	setHeight: function(el, height){
		Sh.setStyle(el, 'height', height + 'px');	
	},

	/* position */
	getPosition: function(el){
		var pos = S.$(el).position();
		return new S.Position(pos.left, pos.top);
	},
	
	setPosition: function(el, x, y, unit){
		if(S.isObject(x)){
			unit = y;		
			y = x.y;
			x = x.x;
		}
		if(!S.isDefined(unit)){
			unit = 'px';
		}
		Sh.setStyle(el, 'left', x + unit);
		Sh.setStyle(el, 'top', y + unit);	
	},
	absoluteOverlay: function(src, dest){
		this.setPosition(dest, this.getAbsolutePosition(src));
		this.setDimensions(dest, this.getDimensions(src));
		Sh.setStyle(dest, {
			zIndex: (parseInt(Sh.getStyle(src, 'zIndex')) || 0) + 1,
			position: 'absolute'
		});
	}	
};

var docEl = document.documentElement;
if(docEl.getBoundingClientRect){
	// assumes w3c box-model
	S.box.getAbsolutePosition = function(el){
		var doc = el.ownerDocument;
		if(!doc){
			return null;
		}
		var body = doc.body;
		var docEl = doc.documentElement;
		var box = el.getBoundingClientRect();
		var y = box.top  + (self.pageYOffset || docEl.scrollTop  || body.scrollTop) - (docEl.clientTop || body.clientTop || 0);
		var x = box.left + (self.pageXOffset || docEl.scrollLeft || body.scrollLeft) - (docEl.clientLeft || body.clientLeft || 0);
		return new S.Position(x, y);
	};
}


})();
(function(){
	var S = shrubbery;
	var _ = S._;

    var m = {
        "\b": '\\b',
        "\t": '\\t',
        "\n": '\\n',
        "\f": '\\f',
        "\r": '\\r',
        '"' : '\\"',
        "\\": '\\\\'
    };

    var encodeString = function(s){
        if (/["\\\x00-\x1f]/.test(s)) {
            return '"' + s.replace(/([\x00-\x1f\\"])/g, function(match, c) {
                if(m[c]){
                	return m[c];
                }
                return "\\u" + S.string.pad(c.charCodeAt().toString(16), 4);
            }) + '"';
        }
        return '"' + s + '"';
    };	
		
	S.json = {
		methods: ['toJSON', 'toJson'],
		decode: function(json){
			try{
				return eval('(' + json + ')');
			}
			catch(e){
				throw new S.error.Exception('JSON format Error: %s', json);
			}
		},
		encode: function(o){
			if(!S.isDefined(o)){
				o = null;
			}		
			if(o === null || S.isBoolean(o) || S.isNumber(o)){
				return "" + o;
			}
			if(S.isObject(o)){
				var methods = this.methods;
				for(var i=0;i<methods.length;i++){
					var m = methods[i];
					if(S.isFunction(o[m])){
						var result = o[m]();
						if(!S.isString(result)){
							result = S.json.encode(result);
						}
						return result;
					}
				}				
			}
			if(S.isArray(o)){
				return '[' + _.map(o, function(item){
					return S.json.encode(item);
				}).join(',') + ']';
			}
			if(o instanceof Date){
				return S.date.format("%Y-%m-%dT%H:%i:%s", o);
			}
			if(S.isString(o)){
				return encodeString(o);
			}
			var buf = [];
			for(var k in o){
				if(k[0]!='$'){
					buf.push(encodeString(k) + ":" + S.json.encode(o[k]));
				}
			}			
			return "{" + buf.join(",") + "}";
	    }
	};
 
 
})();
(function(){
	var S = shrubbery;
	var _ = S._;
	var Sh = S.html;
	S.text = {
		cssProperties: ['fontFamily','fontStyle','fontSize','fontVariant','fontWeight','lineHeight','letterSpacing'],
		createOffscreenHelper: function(){
			var h = Sh.create('s-offscreen');
			Sh.setStyle(h, {
				position: 'absolute',
				left: '-1000px',
				top: '-1000px'
			});		
			return h;
		},
		getTextStyles: function(el){
			var style = {};
			_.each(this.cssProperties, function(p){
				style[p] = Sh.getStyle(el, p);
			});
			return style;
		},
		copyTextStyles: function(from, to){
			Sh.setStyle(to, this.getTextStyles(from));
		},
		TextMetrics: S.Class.create({
			__init__: function(options){
				this.options = options || {};
				this.helper = S.text.createOffscreenHelper();
				if(this.options.style){
					Sh.setStyle(this.helper, this.options.style);
				}
			},
			measure: function(text){
				var h = this.helper;
				h.innerHTML = text;
				document.body.appendChild(h);				
				var dim = [S.box.getWidth(h), S.box.getContentHeight(h)];
				Sh.remove(h);
				return dim;
			},
			getHeight: function(text){
				var h = this.helper;
				h.innerHTML = text;
				document.body.appendChild(h);
				var height = S.box.getContentHeight(h);
				Sh.remove(h);
				return height;
			}
		})
	};
	
	S.text.TextMetrics.forElement = function(el, options){
		options = options || {};
		options.style = S.mixin(options.style, S.text.getTextStyles(el));
		return new S.text.TextMetrics(options);
	};
})();
(function(){
	var S = shrubbery;
	var Se = S.event;
	
	S.dnd = {
		Draggable: S.Class.create(S.Observable, {
			__init__: function(handle, options){
				this.init(S.Observable);
				this.addEvents('DragStart', 'DragStop', 'Drag');
				this.handle = handle;
				
				S.dnd.Draggable.register(this);
				this.downHandler = Se.addListener(handle, 'mousedown', this, function(event){
					S.dnd.Draggable.onMouseDown(event, this);
				});
			},
			isDragging: function(){
				return this === S.dnd.Draggable.currentDraggable;
			},
			dispose: function(){
				Se.removeListener(this.downHandler);
				S.dnd.Draggable.unregister(this);
			},
			onDragStart: function(delta, event){
				return true;
			},
			onDragStop: function(delta, event){				
			},
			onDrag: function(delta, event){
			}
			
		},/* static */{
			register: function(draggable){
				if(!this.instances){
					this.instances = [];
					this.currentDraggable = null;
					this.upHandler = Se.addListener(document, 'mouseup', this, 'onMouseUp');
					this.moveHandler = Se.addListener(document, 'mousemove', this, 'onMouseMove');
				}
				this.instances.push(draggable);
			},
			
			unregister: function(draggable){
				
			},
			
			onMouseDown: function(event, draggable){				
				this.startPointer = Se.pointer(event);
				this.lastPointer = this.startPointer;
				this.lastDelta = new S.Position(0, 0);
				if(draggable.onDragStart(this.lastDelta, event) !== false){
					this.currentDraggable = draggable;
					draggable.fireEvent('DragStart', event);					
				}				
			},
			
			onMouseUp: function(event){
				var draggable = this.currentDraggable;
				if(draggable){
					this.lastPointer = null;
					draggable.onDragStop(this.lastDelta, event);
					draggable.fireEvent('DragStop', this.lastDelta, event);
					this.currentDraggable = null;
				}
			},
			onMouseMove: function(event){
				var draggable = this.currentDraggable;
				if(draggable){
					var startPointer = this.startPointer;
					var pointer = Se.pointer(event);
					var delta = pointer.minus(startPointer);
					this.lastPointer = pointer;
					this.lastDelta = delta;					
					draggable.onDrag(delta, event);
					draggable.fireEvent('Drag', delta, event);
				}
			}
			
		})		
	};
	
	var Sh = S.html;
	
	S.dnd.Movable = S.Class.create(S.dnd.Draggable, {
		__init__: function(el, options){
			options = S.mixin(options, {
				handle: el,
				overlay: 's-transparent'
			});
			this.element = el;
			this.init(S.dnd.Draggable, [options.handle, options]);			
		},
		onDragStart: function(delta){
			this.startPosition = S.box.getPosition(this.element);
		},
		onDrag: function(delta){
			S.box.setPosition(this.element, this.startPosition.plus(delta));
		}
	});
	
	S.dnd.type = {
		ANY: '*'
	};
	
	S.dnd.DragDrop = S.Class.create({
		__init__: function(source, element, data){
			this.source = source;
			this.element = element;
			this.data = data;
			this.startPosition = S.box.getPosition(element);
		}
	}, {
		registerDragSource: function(source){
			if(!this.sources){
				this.sources = [];
			}
			this.sources.push(source);
			source.addListener('Drag', S.bind(this, 'onDrag'));
			source.addListener('DragStart', S.bind(this, 'onDragStart'));
			source.addListener('DragStop', S.bind(this, 'onDragStop'));
		},
		registerDropTarget: function(target){
			if(!this.targets){
				this.targets = [];
				this.currentTarget = null;
			}
			this.targets.push(target);
		},		
		onDragStart: function(source, event){
			var element = source.getDraggedElement(event);
			if(!element){
				return;
			}
			var data = source.getDraggedData(event);
			this.currentDD = new S.dnd.DragDrop(source, element, data);
		},
		onDrag: function(source, delta, event){
			var dd = this.currentDD;
			if(this.targets && dd){
				var hoverTarget = null;
				var pointer = Se.pointer(event);				
				for(var i=0;i<this.targets.length;i++){
					var target = this.targets[i] ;
					if(S.box.getAbsoluteBox(target.dropTargetElement).containsPosition(pointer)){
						if(target.acceptsDrop(dd)){
							hoverTarget = target;
						}
						break;
					}
				}
				if(this.currentTarget != hoverTarget){
					if(this.currentTarget){
						this.currentTarget.onExit(dd, event);
					}
					this.currentTarget = hoverTarget;
					if(hoverTarget){
						hoverTarget.onEnter(dd, event);
					}
				}
			}
			if(dd){
				S.box.setPosition(dd.element, dd.startPosition.plus(delta));			
			}
		},
		onDragStop: function(source, delta, event){
			if(!this.currentDD){
				return;
			}
			if(this.currentTarget){
				this.currentTarget.onDrop(this.currentDD, event);
			}
			if(this.currentDD.source != this.currentTarget){
				this.currentDD.source.onDrop(this.currentDD, this.currentTarget);
			}
			this.currentDD = null;
		}
	});
	
	S.dnd.DragSource = S.Class.create(S.dnd.Draggable, {
		__init__: function(element, options){
			options = S.mixin(options, {
				type: S.dnd.type.ANY
			});
			this.element = element;
			this.init(S.dnd.Draggable, [element, options]);
			this.current = null;
			S.dnd.DragDrop.registerDragSource(this);
		},
		getDraggedElement: function(event){
			return this.element;
		},
		getDraggedData: function(event){
			return this.element;
		},
		onDrop: function(dd){
			this.fireEvent('Drop', dd);
		}
	});
	
	S.dnd.DropTarget = S.Class.create(S.Observable, {
		__init__: function(element, options){
			this.init(S.Observable);
			this.addEvents('Enter', 'Exit', 'Drop');
			options = S.mixin(options, {
				type: S.dnd.type.ANY
			});
			this.dropTargetElement = element;
			this.type = options.type;
			S.dnd.DragDrop.registerDropTarget(this);
		},
		onEnter: function(dd, event){
			this.fireEvent('Enter', dd, event);
		},
		onExit: function(dd, event){
			this.fireEvent('Exit', dd, event);
		},
		onDrop: function(dd, event){
			this.fireEvent('Drop', dd, event);
		},
		acceptsDrop: function(dd, event){
			return true;
		}
		
	});
	
})();
(function(S){
var _ = S._;

S.Class.define('shrubbery.async.Mutex', S.Observable, {
	__init__: function(){
		this.init(S.Observable);
		this.count = 0;
		this.waiting = [];
		this.addEvents('Error');		
	},
	acquire: function(){
		this.count += 1;
	},
	release: function(){
		this.count -= 1;
		if(!this.count){
			_.each(this.waiting, function(callback){
				callback();
			});
		}
	},
	error: function(){
		this.fireEvent('Error', arguments);
	},
	wait: function(callback){		
		if(!this.count){
			callback();
		}
		else{
			this.waiting.push(callback);
		}
	}
});

})(shrubbery);
(function(S){

S.Module.define('shrubbery.http', {
	errors: new S.error.Interceptor('shrubbery.http')
});

var transportFactories = [
	function(){return new XMLHttpRequest();}, 
	function(){return new ActiveXObject('Msxml2.XMLHTTP');},
	function(){return new ActiveXObject('Microsoft.XMLHTTP');}
];

var getTransport = function() {
	var transport = null;
	for(var i=0;i<transportFactories.length;i++){
		try{
			transport = transportFactories[i]();
		}
		catch(e){
			transport = null;
		}
		if(transport){
			break;
		}
	}
	return transport;
};
	
function isSuccess(status){
	return !status || (status >= 200 && status < 300);
}


S.Class.define('shrubbery.http.Client', S.Observable, {
	__init__: function(options){
		options = S.mixin(options, {
			method: 'post',
			requestImpl: S.http.Request
		});
		S.configure(this, options, 'requestImpl', 'url', 'method');
	},
	request: function(options){
		return new this.requestImpl(this, options);
	},
	send: function(request, callback, errback){
		var transport = getTransport();
		transport.open(request.method.toUpperCase(), request.url, true);
		var onStateChange = S.http.errors.intercepted(this.send, S.bind(this, function(){
			var readyState = transport.readyState;
			if (readyState > 1 && !((readyState == 4) && transport._complete)){
				if(readyState == 4){					
					transport._complete = true;
					var success = isSuccess(transport.status);
					if(success){
						if(callback){
							callback(transport);
						}
					}
					else{
						if(errback){
							errback(transport);
						}
					}
					// avoid memory leak in MSIE: clean up					
					transport.onreadystatechange = S.lambda.empty;
				}
			}			
		}));
		transport.onreadystatechange = onStateChange;
		var headers = request.getHeaders();
		for(var name in headers){
			transport.setRequestHeader(name, headers[name]);
		}
		transport.send(request.getBody());
		if (!request.asynchronous && transport.overrideMimeType){
			onStateChange();
		}							
		
	}
});

S.Class.define('shrubbery.http.Request', S.Observable, {
	__init__: function(client, options){
		options = S.mixin(options, {
			contentType: 'application/x-www-form-urlencoded',
			encoding: 'utf-8',
			method: client.method,
			url: client.url,
			accept: 'text/javascript, text/html, application/xml, text/xml, */*',
			headers: {}
		});		
		this.client = client;
		S.configure(this, options, 'url', 'body', 'parameters', 'contentType', 'encoding', 'method', 'headers');
		this.method = this.method.toUpperCase();
		this.init(S.Observable);
		this.addEvents('Complete', 'Success', 'Error');
	},
	isSuccess: function(httpStatus){
		return !httpStatus || (httpStatus >= 200 && httpStatus < 300);
	},
	getContentType: function(){
		return this.contentType + (this.encoding ? '; charset=' + this.encoding : '');
	},	
	getParams: function(){
		var params = this.parameters;				
		if(S.isObject(this.parameters)){					
			params = S.toQueryString(this.parameters);
		}
		return params;
	},
	getBody: function(){
		if(this.method != 'GET'){
			var params = this.getParams();
			if(params){
				return params;
			}
		}
		return this.body;
	},
	getHeaders: function(){
		var headers = S.mixin(this.headers, {
			'Content-Type': this.getContentType(),
			'Accept': this.accept
		});
	},
	getURL: function(){
		var url = this.url;
		if(this.method == 'GET'){
			var params = this.getParams();
			if(params){
				url += (S.string.contains(this.url, '?') ? '&' : '?') + params;
			}
		}
		return url;
	},				
	send: function(callback, errback){
		this.client.send(this, callback, errback);
	}
});

S.http.client = new S.http.Client();

})(shrubbery);
(function(S){
var _ = S._;

S.Module.define('shrubbery.djq', {
	and: function(){
		return new CompoundQ('and', S.args(arguments));
	},
	or: function(){
		return new CompoundQ('or', S.args(arguments));
	}
});


S.Class.define('shrubbery.djq.Q', {
	__init__: function(kwargs){
		this.kwargs = kwargs;
	},
	toObject: function(){
		return {'__class__': 'Q', kwargs: this.kwargs};
	},
	pushLookup: function(name){
		var kwargs = {};
		for(var key in this.kwargs){
			kwargs[name + '__' + key] = this.kwargs[key];
		}
		this.kwargs = kwargs;
	}
});

S.Class.define('shrubbery.djq.Annotation', {
	__init__: function(type, args){
		this.type = type;
		this.args = args;
	},
	toObject: function(){
		return {'__class__': this.type, 'args': this.args};
	}
});

_.each(['Count', 'Min', 'Max'], function(annotation){
	S.define('shrubbery.djq.' + annotation, function(){
		return new S.djq.Annotation(annotation, S.copy(arguments));
	});
});

var CompoundQ = S.Class.create({
	__init__: function(connector, qObjects){
		this.connector = connector;
		this.qObjects = qObjects;
	},
	pushLookup: function(name){
		_.each(this.qObjects, function(q){
			q.pushLookup(name);
		});
	},
	toObject: function(){
		return {
			'__class__': 'Q', 
			'connector': this.connector, 
			'objects': _.map(this.qObjects, function(q){
				return q.toObject();
			})
		};
	}
});

S.Class.define('shrubbery.djq.Request', S.Observable, {
	__init__: function(queryset, action, params){			
		this.init(S.Observable);
		this.queryset = queryset;
		this.action = action;
		this.params = params || {};
		this.addEvents('Complete', 'Error', 'Success');
	},
	getParams: function(){
		return S.mixin(this.params, {
			filters: this.queryset.chain,
			fields: this.queryset.fields,
			model: this.queryset.model.serialize()
		});		
	},
	getClient: function(){
		return this.queryset.getClient();
	},
	getAction: S.getter('action'),
	onComplete: function(){
		this.fireEvent('Complete');
	},
	onError: function(){
		this.onComplete();
		this.fireEvent.apply(this, ['Error'].concat(arguments));
	},
	onSuccess: function(){
		this.onComplete();		
		this.fireEvent.apply(this, ['Success'].concat(arguments));
	},
	execute: function(callback, errback){
		this.getClient().call(this, S.bind(this, function(){
			this.onSuccess.apply(this, arguments);
			if(callback){
				callback.apply(this, arguments);
			}
		}), S.bind(this, function(){
			this.onError.apply(this, arguments);
			if(errback){
				errback.apply(this, arguments);
			}
		}));
	}
});

function qsMap(name){
	return function(){
		return this.clone({method: name, args: S.copy(arguments)});
	};
}

function qsFilterMap(name){
	return function(arg){
		if(arg.toObject){
			return this.clone({method: name, args: [arg.toObject()]});
		}
		return this.clone({method: name, kwargs: arg});
	};
}

S.Class.define('shrubbery.djq.QuerySet', {
	__init__: function(model, chain, fields){
		this.model = model;
		this.chain = chain || [];
		this.fields = fields || [];
	},
	clone: function(push){
		return new S.djq.QuerySet(this.model, push ? this.chain.concat([push]) : this.chain, this.fields, this.m2m);
	},

	filter: qsFilterMap('filter'),
	exclude: qsFilterMap('exclude'),

	reverse: qsMap('reverse'),
	orderBy: qsMap('order_by'),
	slice: qsMap('slice'),	
	only: qsMap('only'),
	distinct: qsMap('distinct'),
	annotate: function(kwargs){
		var qs = this;
		for(var name in kwargs){
			qs = qs.clone({method: 'annotate', kwargs: S.dict([name, kwargs[name].toObject()])});
		}
		return qs;
	},
	
	query: function(fields){
		if(fields){
			if(!S.isArray(fields)){
				fields = S.copy(arguments);
			}
		}
		return new S.djq.Request(this, 'query', {fields: fields});
	},
	count: function(){
		return new S.djq.Request(this, 'count');
	},
	update: function(kwargs){
		return new S.djq.Request(this, 'update', {kwargs: kwargs});
	},
	'delete': function(){
		return new S.djq.Request(this, 'delete');
	},	
	get: function(kwargs){
		return new S.djq.ObjectRequest(this, 'get', {kwargs: kwargs});
	},
	getOrCreate: function(kwargs){
		return new S.djq.Request(this, 'get_or_create', {kwargs: kwargs});
	},
	create: function(kwargs){
		return new S.djq.Request(this, 'create', {kwargs: kwargs});
	},
	getClient: function(){
		return this.model.getClient();
	}
});



S.Class.define('shrubbery.djq.ObjectRequest', S.djq.Request, {
	m2m: function(name){
		return new S.djq.M2M(this, name);
	}
});

S.Class.define('shrubbery.djq.M2MRequest', S.djq.Request, {
	getParams: function(){
		return S.mixin(this.params, {
			model: this.queryset.serialize()
		});
	}
});

S.Class.define('shrubbery.djq.BatchRequest', S.djq.Request, {
	__init__: function(requests){
		this.requests = requests;
	},
	onSuccess: function(){
		this.call(S.djq.Request, 'onSuccess', arguments);
		var args = S.args(arguments);	
		_.each(this.requests, function(request){
			request.onSuccess.apply(request, args);
		});
	},
	onError: function(){
		this.call(S.djq.Request, 'onError', arguments);
		var args = S.args(arguments);
		_.each(this.requests, function(request){
			request.onError.apply(request, args);
		});		
	},
	onComplete: function(){
		this.call(S.djq.Request, 'onComplete', arguments);
		var args = S.args(arguments);		
		_.each(this.requests, function(request){
			request.onComplete.apply(request, args);
		});		
	},	
	getParams: function(){
		return {
			action: 'batch',
			requests: _.map(this.requests, function(request){
				request.getParams();
			})
		};
	},
	execute: function(callback, errback){
		_.each(this.requests, function(request){
			request.getParams();
		});
	}
});

S.Class.define('shrubbery.djq.M2M', {
	__init__: function(request, field){
		this.request = request;
		this.field = field;
	},
	all: function(){
		return new S.djq.QuerySet(this);
	},
	assign: function(data){
		return new S.djq.M2MRequest(this, 'm2m_assign', {data: data});
	},
	getClient: function(){
		return this.request.getClient();
	},
	serialize: function(){
		return {
			object: this.request.getParams(),
			field: this.field
		};
	}
});

S.Class.define('shrubbery.djq.Model', {
	__init__: function(client, name){
		this.client = client;
		this.name = name;
		this.objects = new S.djq.QuerySet(this);
	},
	getClient: S.getter('client'),
	getName: S.getter('name'),
	serialize: function(params){
		return this.name;
	}
});

var interceptor = new S.error.Interceptor('shrubbery.djq.Client');

S.Class.define('shrubbery.djq.Client', S.Observable, {
	__init__: function(url, options){
		this.init(S.Observable);
		this.url = url;
		this.addEvents('Error', 'Request');
		this.nextSerial = 1;
		this.logQueries = false;		
		this.httpClient = new S.http.Client();
	},
	
	logResponse: function(responseObject){
		if(responseObject.sql && this.logQueries){
			var queries = responseObject.sql;
			for(var i=0;i<queries.length;i++){
				var query = queries[i];
				console.log(''+query.sql.replace('%', '%%')+' ['+query.time+'s]');
			}
		}
		if(responseObject.status < 0){
			console.log(responseObject.exception);
			console.log(responseObject.traceback);
		}
	},
	
	logRequest: function(url, json){
		this.fireEvent('Request');
		console.log(url, json);
	},
	
	call: function(req, callback, errback){
		params = S.mixin(req.getParams(), {
			serial: this.nextSerial++,
			action: req.getAction()
		});
		var reqJson = S.json.encode(params);
		this.logRequest(this.url, reqJson);
		var httpReq = this.httpClient.request({
			url: this.url,
			body: reqJson
		});
		httpReq.send(interceptor.intercepted("djq-request", S.bind(this, function(response){				
			var responseObject = S.json.decode(response.responseText);
			this.logResponse(responseObject);
			if(responseObject.status !== 0){
				if(responseObject.status > 0){
					if(errback){
						errback(responseObject.result, responseObject.status);
					}
					this.fireEvent('Error', responseObject);							
				}
				else{
					var sql = '';
					if(responseObject.sql){
						_.each(responseObject.sql, function(query){
							sql += query.sql.replace('%', '%%')+' ['+query.time+'s]\n';
						});
					}
					throw new S.error.Exception(responseObject.exception +"\nTraceback: " + responseObject.traceback + "\nSQL: " + sql);						
				}
			}
			else{
				var result = responseObject.result;
				callback(result);
			}
		})));
	},
	
	getModel: function(name){
		return new S.djq.Model(this, name);
	}
});

})(shrubbery);
(function(){
var S = shrubbery;

function eventHandler(self, event){
	return function(){
		var args = S.copy(arguments);
		args.splice(0, 0, event);
		self.fireEvent.apply(self, args);
	};
}

S.def('shrubbery.swfu._instances', {});

S.Class.define('shrubbery.swfu.SWFU', S.Observable, {
	__init__: function(options){
		this.init(S.Observable);
		options = S.mixin(options, {
			fileTypes: '*.*',
			width: '100%',
			height: '100%',
			paddingTop: 0,
			paddingLeft: 0,
			text: 'upload',
			disabled: false,
			filePostName: 'file',
			css: '',
			wmode: SWFUpload.WINDOW_MODE.TRANSPARENT
		});
		this.swfu = options.swfu;		
		if(!this.swfu){
			var swfuSettings = S.mixin(options.swfuSettings, {
				upload_url: options.url,
				file_post_name: options.filePostName,
				use_query_string: false,
				file_types: options.fileTypes,
				file_types_description: options.fileTypes || options.fileTypes,
				file_upload_limit: 1,
				flash_url: options.flashURL || S.swfu.FLASH_URL,
				debug: true,
				file_queue_limit: 1,
				// button
				button_placeholder: options.element,
				button_width: options.width,
				button_height: options.height,
				button_text: options.text,
				button_text_top_padding: options.paddingTop,
				button_text_left_padding: options.paddingLeft,
				button_text_style: options.css,
				button_disabled: options.disabled,
				button_action: SWFUpload.BUTTON_ACTION.SELECT_FILE,				
				button_window_mode: options.wmode,
				// events
				upload_start_handler: eventHandler(this, 'UploadStart'),
				upload_success_handler: eventHandler(this, 'UploadSuccess'),
				upload_progress_handler: eventHandler(this, 'UploadProgress'),
				upload_error_handler: eventHandler(this, 'UploadError'),
				upload_complete_handler: eventHandler(this, 'UploadComplete'),
				file_queued_handler: eventHandler(this, 'FileQueued'),
				file_queue_error_handler: eventHandler(this, 'FileQueueError'),
				file_dialog_complete_handler: eventHandler(this, 'FileDialogComplete'),
				file_dialog_start_handler: eventHandler(this, 'FileDialogStart'),
				debug_handler: eventHandler(this, 'Debug')
			});
			this.swfu = new SWFUpload(swfuSettings);
			S.swfu._instances[this.swfu.movieName] = this;
		}
		this.addEvents('UploadStart', 'UploadSuccess', 'UploadProgress', 'UploadError', 'UploadComplete', 'FileQueued', 'FileQueueError', 'FileDialogComplete', 'FileDialogStart');
	},
	
	setPostParams: function(params){
		this.swfu.setPostParams(params);
	},
	
	clearQueue: function(){
		this.swfu.cancelUpload();
	},
	
	getHttpClient: function(){
		var client = new S.http.Client();
		client.send = S.bind(this, function(request, callback, errback){
			this.swfu.setUploadURL(request.url);
			this.swfu.setPostParams(request.getParams());			
			var successListener = S.bind(this, function(file, serverData, receivedResponse){
				if(callback){
					callback();
				}
				this.removeListener(errorListener);
				this.removeListener(successListener);
			});
			var errorListener = S.bind(this, function(){
				if(errback){
					errback();
				}
				this.removeListener(errorListener);
				this.removeListener(successListener);
			});
			this.addListener('UploadError', errorListener);
			this.addListener('UploadSuccess', successListener);
			this.swfu.startUpload();
		});
		return client;
	},
	
	upload: function(file, options){
		if(options){
			if(options.postParams){
				this.swfu.setPostParams(options.postParams);				
			}
			if(options.url){
				this.swfu.setUploadURL(options.url);
			}
		}
		this.swfu.startUpload(file ? file.id : undefined);
	}
});

S.swfu.IMAGE_FILE_TYPES = '*.png,*.jpg,*.jpeg,*.gif';
S.swfu.FLASH_URL = '';

})();

