/**
    @fileoverview
    Содержит базовые вспомогательные функции и классы
*/

var DEBUG = 1;

/*if (DEBUG > 0) {
    Object.prototype.dump = function()
    {
        if (this.constructor == Number
            || this.constructor == String
            || this.constructor == Boolean) {
            return this.toString();
        } else if (this.constructor == Function) {
            return "function";
        }
        var result = "";
        for (var propName in this) {
            if (propName == "dump" || propName == "alertDump") {
                continue;
            }
            if (result.length) {
                result += ", ";
            }
            result += propName + ": {"
                + ((this[propName]) ? this[propName].dump() : "null") + "}";
        }
        return "Object: " + result;
    }

    Object.prototype.alertDump = function()
    {
        alert(this.dump());
    }

    Array.prototype.dump = function()
    {
        var result = "";
        for (var i = 0, len = this.length; i < len; i++) {
            if (result.length) {
                result += ", ";
            }
            result += i + ": {" + this[i].dump() + "}";
        }
        return "Array: " + result;
    }

    Number.prototype.dump = function() { return this.toString(); };
    String.prototype.dump = function() { return this.toString(); };
    Boolean.prototype.dump = function() { return this.toString(); };
} */

/**
    @class Используется для реализации механизма интерфесов. При необходимости
        обеспечить наличие определенного набора методов в разных классах можно
        воспользоваться методом ensureImplements:
    @constructor
*/
var Interface = function(name, methods)
{
    if (arguments.length != 2) {
        throw new Error("Конструктор класса Interface должен вызываться"
            + " с двумя аргуметами");
    }
    this.name = name;
    this.methods = [];
    for (var i = 0, len = methods.length; i < len; i++) {
        if (typeof(methods[i]) !== "string") {
            throw new Error("Имена методов должны передаваться конструктору"
                + " класса Interface как строки");
        }
        this.methods.push(methods[i]);
    }
};

/**
    Возвращает true если в объекте присутствую все методы, перечисленные
    в данном интерфейсе

    @param {Object} object Объект для проверки на соответствие интерфейсу
    @return Реализуется или нет данный интерфейс в указанном объекте
    @type Boolean
*/
Interface.ensureImplements = function(object)
{
    if (arguments.length < 2) {
        throw new Error("Функции Interface.ensureImplements должно быть"
            + " передано как минимум 2 аргумента");
    }
    for (var i = 1, len = arguments.length; i < len; i++) {
        var interface = arguments[i];
        if (interface.constructor !== Interface) {
            throw new Error("Аргументы функции Interface.ensureImplements"
                + " начиная со 2-го должны быть экземплярами класса Interface");
        }
        for (var j = 0, mLen = interface.methods.length; j < mLen; j++) {
            var method = interface.methods[j];
            if (! object[method] || typeof object[method] !== "function") {
                throw new Error("Interface.ensureImplements: \n"
                    + "объект не описывает интерфейс " + interface.name
                    + ". Метод " + method + " не найден.");
            }
        }
    }
};

/**
    Выполняет наследование одного класса subClass от superClass с возможностью
    последующего вызова перекрытых родительских методов через свойство
    superclass

    @param {Object} subClass
    @param {Object} superClass
    @type null
*/
var extend = function(subClass, superClass)
{
    var TEMP = function() {};
    TEMP.prototype = superClass.prototype;
    subClass.prototype = new TEMP();
    subClass.prototype.constructor = subClass;

    subClass.superclass = superClass.prototype;
    if (superClass.prototype.constructor == Object.prototype.constructor) {
        superClass.prototype.constructor = superClass;
    }
};

var instantiate = function(object, givingClass)
{
    if (! object || ! givingClass || object.instantiated) {
        return null;
    }
    if (arguments[2]) {
        for (var i = 2, len = arguments.length; i < len; i++) {
            object[arguments[i]] = givingClass.prototype[arguments[i]];
        }
    } else {
        for (method in givingClass.prototype) {
            object[method] = givingClass.prototype[method];
        }
    }
    object.instantiated = true;
    if (typeof object.initialize === "function") {
        object.initialize.call(object);
    }
    return object;
}


var WRAP_ALL = 7;
var WRAP_PROPERTIES = 3;
var WRAP_METHODS = 5;

