﻿/*jshint maxlen:500, browser:true */
/*global Type:true, BL:true, Node:true, $find:false, Sys:true */
/*
* todo: put anything that depends on ASP.NET AJAX under BL.WebTools or BL.ASP
*
*
*/


/***** Essential Utilities *****/
function AssertException(message) { this.message = message; }
AssertException.prototype.toString = function () {
    return 'AssertException: ' + this.message;
};

function assert (exp, message) {
    if (!exp) {
        throw new AssertException(message);
    }
}

function imports (src, scope) {
    scope = scope || window;
    for (var i in src) if (src.hasOwnProperty(i)) scope[i] = src[i];
}

window.Type = window.Type || {};
window.Type.registerNamespace = window.Type.registerNamespace || function (ns) {
    var d = window;
    var parts = ns.split(".");
    for (var i = 0; i < parts.length; i++) {
        var curPart = parts[i];
        var curObj = d[curPart];
        if (!curObj) {
            d[curPart] = {};
        }
        d = curObj;
    }
};


/***** BL *****/
Type.registerNamespace("BL");

BL.log = function (msg) {
    // after: http://james.padolsey.com/javascript/debugging-with-log-or-log/
    var logger = (window.console && console.log) ? console.log : (opera && opera.postError) ? opera.postError : null;
    logger && logger(msg || this);
    return BL.log.cache.push([ msg, new Date() ]) - 1;
};

BL.log.cache = [];

BL.logObject = function logObject(o, functionBodies, showPrivate) {
    for (var i in o) {
        if (showPrivate === true || i.substr(0, 1) !== "_") {
            if (functionBodies === true || typeof (o[i]) !== "function") {
                BL.log(String.format("{0}: {1}", i, o[i]));
            } else {
                BL.log(String.format("{0}: function", i));
            }
        }
    }
};

BL.Dictionary = function () {

    BL.Dictionary.prototype.getLength = function () {
        var i = 0;
        this.walk(function () { i++; });
        return i;
    };

    BL.Dictionary.prototype.walk = function (cb, data) {
        // callback signature: function (name, value, data)
        for (var n in this) if (this.hasOwnProperty(n)) cb(n, this[n], data);
    };

    BL.Dictionary.prototype.getKeys = function () {
        var keys = [];
        this.walk(function (n) { keys.push(n); });
        return keys;
    };

    BL.Dictionary.prototype.getValues = function () {
        var values = [];
        this.walk(function (n, v) { values.push(v); });
        return values;
    };

    BL.Dictionary.prototype.slice = function () {
        var values = [];
        for (var i in arguments) values.push(this.hasOwnProperty(arguments[i]) ? this[arguments[i]] : undefined);
        return values;
    };

    BL.Dictionary.prototype.concat = function () {
        for (var i in arguments) {
            var d = arguments[i];
            for (var n in d) if (d.hasOwnProperty(n)) this[n] = d[n];
        }
    };

    if (arguments) this.concat.apply(this, arguments);

};


