/* Library of other tools and resources */

if(typeof(rocketlib) == 'undefined') {
    var rocketlib = {};
}
var rl = rocketlib;

/* A simple wrapper around MochiKit.DOM.getElement(..). Returns a DOM node.
 * If `el` is already a DOM node, it's returned directly.
 */
rl.getdom = function(el) {
    return MochiKit.DOM.getElement(el);
};
/* Returns a jQuery chain for the element or node `el` */
rl.getjq = function(el) {
    return jQuery(rl.getdom(el));
};

rl.Prototype = function(properties) {
    var proto = function() {
        if(this.initialize) {
            return this.initialize.apply(this, arguments);
        }
    };
    proto.prototype = properties || {};
    return proto;
};
rl.Prototype.create = function(properties) {
    return new rl.Prototype(properties);
};

/* rl.Request = XMLHttpRequest helper that uses MochiKit's Async, wrapped
 * in a bit of a friendlier API.
 */
rl.Request = new rl.Prototype({
    initialize: function(url, options) {
        this.url = url;
        this.setOptions(options);
    },

    setOptions: function(options) {
        this.options = MochiKit.Base.update({
            method: 'GET',
            asynchronous: true,
            contentType: 'application/x-www-form-urlencoded',
            encoding: 'UTF-8',
            onLoading: MochiKit.Base.noop,
            onSuccess: MochiKit.Base.noop,
            onError: MochiKit.Base.noop,
            headers: {
                'X-Requested-With': 'XMLHttpRequest'
            }
        }, options || {});
        return this;
    },
  
    xhrRequestOptions: function(options) {
        var reqopts = {
            method: (options.method || 'GET').toUpperCase(),
            sendContent: (options.sendContent || '')
        };
        if(options.mimeType) {
            reqopts.mimeType = options.mimeType;
        }

        var headers = MochiKit.Base.merge(options.headers || {});
        if(reqopts.method == 'POST') {
            headers['Content-type'] = options.contentType +
                (options.encoding ? '; charset='+options.encoding : '');
        }
        reqopts['headers'] = headers;
        return reqopts;
    },
  
    get: function(query, options) {
        /* Returns a MochiKit.Async.Deferred, ready to have callbacks and errbacks
         * added */
        var url = this.url;
        if(query) {
            query = MochiKit.Base.queryString(query);
            url += '?' + query;
        }
        options = MochiKit.Base.merge(this.options, {method: 'GET'}, options);

        this.options.onLoading();
        var d = MochiKit.Async.doXHR(url, this.xhrRequestOptions(options));
        d.addCallbacks(this.options.onSuccess, this.options.onError);
        return d;
    },
  
    post: function(query, options) {
      var m = MochiKit.Base;
      if(!m.isUndefinedOrNull(query)) {
          if(typeof(query) != 'string') {
              query = m.queryString(query);
          }
      }
      options = MochiKit.Base.merge(this.options, {method: 'POST'}, options);
      options.sendContent = options.sendContent || query || '';

      this.options.onLoading();
      var d = MochiKit.Async.doXHR(this.url, this.xhrRequestOptions(options));
      d.addCallbacks(this.options.onSuccess, this.options.onError);
      return d;
    }
});

/* JSON AJAX Requester, wraps a `rocketlib.Request` object, offers similar API
 * except that it adds MochiKit.Async.evalJSONRequest to the callback chain so
 * that the onSuccess handler gets JS objects passed in.
 */
rl.JSONRequest = new rl.Prototype({
    initialize: function(url, options) {
        this.request = new rl.Request(url);
        options = MochiKit.Base.update({
            mimeType: 'text/plain'
        }, options || {});
        this.request.setOptions(options);
        this.request.options.headers['Accept'] = 'application/json, text/x-json, text/plain';
    },
  
    get: function(query) {
        /* Returns a Deferred that already has MochiKit.Async.evalJSONRequest 
         * in its callback chain. `query` could be null, a hash, goes through
         * MochiKit.Base.queryString(...).
         */
        return this.request.get(
            query, {mimeType: 'text/plain'}
        ).addCallback(MochiKit.Async.evalJSONRequest);
    },
  
    post: function(query) {
        return this.request.post(
            query, {mimeType: 'text/plain'}
        ).addCallback(MochiKit.Async.evalJSONRequest);
    }
});


