// ynh.js
// (c)2005 YNH.org
// 2005-04-01

function YNH() {
    var i, n, o;
    for (i = 0; i < arguments.length; ++i) {
        o = arguments[i];
        if (isObject(o)) {
            for (n in o) {
                this[n] = o[n];
            }
        }
    }
}
YNH.copyright = '(c)2005 Your Name Here';
YNH.com = 'http://www.YNH.com/';
YNH.param = {};
YNH.seq = 0;
YNH.WARNING =
    'This package is still under development. Interfaces will change.';

var global = this;

function isAlien(a) {
    return isObject(a) && typeof a.constructor != 'function';
}

function isArray(a) {
    return isObject(a) && a.______array == '______array';
}

function isBoolean(a) {
    return typeof a == 'boolean';
}

function isEmpty(o) {
    if (isObject(o)) {
        var i, v;
        for (i in o) {
            v = o[i];
            if (!isFunction(v) && !isUndefined) {
                return false;
            }
        }
    }
    return true;
}

function isFunction(a) {
    return typeof a == 'function';
}

function isNull(a) {
    return typeof a == 'object' && !a;
}

function isNumber(a) {
    return typeof a == 'number' && isFinite(a);
}

function isObject(a) {
    return (typeof a == 'object' && !!a) || isFunction(a);
}

function isString(a) {
    return typeof a == 'string';
}

function isUndefined(a) {
    return typeof a == 'undefined';
}

function object(o) {
    var f = function () {};
    f.prototype = o;
    return new f();
}

function stringify(arg, depth) {
    var i, o, v;
    depth = (isNumber(depth)) ? depth - 1 : 10;
    switch (typeof arg) {
    case 'object':
        if (arg) {
            if (arg.constructor == Array) {
                if (depth < 0) {
                    return '[...]';
                }
                o = '[';
                for (i = 0; i < arg.length; ++i) {
                    v = stringify(arg[i], depth);
                    if (v != 'function' && !isUndefined(v)) {
                        o += (o != '[' ? ',' : '') + v;
                    } else {
                        o += ',';
                    }
                }
                return o + ']';
            } else if (typeof arg.toString != 'undefined') {
                if (depth < 0) {
                    return '{...}';
                }
                o = '{';
                for (i in arg) {
                    v = stringify(arg[i], depth);
                    if (v != 'function' && !isUndefined(v)) {
                        o += (o != '{' ? ',' : '') + i.quote() + ':' + v;
                    }
                }
                return o + '}';
            } else {
                return;
            }
        }
        return 'null';
    case 'unknown':
    case 'undefined':
        return;
    case 'string':
        return arg.quote();
    case 'function':
        return 'function';
    default:
        return String(arg);
    }
}

Function.prototype.method = function (n, f) {
    this.prototype[n] = f;
    return this;
};

Function.returnFalse = function () {
    return false;
};

Function.returnThis = function () {
    return this;
};

if (!isFunction(Function.apply)) {
    Function.method('apply', function (o, a) {
        var r, x = '______apply';
        if (!isObject(o)) {
            o = {};
        }
        o[x] = this;
        switch ((a && a.length) || 0) {
        case 0:
            r = o[x]();
            break;
        case 1:
            r = o[x](a[0]);
            break;
        case 2:
            r = o[x](a[0], a[1]);
            break;
        case 3:
            r = o[x](a[0], a[1], a[2]);
            break;
        case 4:
            r = o[x](a[0], a[1], a[2], a[3]);
            break;
        case 5:
            r = o[x](a[0], a[1], a[2], a[3], a[4]);
            break;
        case 6:
            r = o[x](a[0], a[1], a[2], a[3], a[4], a[5]);
            break;
        default:
            alarm('Too many arguments to apply.');
        }
        delete o[x];
        return r;
    });
}