BL.Url = function (str) {
    // Based on: http://james.padolsey.com/javascript/parsing-urls-with-the-dom/

    this.source = str;
    this.protocol = null;
    this.host = null;
    this.port = null;
    this.query = null;
    this.hash = null;
    this.path = null;

    this.getBaseName = function () {
        return (this.path.match(/\/([^\/?#]+)$/i) || ['', ''])[1];
    };

    this.getPathParts = function () {
        return this.path.replace(/^\//, '').split('/');
    };

    this.getLocalPart = function () {
        var out = this.path;
        if (this.query) out += String.format('?{0}', this.query);
        if (this.hash) out += String.format('#{0}', this.hash);
        return out;
    };

    this.getParameters = function () {
        return BL.Url.parseUrlEncodedString(this.query);
    };

    this.toString = function () {
        var out = "";
        if (this.protocol) out += String.format('{0}://', this.protocol);

        if (this.host) {
            out += this.host;
            if (this.port) out += String.format(':{0}', this.port);
        }

        if (this.path) out += this.path;
        if (this.query) out += String.format('?{0}', this.query);
        if (this.hash) out += String.format('#{0}', this.hash);

        return out;
    };

    //todo: find authority info as: http://blog.stevenlevithan.com/archives/parseuri

    (function (o) {
        var a = document.createElement('a');
        a.href = o.source;
        o.protocol = a.protocol.replace(':', '');
        o.host = a.hostname;
        o.port = a.port;
        o.query = a.search.substring(1);
        o.hash = a.hash.substring(1);
        o.path = a.pathname.replace(/^([^\/])/, '/$1');
    })(this);

};

BL.Url.parseUrlEncodedString = function (data) {

    var nvc = new BL.Dictionary();

    function initFromArray(data) {
        for (var param in data) {
            if (data.hasOwnProperty(param)) {
                var parts = null;
                parts = data[param].split('=');
                if (parts[1] === undefined) {
                    parts[1] = true;
                }
                nvc[parts[0]] = decodeURIComponent(parts[1]);
            }
        }
    }

    function initFromString(data) {
        if (data.substring(0, 1) === '?') {
            data = data.substring(1);
        }
        initFromArray(data.split('&'));
    }

    if (typeof (data) === 'string') {
        initFromString(data);
    } else {
        initFromArray(data);
    }

    return nvc;
};

/***** BL.Env *****/
Type.registerNamespace("BL.Env");

BL.Env.Url = (function () {
    return BL.Url.parseUrlEncodedString(location.search);
} ());

BL.Env.Cookies = (function () {
    return BL.Url.parseUrlEncodedString(document.cookie.split('; '));
} ());

BL.Env.Server = (function () {

    /*
    Inspired by:
    CGI Object - Copyright 2000 Jeff Howden
    jeff@jeffhowden.com - http://jeffhowden.com/
    */

    var server = new BL.Dictionary();
    server.http_host = location.host;
    server.http_user_agent = navigator.userAgent;
    server.http_cookie = document.cookie;
    server.protocol = location.protocol.split(':')[0];
    server.path_info = location.pathname.split('?')[0];
    server.query_string = location.search.substring(1);
    server.script_name = server.path_info;
    server.http_referer = document.referrer;

    return server;

})();

/***** BL.String *****/
Type.registerNamespace("BL.String");
BL.String.reverse = function (str) {
    return str.split('').reverse().join('');
};

BL.String.truncate = function (str, len, smart) {
    if (typeof smart === "undefined") smart = true;
    str = str.substring(0, len);
    if (smart === true) {
        str = BL.String.reverse(str);
        var pos = str.search(/\W/);
        str = (pos >= 0 ? BL.String.reverse(str.substring(pos, str.length)) : '');
    }
    return str;
};

BL.String.extract = function (str, regex) {
    var matches = str.match(regex);
    return matches && matches.length > 1 ? matches.slice(1) : {};
};

/***** BL.Image *****/
Type.registerNamespace("BL.Image");
BL.Image.getBounds = function (x, y, maxX, maxY) {
    var scale = Math.min((maxX / x), (maxY / y));
    if (scale < 1) {
        x = Math.round(scale * x);
        y = Math.round(scale * y);
    }
    return [x, y];
};


/***** BL.Dom *****/
Type.registerNamespace("BL.Dom");
assert(typeof BL.String !== "undefined", "BL.Dom depends on BL.String");

// Fix IE8 and earlier, and quirks mode
if (typeof window.Node === "undefined") {
    window.Node = {
        ELEMENT_NODE: 1,
        ATTRIBUTE_NODE: 2,
        TEXT_NODE: 3,
        CDATA_SECTION_NODE: 4,
        ENTITY_REFERENCE_NODE: 5,
        ENTITY_NODE: 6,
        PROCESSING_INSTRUCTION_NODE: 7,
        COMMENT_NODE: 8,
        DOCUMENT_NODE: 9,
        DOCUMENT_TYPE_NODE: 10,
        DOCUMENT_FRAGMENT_NODE: 11,
        NOTATION_NODE: 12
    };
}

BL.Dom.isNode = function (o) {
    // http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object/384380#384380
    return (
        typeof Node === "object" ? o instanceof Node :  //DOM2
        typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName === "string" //IE
    );
};

BL.Dom.isElement = function (o) {
    return (
        typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2 
        typeof o === "object" && o.nodeType === Node.ELEMENT_NODE && typeof o.nodeName === "string" //IE
    );
};

BL.Dom.walk = function walk(node, callbacks) {
    /*
    * Traverses the DOM tree at a node in a depth-first fashion. Upon visiting each node,
    * it calls the callback function for that node type, if provided.
    *
    */
    var next;
    if (callbacks[node.nodeType]) callbacks[node.nodeType](node);

    if (node.nodeType === Node.ELEMENT_NODE) {
        if ((node = node.firstChild)) {
            do {
                next = node.nextSibling;
                walk(node, callbacks);
            } while ((node = next));
        }
    }
};

BL.Dom.drop = function (node) {
    node.parentNode.removeChild(node);
};

BL.Dom.truncate = function (node, len) {
    /*
    * Truncates the DOM tree at a node such that its total text length is len.
    * todo: Prune empty ancestor trees of truncated nodes.
    *
    */
    var totlen = 0;
    var cb = [];

    cb[Node.ELEMENT_NODE] = function (node) {
        if (totlen >= len) BL.Dom.drop(node);
    };

    cb[Node.TEXT_NODE] = function (node) {
        if (totlen >= len) {
            BL.Dom.drop(node);
            return;
        }

        totlen += node.data.length;
        if (totlen > len) {
            node.data = BL.String.truncate(node.data, (node.data.length - (totlen - len)), true);
        }
    };

    BL.Dom.walk(node, cb);
};


/***** BL.Telerik *****/
Type.registerNamespace("BL.Telerik");

BL.Telerik.autosizeRadWindow = function (rw) {
    var $el = $(rw.get_contentElement());
    rw.set_width($el.width());
    rw.set_height($el.height());
}

BL.Telerik.autocloseRadWindow = function(rw, timeout) {
    setTimeout(function () { rw.close(); }, timeout);
}

/***** BL.WebTools *****/
Type.registerNamespace("BL.WebTools");
String.format = String.format || function (l, j) {
    // Lifted directly from ASP.NET AJAX for compatibility
    var c = "",
        e = j[0];
    for (var a = 0; true; ) {
        var f = e.indexOf("{", a),
            d = e.indexOf("}", a);
        if (f < 0 && d < 0) {
            c += e.slice(a);
            break;
        }
        if (d > 0 && (d < f || f < 0)) {
            c += e.slice(a, d + 1);
            a = d + 2;
            continue;
        }
        c += e.slice(a, f);
        a = f + 1;
        if (e.charAt(a) === "{") {
            c += "{";
            a++;
            continue;
        }
        if (d < 0) break;
        var h = e.substring(a, d),
            g = h.indexOf(":"),
            k = parseInt(g < 0 ? h : h.substring(0, g), 10) + 1,
            i = g < 0 ? "" : h.substring(g + 1),
            b = j[k];
        if (typeof b === "undefined" || b === null) b = "";
        if (b.toFormattedString) c += b.toFormattedString(i);
        else if (l && b.localeFormat) c += b.localeFormat(i);
        else if (b.format) c += b.format(i);
        else c += b.toString();
        a = d + 1;
    }
    return c;
};

assert(typeof String.format === "function", "BL.WebTools depends on String.format");

BL.WebTools.TokenReplace = function (inString, tokName, tokVal) {
    var empty = (tokVal === 0 || tokVal === "" || tokVal === undefined || tokVal === null);

    var plainToken = String.format("<#{0}#>", tokName);
    var bangToken = String.format("<#\\!{0}#>", tokName);
    var beginConditionalToken = String.format("<#\\?{0}#>", tokName);
    var endConditionalToken = String.format("<#/\\?{0}#>", tokName);

    if (empty) {
        var conditionalExpression = new RegExp(String.format("{0}.*{1}", beginConditionalToken, endConditionalToken), "g");
        inString = inString.replace(conditionalExpression, "");
    } else {
        inString = inString
                    .replace(new RegExp(beginConditionalToken, "g"), "")
                    .replace(new RegExp(endConditionalToken, "g"), "")
                    .replace(new RegExp(plainToken, "g"), tokVal);
    }

    inString = inString.replace(new RegExp(bangToken, "g"), tokVal);

    return inString;
};

BL.WebTools.ValidateDropDownList = function (e, args) {
    var disabled = e.disabled;
    var value = args.Value;
    var rcb = $find(e.controltovalidate);

    if (rcb !== null) {
        disabled = !rcb.get_enabled();
        value = rcb.get_value();
    }
    args.IsValid = disabled || (value !== "");
};

/***** jQuery plugins *****/

(function ($) {
    $.fn.truncateToLength = function (length, suffix) {
        if (typeof suffix === "undefined") suffix = "";
        BL.Dom.truncate(this.get()[0], (length - suffix.length));
        return this.append(suffix);
    };

    $.fn.truncateToHeight = function (height, suffix) {
        // todo: maxI should be parameterised.
        var maxI = 20;
        var i = 0;
        while (i++ < maxI && this.height() > height && this.height() > 0) {
            var l = this.text().length;
            var excessRatio = this.height() / height;
            var lnew = Math.ceil(l / excessRatio);
            this.truncateToLength(lnew, suffix);
        }
        return this;
    };

    $.fn.removeChildrenToHeight = function (height) {
        while (this.children().length > 1 && this.height() > height) {
            this.children().last().remove();
        }
        return this;
    };

    $.fn.isNode = function () {
        return this.length === 1 && BL.Dom.isNode(this.get()[0]);
    };

    $.fn.isElement = function () {
        return this.length === 1 && BL.Dom.isElement(this.get()[0]);
    };

    $.block = function (event, jq) {
        event += '.block';
        return jq
            .unbind(event)
            .bind(event, function () { return false; });
    };

    $.fn.block = function (event) {
        return $.block(event, this);
    };

    $.autoLoad = function (fn) {
        // runs a function onLoad, and if applicable, when MS-AJAX requests return
        var prm = null;
        $(fn);
        if (Sys && Sys.WebForms && Sys.WebForms.PageRequestManager && (prm = Sys.WebForms.PageRequestManager.getInstance()))
            prm.add_endRequest(fn);
    };

    $.fn.ruledHeader = function(settings) {
		
		var config = $.fn.ruledHeader.config;
		
		if (settings) $.extend(config, settings);
		
		function wrap($el) {
            $el
                .css('display','inline');

			var td = $('<td />')
                .css('width', ($el.width() + config.spacing));

			var tr = $('<tr />')
                .append(td)
                .append('<td class="hr" />');

			$('<table />')
                .addClass(config.cssClass)
                .append(tr)
                .insertAfter($el);

			td.append($el);
            $el.data('ruledHeader', true);
		}
		
		var jq = $([1]);
		return this.each(function() {
            jq[0] = this;
            if (! jq.data('ruledHeader')) {
                wrap(jq);
                jq.data('ruledHeader', true);
            }
        });
		
	};
	
	$.fn.ruledHeader.config = {
		cssClass: 'ruledH',
        spacing: 20
	};


    $.fn.toggleChildren = function (settings) {
        //todo: remove() method
        //todo: does default selector equate to children()? Otherwise fix.
        //todo: is startIndex necessary? That could be done by :gt() in the selector.
        var config = $.fn.config;
        if (settings) $.extend(config, settings);

        var $children = this.find(config.selector).slice(config.startIndex);
        var $controls = this.find(config.controlSelector);

        function doToggle() {
            $children.add($controls).toggle();
            return false;
        }

        $children.length ? $controls.bind('click', doToggle) && doToggle() : $controls.hide();

        return this;
    };

    $.fn.config = { startIndex: 0, selector: '> *', controlSelector: 'a.toggleChildren', startToggled: true };

})(jQuery);

/***** jquery.swap.js *****/

(function ($) {

    $.swap = function (source, target, settings) {
        var config = $.swap.defaults;

        if (settings) $.extend(config, settings);

        if (source.length === 1 && target.length === 1) {
            source.fadeOut(config.hideDuration, function () { target.fadeIn(config.showDuration); });
        }
    };

    $.swap.defaults = {
        showDuration: 0,
        hideDuration: 0
    };

    $.fn.swapWith = (function () {
        // usage: $(selector).swapWith(jQuery|HTMLElement, [settings])
        // swapWith silently fails (by design) on collections length > 1
        var jq = $([0]);
        return function (target, settings) {
            if (!target instanceof $) {
                jq[0] = target;
                target = jq;
            }

            $.swap(this, target, settings);

            return this;
        };
    })();

    $.fn.swapWithNext = function (settings) {
        // see: http://james.padolsey.com/javascript/76-bytes-for-faster-jquery/
        if (this.length > 1) {
            var jq = $([1]);
            return this.each(function () {
                jq[0] = this;
                jq.swapWith(jq.next(), settings);
            });
        }

        return this.swapWith(this.next(), settings);
    };

    $.fn.swapWithPrev = function (settings) {
        // see: http://james.padolsey.com/javascript/76-bytes-for-faster-jquery/
        if (this.length > 1) {
            var jq = $([1]);
            return this.each(function () {
                jq[0] = this;
                jq.swapWith(jq.prev(), settings);
            });
        }

        return this.swapWith(this.prev(), settings);
    };

}) (jQuery);


/***** Sizzle plugins *****/

(function (Sizzle) {

    Sizzle.parseArgs = function (str, opts) {
        var out = {};
        out.args = str.replace(' ', '').split(',');
        var i = opts.length;
        while (i-- < 0) out[opts[i]] = out.args[i];
    };

    Sizzle.selectors.filters['icontains'] = function (el, i, match, stack) {
        // http://stackoverflow.com/questions/4574686/jquery-expr-where-to-put
        return (el.textContent || el.innerText || Sizzle.getText(el) || '').toLowerCase().indexOf(match[3].toLowerCase()) >= 0;
    };

    Sizzle.selectors.filters['iexact'] = function (el, i, match, stack) {
        // http://www.ericmmartin.com/creating-a-custom-jquery-selector/
        return (el.textContent || el.innerText || Sizzle.getText(el) || '').toLowerCase() === match[3].toLowerCase();
    };

    Sizzle.selectors.filters['exact'] = function (el, i, match, stack) {
        // http://www.ericmmartin.com/creating-a-custom-jquery-selector/
        return (el.textContent || el.innerText || Sizzle.getText(el) || '') === match[3];
    };

    Sizzle.selectors.filters['mod'] = function (el, i, meta, stack) {
        // usage: ':mod(m, [n])'
        var args = Sizzle.parseArgs(meta[3], ['m', 'n']);
        return i % args.m === args.n;
    };

    Sizzle.selectors.filters['nth-child-mod'] = (function () {
        var lastParent = null;
        var j = 0;
        var args = null;

        return (function (el, i, meta, stack) {
            // usage: ':nth-child-mod(n, m)'
            if (i === 0) {
                args = Sizzle.parseArgs(meta[3], ['n', 'm']);
                lastParent = null;
                j = 0;
            }

            if (el.parentNode !== lastParent) {
                lastParent = el.parentNode;
                j = 0;
            }

            return j % args.m === args.n;
        });

    })();

}) (jQuery.find);