/* Mimics some of the old Prototype.js Element API, but uses jQuery */
rl.Element = {
    /* Returns true if the element is visible. ``rl.Element.visible(..)`` */
    visible: function(element) {
        return rl.getjq(element).is(':visible');
    },
  
    hide: function(element) {
        return rl.getjq(element).hide().get(0);
    },
  
    show: function(element) {
        return rl.getjq(element).show().get(0);
    },
  
    toggleDisplay: function(element) {
        return rl.getjq(element).toggle().get(0);
    },
  
    down: function(element, tagname, classname) {
        tagname = tagname || '';
        classname = classname ? '.'+classname : '';
        var expr = tagname+classname;
        if(expr.length > 0) {
            return rl.getjq(element).find(expr).get(0);
        }
    },
  
    up: function(element, tagname, classname) {
        tagname = tagname || '';
        classname = classname ? '.'+classname : '';
        var expr = tagname+classname;
        if(expr.length > 0) {
            return rl.getjq(element).parents(expr).get(0);
        }
    }
};

// Common event handlers. Depends on MochiKit.Signal events.
rl.Handlers = {
    stopEvent: function(evt) {
        if(evt && evt.stop) {
            evt.stop();
        }
    }
};

rl.Enumerable = {
    pairs: function(obj) {
        /* Returns MochiKit.Base.items(obj), but mapped to objects with `key` 
         * and `value` attributes.
         */
        var m = MochiKit.Base;
        return m.map(
            function(pair) { return {key: pair[0], value: pair[1]}; },
            m.items(obj)
        );
    },
  
    includes: function(iterable, value) {
        /* Uses MochiKit.Base.findValue(iterable, value), but returns just a bool
         * value instead of the index.
         */
        return (MochiKit.Base.findValue(iterable, value) > -1);
    }  
};


rl.String = {
    ends_with: function(src, end_value) {
        regex = new RegExp(end_value.toString() + '$');
        return !!(src.match(regex));
    },

    starts_with: function(src, start_value) {
        regex = new RegExp('^' + start_value.toString());
        return !!(src.match(regex));
    },

    ensure: function(value) {
        return MochiKit.Base.isUndefinedOrNull(value) ? '' : String(value);
    },

    gsub: function(source, pattern, replacement) {
        // `replacement` receives a match object and should return a string.
        var result = '', match;

        while (source.length > 0) {
            if (match = source.match(pattern)) {
                result += source.slice(0, match.index);
                result += rl.String.ensure(replacement(match));
                source  = source.slice(match.index + match[0].length);
            } else {
                result += source; source = '';
            }
        }
        return result;
    }  
};