Function.
    method('inherits', function (u) {
        var d = 0, p = (this.prototype = new u());
        this.method('uber', function uber(n) {
            var f, r, t = d, v = u.prototype;
            if (t) {
                while (t) {
                    v = v.constructor.prototype;
                    t -= 1;
                }
                f = v[n];
            } else {
                f = p[n];
                if (f == this[n]) {
                    f = v[n];
                }
            }
            d += 1;
            r = f.apply(this, YNH.slice(arguments, 1));
            d -= 1;
            return r;
        });
        return this;
    }).
    method('swiss', function (p) {
        for (var i = 1; i < arguments.length; i += 1) {
            var name = arguments[i];
            this.prototype[name] = p.prototype[name];
        }
        return this;
    });

if (!isFunction(Array.prototype.splice)) {
    Array.
        method('pop', function () {
            return this.splice(this.length - 1, 1)[0];
        }).
        method('push', function () {
            this.splice.apply(this,
                [this.length, 0].concat(YNH.slice(arguments)));
            return this.length;
        }).
        method('shift', function () {
            return this.splice(0, 1)[0];
        }).
        method('splice', function (s, d) {
            var max = Math.max,
                min = Math.min,
                a = [], // The return value array
                i = max(arguments.length - 2, 0),   // insert count
                k = 0,
                l = this.length,
                n,  // new length
                o,
                v,  // delta
                x;  // items past the start point
            s = s || 0;
            if (s < 0) {
                s += l;
            }
            s = max(min(s, l), 0);  // start point
            d = max(min(isNumber(d) ? d : l, l - s), 0);    // delete count
            v = i - d;
            n = l + v;
            while (k < d) {
                o = this[s + k];
                if (!isUndefined(o)) {
                    a[k] = o;
                }
                k += 1;
            }
            x = l - s - d;
            if (v < 0) {
                k = s + i;
                while (x) {
                    this[k] = this[k - v];
                    k += 1;
                    x -= 1;
                }
                this.length = n;
            } else if (v > 0) {
                k = 1;
                while (x) {
                    this[n - k] = this[l - k];
                    k += 1;
                    x -= 1;
                }
            }
            for (k = 0; k < i; ++k) {
                this[s + k] = arguments[k + 2];
            }
            return a;
        }).
        method('unshift', function () {
            this.splice.apply(this, [0, 0].concat(YNH.slice(arguments)));
            return this.length;
        });
}

Array.
    method('fireEach', function () {
        var i, j;
        for (i = 0; i < this.length; ++i) {
            j = this[i];
            if (j && j.fire) {
                j.fire.apply(j, arguments);
            }
        }
    }).
    method('forEach', function (f) {
        var a = YNH.slice(arguments, 1), m = f, o, r = [];
        for (var i = 0; i < this.length; ++i) {
            o = this[i];
            if (!isFunction(f)) {
                m = o[f];
            }
            if (isFunction(m)) {
                r[i] = m.apply(this[i], a);
            }
        }
        return r;
    }).
    method('indexOf', function (o, initial) {
        for (var i = initial || 0; i < this.length; ++i) {
            if (this[i] == o || (isFunction(o) &&
                    o.apply(this[i], YNH.slice(arguments, 2)))) {
                return i;
            }
        }
        return -1;
    }).
    method('______array', '______array');

String.
    method('entityify', function () {
        return this.replace(/&/g, '&amp;').replace(/</g,
            '&lt;').replace(/>/g, '&gt;');
    }).
    method('quote', function () {
        var c, i, l = this.length, o = '"';
        for (i = 0; i < l; i += 1) {
            c = this.charAt(i);
            if (c >= ' ') {
                if (c == '\\' || c == '"') {
                    o += '\\';
                }
                o += c;
            } else {
                switch (c) {
                case '\b':
                    o += '\\b';
                    break;
                case '\f':
                    o += '\\f';
                    break;
                case '\n':
                    o += '\\n';
                    break;
                case '\r':
                    o += '\\r';
                    break;
                case '\t':
                    o += '\\t';
                    break;
                default:
                    c = c.charCodeAt();
                    o += '\\u00' + Math.floor(c / 16).toString(16) +
                        (c % 16).toString(16);
                }
            }
        }
        return o + '"';
    }).
    method('supplant', function (o) {
        var i, j, s = this, v;
        o = o || YNH.param;
        for (;;) {
            i = s.lastIndexOf('{');
            if (i < 0) {
                break;
            }
            j = s.indexOf('}', i);
            if (j < 0) {
                break;
            }
            v = o[s.substring(i + 1, j)];
            if (!isString(v) && !isNumber(v)) {
                break;
            }
            s = s.substring(0, i) + v + s.substring(j + 1);
        }
        return s;
    }).
    method('trim', function () {
        return this.replace(/^\s*(\S*(\s+\S+)*)\s*$/, '$1');
    });