var wrapMethod = function(methodName, result, proto)
{
    var method = proto.wrappedMethods[methodName];
    if (! method) {
        return;
    }
    var type = null;
    try {
        eval("type = " + method.type + ";");
    } catch (e) {alert("!!!" + methodName); return}
    result["on_" + methodName] = new Publisher();
    var native = true;
    if (typeof result.wrapped[methodName] === "undefined") {
        native = false;
    }
/*    try {
        result.wrapped[methodName]();
    } catch (e) {
        native = false;
    }*/
//    if (method.type && method.type.prototype && method.type.prototype.wrapper) {
    if (! native) {
        result[methodName] = function(name, m, p) {
            return function() {
//alert("wrapped method " + name);
                try {
//                    alert(arguments[0].nodeType);
                    var result = wrap(p[name].apply(this, arguments), m.type);
//                var result = p[name].call(this, arguments);
                } catch (e) {
                    throw new Error("WRAPPER: Требуемая функция " + name + " отсутствует. " + e);
                }
                if (m.onchange) {
                    for (i = 0, len = m.onchange.length; i < len; i++) {
                        var properties = m.onchange[i].properties;
                        var methods = m.onchange[i].methods;
                        var mLen = methods.length;
                        for (j = 0, len < properties.length; j < len; j ++) {
                            var func = result["set_" + properties[j]];
                            if (typeof func !== "function") {
                                continue;
                            }
                            for (k = 0; k < mLen; k++) {
                                func.subscribe(this["on_" + methods[k]],
                                    result);
                            }
                        }
                    }
                }
                if (m.changed) {
                    for (i = 0, len = m.changed.length; i < len; i++) {
                        this["set_" + m.changed[i]]();
                    }
                }
                this["on_" + name].deliver();
                return result;
            }
        }(methodName, method, proto);
    } else {
//alert("wrapping native method " + methodName);
        result[methodName] = function(name, m) {
            return function(a1, a2, a3, a4, a5, a6, a7, a8, a9) {
alert("wrapped native method " + name + " type " + m.type);
                var result = wrap(this.wrapped[name](a1, a2, a3, a4, a5, a6, a7, a8, a9), m.type);
                if (m.changed) {
                    for (i = 0, len = m.changed.length; i < len; i++) {
                        this["set_" + m.changed[i]]();
                    }
                }
                this["on_" + name].deliver();
//alert("END wrapped native method " + name + " type " + m.type);
                return result;
            }
        }(methodName, method);
    }
}

var wrapProperty = function(propName, result, proto)
{
    var property = proto.wrappedProperties[propName];
    if (! property) {
        return;
    }
    var type = null;
    try {
        eval("type = " + property.type + ";");
    } catch (e) {alert("###" + propName); return}
//    var native = true;
//    if (typeof result.wrapped[propName] === "undefined") {
//        native = false;
//    }
    if (type && type.prototype && type.prototype.wrapper) {
//    if (! native) {
//alert("wrapping property " + propName);
//        result[propName] = wrap(result.wrapped[propName], property.type);
        if (! result["get_" + propName]) {
            result["get_" + propName] = function(name, prop) {
                return function() {
                    var type = (arguments[0]) ? arguments[0].toString() : prop.type;
                    var result = wrap(this.wrapped[name], type);
                    if (prop.onchange) {
                        for (i = 0, len = prop.onchange.length; i < len;
                            i++) {
                            var properties = prop.onchange[i].properties;
                            var methods = prop.onchange[i].methods;
                            var mLen = methods.length;
                            for (j = 0, len < properties.length; j < len; j ++) {
                                var func = result["set_" + properties[j]];
                                if (typeof func !== "function") {
                                    continue;
                                }
                                for (k = 0; k < mLen; k++) {
                                    func.subscribe(this["on_" + methods[k]],
                                        result);
                                }
                            }
                        }
                    }
                    return result;
                }
            }(propName, property);
        }
        if (! result["set_" + propName]) {
            result["set_" + propName] = function(name, prop) {
                return function() {
                    this[name] = wrap(this.wrapped[name], prop.type);
                }
            }(propName, property);
        }
    } else {
//alert("wrapping native property " + propName);
        result["get_" + propName] = function(name) {
            return function() {
//alert("wrapped native property " + name);
                return this.wrapped[name];
            }
        }(propName);
        result["set_" + propName] = function(name) {
            return function(prop) {
                alert(name);
                if (name && name != "") this.wrapped[name] = prop;
            }
        }(propName);
    }
//    return result;
}

var wrapMethods = function(result, givingClass)
{
    if (givingClass.superclass) {
        wrapMethods(result, givingClass.superclass);
    }
    var proto = (givingClass.prototype) ? givingClass.prototype : givingClass;
    if (proto.wrappedMethods) {
        for (methodName in proto.wrappedMethods) {
            wrapMethod(methodName, result, proto);
        }
    }
}

var wrapProperties = function(result, givingClass)
{
    if (givingClass.superclass) {
        wrapProperties(result, givingClass.superclass);
    }
    var proto = (givingClass.prototype) ? givingClass.prototype : givingClass;
    if (proto.wrappedProperties) {
        for (propName in proto.wrappedProperties) {
//            result[propName] = result.wrapped[propName];
            wrapProperty(propName, result, proto);
        }
    }
}