rl.String.TEMPLATE_PATTERN = /(^|.|\r|\n)(#\{(.*?)\})/;
rl.String.Template = new rl.Prototype({
    initialize: function(template) {
        this.template = template.toString();
    },

    evaluate: function(object) {
        return rl.String.gsub(this.template, rl.String.TEMPLATE_PATTERN, function(match) {
            var before = match[1];
            if (before == '\\') {
                return match[2];
            }
            return before + rl.String.ensure(object[match[3]]);
        });
    }
});

rl.Serialize = {
  // rl.Serialize.sortable serializes a MochiKit Sortable into a Zope 3
  // style `name:list` option. Requires MochiKit.Sortable.
    sortable: function(element, options) {
        if(typeof(MochiKit.Sortable) == 'undefined') {
            throw "rl.Serialize.sortable requires MochiKit.Sortable";
        }

        var sortable = MochiKit.Sortable;
        element = MochiKit.DOM.getElement(element);
        options = MochiKit.Base.update(sortable.options(element), options || {});
        var name = options.name || element.id;
        var sequence = sortable.sequence(element, options);
        var query = {};
        query[name+':list'] = sequence;
        return MochiKit.Base.queryString(query);
    }
};

//  Function.prototype.delay and periodical from Mootools.Function, as
//  are $clear and $type.
//	mootools.js: moo javascript tools
//	by Valerio Proietti (http://mad4milk.net) MIT-style license.
Function.prototype.delay = function(ms, bind) {
	  return setTimeout(MochiKit.Base.bind(this, bind || this._proto_ || this), ms);
};

Function.prototype.periodical = function(ms, bind){
    return setInterval(MochiKit.Base.bind(this, bind || this._proto_ || this), ms);
};

rl.clear_timer = function(timer){
  	clearTimeout(timer);
  	clearInterval(timer);
  	return null;
};

rl.query_type = function(obj, types){
    if (rl.is_null(obj)) return 'null';
    if (rl.is_undefined(obj)) return 'undefined';

  	var type = 'unknown';
  	if (obj instanceof Function) type = 'function';
  	else if (obj.nodeName){
    		if (obj.nodeType == 3 && !(/\S/).test(obj.nodeValue)) type = 'textnode';
    		else if (obj.nodeType == 1) type = 'element';
  	}
  	else if (obj instanceof Array) type = 'array';
  	else if (typeof obj == 'object') type = 'object';
  	else if (typeof obj == 'string') type = 'string';
  	else if (typeof obj == 'number' && isFinite(obj)) type = 'number';
  	else type = typeof(obj);
  	return type;
};

/* rl.is_null(value) -> bool; rl.is_null(value1, value2, value3) -> bool; */
rl.is_null = MochiKit.Base.isNull;
rl.is_undefined = MochiKit.Base.isUndefinedOrNull;

function $null() {
    return MochiKit.Base.isNull.apply(MochiKit.Base, arguments);
}

function $undefined() {
    return MochiKit.Base.isUndefinedOrNull.apply(MochiKit.Base, arguments);
}

var Path = {
    join: function() {
        var joined = '';
        MochiKit.Iter.forEach(arguments, function(part) {
            part = part.toString();
            if(!joined) {
                joined += part;
            } else if(!rl.String.ends_with(joined, '/')) {
                joined += '/' + part;
            } else {
                joined += part;
            }
        });
        return joined;
    }
};

//
// `Cookie.js`, from Carlos Reche
// http://wiki.script.aculo.us/scriptaculous/show/Cookie
// Expanded by J.Shell to allow option setting (path, expires, etc).
// 
rl.Cookie = {
    allowedKeys: ['expires', 'max-age', 'path', 'domain', 'secure'],
    getOptions: function(options) {
        var m = MochiKit.Base;
        options = MochiKit.Base.update({
            path: '/'
        }, options || {});

        if(rl.is_undefined(options.expires, options.daysToExpire)) {
            options.daysToExpire = 365;
        }

        if(!rl.is_undefined(options.daysToExpire)) {
            var d = new Date();
            d.setTime(d.getTime() + (86400000 * parseFloat(options.daysToExpire)));
            options.expires = d.toGMTString();
        }

        var newopts = {};
        MochiKit.Iter.forEach(rl.Enumerable.pairs(options), function(pair) {
            if(rl.Enumerable.includes(rl.Cookie.allowedKeys, pair.key)) {
                newopts[pair.key] = pair.value;
            }
        });
        return newopts;
    },

    optionString: function(options) {
        var opts = [];
        MochiKit.Iter.forEach(rl.Cookie.allowedKeys, function(item) {
            if(MochiKit.Base.isUndefinedOrNull(options[item])) {
                return;
            }
      
            var opt = options[item];
            if((item == 'secure') && (opt == true)) {
                opts.push('secure');
                return;
            } else {
                opts.push(escape(item)+'='+opt);
            }
        });
    
        if(!opts.length) {
            return "";
        }

        return ';' + opts.join(';') + ';';
    },
  
    set: function(name, value, options) {
        options = rl.Cookie.optionString(rl.Cookie.getOptions(options));
        return (document.cookie = escape(name) + '=' + escape(value || '') + options);
    },
    get: function(name) {
        var cookie = document.cookie.match(new RegExp('(^|;)\\s*' + escape(name) + '=([^;\\s]*)'));
        return (cookie ? unescape(cookie[2]) : null);
    },
    erase: function(name) {
        var cookie = rl.Cookie.get(name) || true;
        rl.Cookie.set(name, '', -1);
        return cookie;
    },
    accept: function() {
        if (typeof(navigator.cookieEnabled) == 'boolean') {
            return navigator.cookieEnabled;
        }
        rl.Cookie.set('_test', '1');
        return (rl.Cookie.erase('_test') == '1');
    }
};