YNH.
    method('fire', function (m) {
        var e,
            f,
            i,
            v = this.registry && this.registry[isString(m) ? m : m.type];
        if (isArray(v)) {
            for (i = 0; i < v.length; ++i) {
                e = v[i];
                f = e.method;
                if (isString(f)) {
                    f = this[f];
                }
                f.apply(this, e.parameters || [m]);
            }
        }
        return this;
    }).
    method('forward', function (type, receiver) {
        return this.on(type, function (m) {
            receiver.fire(m);
        });
    }).
    method('off', function (type) {
        if (this.registry) {
            if (type) {
                delete this.registry[type];
            } else {
                delete this.registry;
            }
        }
        return this;
    }).
    method('on', function (type, method, parameters) {
        if (parameters !== undefined && !isArray(parameters)) {
            parameters = [parameters];
        }

        var e = {method: method, parameters: parameters},
            v = this.registry;
        if (!v) {
            v = this.registry = {};
        }
        if (isArray(v[type])) {
            v[type].push(e);
        } else {
            v[type] = [e];
        }
        return this;
    }).
    method('ordered', function () {
        var a = (this.bag = []);

        this.add = function (o) {
            if (isObject(o)) {
                if (o.holder) {
                    o.holder.remove(o);
                }
                o.holder = this;
            }
            a.push(o);
            return this;
        };

        this.fireEach = function (message) {
            a.fireEach.apply(a, arguments);
            return this;
        };

        this.forEach = function (method) {
            return a.forEach.apply(a, arguments);
        };

        this.get = function (i) {
            var aj, j;
            if (isNumber(i)) {
                return a[i];
            } else {
                for (j = 0; j < a.length; ++j) {
                    aj = a[j];
                    if (isObject(aj) && aj.id == i) {
                        return aj;
                    }
                }
            }
        };

        this.remove = function (i) {
            if (isNumber(i)) {
                if (isObject(a[i])) {
                    a[i].holder = null;
                }
                a.splice(i, 1);
            } else {
                for (var j = 0; j < a.length; ++j) {
                    if (a[j] == i || a[j].id == i) {
                        if (isObject(a[j])) {
                            a[j].holder = null;
                        }
                        a.splice(j, 1);
                        break;
                    }
                }
            }
            return this;
        };

        return this;
    }).
    method('unordered', function () {
        var b = {};
        this.bag = b;

        this.add = function (o) {
            if (o.holder) {
                o.holder.remove(o);
            }
            o.holder = this;
            b[o.id] = o;
            return this;
        };

        this.fireEach = function () {
            for (var j in b) {
                b[j].fire.apply(b[j], arguments);
            }
            return this;
        };

        this.forEach = function (method) {
            var a = [],
                j,
                m = method,
                o,
                r = YNH.slice(arguments, 1);
            for (j in b) {
                o = b[j];
                if (!isFunction(method)) {
                    m = o[method];
                }
                if (isFunction(m)) {
                    a[j] = m.apply(o, r);
                }
            }
            return a;
        };

        this.get = function (i) {
            return b.hasOwnProperty(i) ? b[i] : undefined;
        };

        this.remove = function (i) {
            if (!isString(i)) {
                i = i.id;
            }
            b[i].holder = null;
            delete b[i];
            return this;
        };

        return this;
    });


YNH.slice = function (a, f, t) {
    var p;
    switch (arguments.length) {
    case 0:
        return [];
    case 1:
        p = [];
        break;
    case 2:
        p = [f];
        break;
    default:
        p = [f, t];
    }
    return Array.prototype.slice.apply(a, p);
};