var wrap = function(object, givingClass)
{
    if (! object) {
        return null;
    }
    var givingClassString = givingClass;
    try {
        givingClass = eval(givingClass + ";");
//        if (givingClass && givingClass.prototype && givingClass
//        givingClass = eval("WRAPPER_" + givingClass + ";");
    } catch (e) {
        return object;
    }
    if (object instanceof givingClass ||
        givingClass.prototype && ! givingClass.prototype.wrapper) {
        return object;
    }
    var result = null;
    if (object.wrapped) {
        result = object;
    } else {
        result = new givingClass();
        result.wrapped = object;
    }
    wrapMethods(result, givingClass);
    wrapProperties(result, givingClass);
/*    if (typeof result.initialize === "function") {
        result.initialized = false;
        result.initialize.call(result);
    } else {
        result.initialized = true;
    }

    if (DEBUG >= 2) {
        var str = "";
        if (givingProto.wrappedMethods) {
            str += "\nMETHODS:\n" + givingProto.wrappedMethods.dump();
        }
        if (givingProto.wrappedProperties) {
            str += "\nPROPERTIES:\n" + givingProto.wrappedProperties.dump();
        }
        alert("WRAPPING:" + str);
    }*/
    return result;
}

var wrapPrototype = function(classString, wrapperClass) {
    if (! wrapperClass.prototype || ! wrapperClass.prototype.wrapper) {
        return;
    }
    var checkedClass = undefined;
    try {
        eval("checkedClass = " + classString);
    } catch (error) {};
    var proto = wrapperClass.prototype;
    if (typeof checkedClass === "undefined" || ! checkedClass.prototype) {
        window[classString] = function() {};
        window[classString].prototype = proto;
        window[classString].prototype.wrapper = true;
    } else if (proto.testInstance) {
        var inst = proto.testInstance();
        if (inst) {
 //           alert("testing " + classString);
            for (method in proto.wrappedMethods) {
                if (typeof inst[method] !== "undefined") {
                    wrapperClass.prototype.wrappedMethods[method] = null;
                } else {
                    if (proto[method]) {
                        alert("wrapping method " + method + " of " + classString);
                        checkedClass.prototype[method] = proto[method];
                    } else {
                        alert("wrapping method " + method + " not present for " + classString);
                        throw new Error("wrapping method " + method + " not present for " + classString);
                    }
                }
            }
            for (prop in proto.wrappedProperties) {
                if (typeof inst[prop] !== "undefined") {
                    wrapperClass.prototype.wrappedProperties[prop] = null;
                } else {
                    alert(prop);
                    wrapProperty(method, checkedClass.prototype, proto);
                }
            }
        } else {
//            window[classString] = function() {};
//            window[classString].prototype = proto;
        }
    }
}

var checkAndWrap = function(checkedClassString, wrapperClass)
{
    if (! wrapperClass.prototype || ! wrapperClass.prototype.wrapper) {
        return;
    }
    var checkedClass = undefined;
    try {
        eval("checkedClass = " + checkedClassString);
    } catch (error) {}
    if (typeof checkedClass === "undefined" || ! checkedClass.prototype) {
        window[checkedClassString] = function() {};
        window[checkedClassString].prototype = wrapperClass.prototype;
        window[checkedClassString].prototype.wrapper = true;
    }
/*    } else {
        var proto = wrapperClass.prototype;
        if (proto.wrappedMethods) {
            for (methodName in proto.wrappedMethods) {
                var shouldWrapped = true;
                for (var name in checkedClass.prototype) {
                    if (name == methodName) {
                        shouldWrapped = false;
                        break;
                    }
				}
                for (var name in checkedClass) {
                    if (name == methodName) {
                        shouldWrapped = false;
                        break;
                    }
				}
                if (shouldWrapped) {
//alert(typeof checkedClass + " " + typeof checkedClass.prototype + " " + typeof checkedClass[methodName] + " " + checkedClass.prototype[methodName]);
alert("wrapping method " + methodName + " for " + checkedClassString + ": " + checkedClass.prototype[methodName]);
                    checkedClass.prototype.wrapper = WRAP_METHODS;
                    wrapMethod(methodName, checkedClass.prototype, proto);
                }
            }
        }
        if (wrapperClass.prototype.wrappedProperties) {
            for (propName in proto.wrappedProperties) {
                var property = checkedClass[propName];
                if (! property && checkedClass.prototype) {
                    try {
                        property = checkedClass.prototype[propName];
                    } catch (error) {
/* TODO: проверить, всегда ли при попытке доступа к nativeProperty в
         FireFox возникает именно "NS_ERROR_XPC_BAD_OP_ON_WN_PROTO" ошибка */
/*                        if (error.name === "NS_ERROR_XPC_BAD_OP_ON_WN_PROTO") {
                            property = 1;
                        }
                    }
                }
                if (! property) {
                    checkedClass.prototype.wrapper = WRAP_PROPERTIES;
                    wrapProperty(propName, checkedClass.prototype, proto);
alert("wrapping property " + propName + " for " + checkedClassString);
                }
            }
        }
    }*/
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

var ajax = new Object();

ajax.onload = null;

ajax.onloadwindow = function()
{
    if (ajax.onload) {
        ajax.onload.deliver();
    }
}


/*
    ГЛОБАЛЬНЫЕ ОБЪЯВЛЕНИЯ
*/

var Publisher = function()
{
    this.subscribers = [];
    this.owners = [];
};

Publisher.prototype.deliver = function()
{
    for (var i = 0, len = this.subscribers.length; i < len; i++) {
        setTimeout((function (callback, owner, args) {
            return function (){
                callback.apply(owner || window, args);
            }})(this.subscribers[i], this.owners[i], arguments), 1);
//        this.subscribers[i].apply(this.owners[i], arguments);
    }
};

Function.prototype.subscribe = function(publisher, owner)
{
    if (! publisher || ! publisher instanceof Publisher) {
        return;
    }
    if (typeof owner !== "object") {
        owner = null;
    }
    var subscriber = this;
    for (var i = 0, len = publisher.subscribers.length; i < len; i++) {
        if (publisher.subscribers[i] === subscriber
            && publisher.owners[i] === owner) {
            return;
        }
    }
    publisher.subscribers.push(this);
    publisher.owners.push(owner);
};

Function.prototype.unsubscribe = function(publisher, owner)
{
    if (! publisher instanceof Publisher) {
        return;
    }
    if (typeof owner !== "object") {
        owner = null;
    }
    var subscriber = this;
    for (var i = 0, len = publisher.subscribers.length; i < len; i++) {
        if (publisher.subscribers[i] === subscriber
            && publisher.owners[i] === owner) {
            publisher.subscribers.splice(i, 1);
            publisher.owners.splice(i, 1);
            return;
        }
    }
};

/*
    ЛОКАЛЬНЫЕ ОБЪЯВЛЕНИЯ
*/

var events = new Object();

events.addEvent = function(target, event, handler)
{
	if (! target) {
        return;
    }
	if (target.addEventListener) {      /* DOM-совместимые браузеры */
		target.addEventListener(event, handler, false);
	} else if (target.attachEvent) {    /* IE */
		target.attachEvent("on" + event, handler);
	} else {                            /* остальные */
		target["on" + event] = handler;
	}
};

events.removeEvent = function(target, event, handler)
{
    if (! target) {
        return;
    }
    if (target.removeEventListener) {   /* DOM-совместимые браузеры */
        target.removeEventListener(event, handler, false);
    } else if (target.detachEvent) {    /* IE */
        target.detachEvent("on" + event, handler);
    } else {                            /* остальные */
        target["on" + event] = null;
    }
};

events.formatEvent = function(event)
{
    if (typeof event.stopPropagation !== "function") { /* IE */
        event.charCode = (event.type == "keypress") ? event.keyCode : 0;
        event.eventPhase = 2;
        event.isChar = (event.charCode > 0);
        event.pageX = event.clientX + document.body.scrollLeft;
        event.pageY = event.clientY + document.body.scrollTop;
        event.preventDefault = function() {
            this.returnvalue = false;
        }
        if (event.type == "mouseout") {
            event.relatedTarget = event.toElement;
        } else if (event.type == "mouseover") {
            event.relatedTarget = event.fromElement;
        }
        event.stopPropagation = function() {
            this.cancelBubble = true;
        }
        event.target = event.srcElement;
        event.time = (new Date).getTime();
    }
    return event;
};

events.getEvent = function()
{
    if (window.event) {
        return events.formatEvent(window.event);
    } else {
        return events.getEvent.caller.arguments[0];
    }
};

/* http://wdh.suncloud.ru/dhtml11.htm */

var DOMPublisher = function(element, eventName)
{
//    if (! element instanceof Element) return;
    DOMPublisher.superclass.constructor.call(this);
    this.id = eventName + "_" + element.id;
    var listener = this;
    events.addEvent(element, eventName, function() {
        var event = events.getEvent();
//        alert(listener.id);
        listener.deliver(event);
    });
};
extend(DOMPublisher, Publisher);

events.getDOMPublisher = function(element, event)
{
    if (typeof event !== "string") {
        return;
    }
    if (typeof element !== "object") {
        element = document.getElementById(element.toString());
    }
    if (! element) {
        return;
    }
    var eventName = "__" + event + "__";
    if (! element[eventName]) {
        element[eventName] = new DOMPublisher(element, event);
    }
    return element[eventName];
};

if (! ajax.onload) {
    ajax.onload = new Publisher;
    ajax.onloadwindow.subscribe(events.getDOMPublisher(window, "load"));
}
