//
var Mohawk = {
    version: '0.1',
    UI: {
        selection: false,
        selectables: []
    },
    
    Utils: {
    },
    
    css: [],
    css_url: '/public/css/mohawk/',
    addCss: function (url) {
        if (!url.match(new RegExp('^(http://|/)', 'i'))) {
            url = Mohawk.css_url + url;
        }
        if (!Mohawk.css.has(url)) {
            Mohawk.css.push(url);
        }
    },
    loadCss: function () {
        Mohawk.css.forEach(function () {
            Mohawk.importCss(this);
        });
    },
    importCss: function (src) {
        var link = document.createElement('LINK');
        link.href = src;
        link.type = 'text/css';
        link.rel = 'stylesheet';
        document.getElementsByTagName('HEAD')[0].appendChild(link);
    },
    
    js: [],
    js_url: '/system/js/',
    addJs: function (url) {
        if (!url.match(new RegExp('^(http://|/)', 'i'))) {
            url = Mohawk.js_url + url;
        }
        if (!Mohawk.js.has(url)) {
            Mohawk.js.push(url);
        }
    },
    loadJs: function () {
        Mohawk.js.forEach(function () {
            var script = document.createElement('SCRIPT');
            script.type = 'text/javascript';
            script.src = this;
            document.getElementsByTagName('HEAD')[0].appendChild(script);
        });
    }
};
    
var IE = typeof window.ActiveXObject != 'undefined';
var FF = navigator.userAgent.match('Firefox') != null;
var OPERA = typeof window.opera != 'undefined';
var WEBKIT = navigator.userAgent.match('WebKit') != null;

var BTN_LEFT = IE ? 1 : 0;
var BTN_MIDDLE = IE ? 4 : 1;
var BTN_RIGHT = 2;

function is_scalar(variable) {
    return ['number', 'string', 'boolean'].has(typeof variable);
};

function is_numeric(variable) {
    return (typeof variable == 'number') || (typeof variable == 'string' && variable * 1 + '' == variable);
};

function empty(variable) {
    return variable === 0
        || variable === '0'
        || variable === false
        || variable === null
        || typeof variable == 'undefined'
        || variable === []
        || variable === {}
        || !variable;
};

function extend(target, source, directly) {
    if (target.prototype && !directly) {
        extend(target.prototype, source);
    } else {
        for (var i in source) {
            target[i] = source[i];
        }
    }
};

Function.prototype.parse = function () {
    var source = this.toSource instanceof Function ? this.toSource() : this.toString().replace(new RegExp('\n', 'g'), ' ');
    // opera 9.5 does not work with '\\{(.*)\\}'
    // so instead we use \\{([^\x01]*)\\}
    // hope that \x01 character won't appear in code
    var regex = new RegExp('\\(?\\s*function\\s+([a-z$_][a-z$_0-9]*)?\\s*\\(\\s*([^\\)]*)\\s*\\)\\s*\\{([^\x01]*)\\}\\s*\\)?\\s*', 'img');
    var m = null;
    if (m = regex.exec(source)) {
        return {
            name: m[1],
            params: m[2].split(new RegExp('\\s*,\\s*')),
            body: m[3]
        }
    } else {
        return null;
    }
};

var ObjectInterface = {
    exists: function (key) {
        return typeof this[key] != 'undefined';
    },
    
    get: function (key, default_value) {
        if (typeof this[key] != 'undefined') {
            return this[key];
        } else {
            return default_value ? default_value : '';
        }
    },

    has: function (value) {
        for (var i in this) {
            if (value == this[i]) {
                return true;
            }
        }
        return false;
    },

    key: function (value) {
        for (var i in this) {
            if (value == this[i]) {
                return i;
            }
        }
        return false;
    },

    keys: function () {
        var keys = [];
        for (var i in this) {
            if (typeof Object.prototype[i] == 'undefined') {
                keys.push(i);
            }
        }
        return keys;
    },

    forEach: function (action) {
        for (var i in this) {
            if (typeof Object.prototype[i] == 'undefined') {
                action.call(this[i], i, this[i]);
            }
        }
    }
};

var ArrayInterface = {
    last: function () {
        return this.slice(-1)[0];
    },

    pull: function (value) {
        for (var i in this) {
            if (value == this[i]) {
                this.splice(i, 1);
            }
        }
    },

    forEach: function (action) {
        for (var i = 0; i < this.length; i ++) {
            action.call(this[i], i, this[i]);
        }
    },
    
    binSearch: function (value) {
        var l = 0;
        var r = this.length - 1;
        if (this[l] > value) {
            return l;
        }
        if (this[r] < value) {
            return r + 1;
        }
        do {
            var m = Math.round((r + l) / 2);
            if (m == l || m == r || this[m] == value) {
                break;
            } else if (this[m] < value) {
                l = m;
            } else {
                r = m;
            }
        } while (true);
        return m;
    },
    
    intersect: function (array, sorted) {
        if (!sorted) {
            this.sort();
            array.sort();
        }
        var i = 0, j = 0;
        var unique = [];
        while (i < this.length && j < array.length) {
            if (this[i] < array[j]) {
                i ++;
            } else if (this[i] > array[j]) {
                j ++;
            } else {
                unique.push(this[i]);
                i ++;
                j ++;
            }
        }
        return unique;
    }
};

extend(Object, ObjectInterface);
extend(Array, ArrayInterface);

Object.combine = function (keys, values) {
    var obj = {};
    for (var i = 0; i < keys.length; i ++) {
        obj[keys[i]] = values[i];
    }
    return obj;
};

var MathInterface = {
    sign: function (x) {
        return x == 0 ? 0 : (x > 0 ? 1 : -1);
    },

    rand: function (min, max) {
        if (!max) {
            min = 0;
            max = min
        }
        return Math.round(Math.random() * (max - min)) + min;
    }
};
extend(Math, MathInterface);

var NumberInterface = {
    toHex: function () {
        if (this == 0) {
             return '0';
        }
        var hex = '';
        var digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
        var d = this;
        var pow = Math.floor(Math.log(d) / Math.log(16));
        var base = Math.pow(16, pow);
        for (var i = pow; i >= 0; i --) {
            var q = Math.floor(d / base);
            d -= q * base;
            hex += digits[q];
            base /= 16;
        }
        return hex;
    }
};
extend(Number, NumberInterface);

var StringInterface = {
    pad: function (length, chr, right) {
        var str = this;
        var size = length - this.length;
        chr = chr || ' ';
        for (var i = 0; i < size; i ++) {
            str = right ? str.concat(chr) : chr.concat(str); 
        }
        return str;
    }
};
extend(String, StringInterface);

RegExp.quote = function (str) {
    var regexp = new RegExp('([\\<\\>\\/\\?\\.\\$\\^\\[\\]\\*\\+\\\\])', 'g');
    return str.replace(regexp, '\\$1');
};

var Class = function (object) {
    return this.extend(object);
};

var Singletone = function (object) {
    var class_interface = new Class(object);
    var instance = new class_interface;
//    Class.setSelf(instance);
    return instance;
}
Class.prototype = {
    self: null,
    parent: null,
    extend: function (descendant) {
        var child = Class.method(descendant.__construct || (this.prototype && this.prototype.__construct ? this.prototype.__construct : function () {}), 'Class.setSelf(this); var self = this.self; var parent = this.parent;');

        // copy parent prototype
        var methods = descendant.keys();
        child.prototype.parent = {};
//        child.prototype.__base = child.prototype; // uncomment this line if you need base class

        for (var i in this.prototype) {
            if (this.prototype[i] instanceof Function) {
                child.prototype.parent[i] = Class.method(this.prototype[i]);
            } else {
                child.prototype.parent[i] = this.prototype[i];
            }

            if (i != 'parent') {
                if (this.prototype[i] instanceof Function) {
                    child.prototype[i] = Class.method(this.prototype[i], !methods.has(i) ? 'if (parent && parent.parent) {parent = parent.parent};' + Class.label : '');
                } else {
                    child.prototype[i] = this.prototype[i];
                }
            }
        }

        // copy child prototype
        for (var i in descendant) {
            if (descendant[i] instanceof Function) {
                child.prototype[i] = Class.method(descendant[i], 'var __function__ = "' + i + '"; var self = this.self; var parent = this.parent;' + Class.label + 'if (self) {try {parent.self = self} catch (e) {}}');
            } else {
                child.prototype[i] = descendant[i];
            }
        }

        child.extend = this.extend;

        return child;
    }
}

Class.extend = Class.prototype.extend;
Class.label = 'Class.here();';
Class.here = function () {};
Class.method = function (action, add) {
    var source = action.parse();
    var f = function () {};
    if (source) {
        if (source.body.indexOf(Class.label) > 0) {
            source.body = source.body.replace(Class.label, add || '');
        } else {
            source.body = (add || '') + source.body;
        }
        
        var str = '/*' + source.name  + '*/ f = function (' + source.params.join(',') + ') {' + source.body + '};';
        eval(str);
    } else {
        // TODO: some error here
        f = false;
    }
    return f;
}
Class.setSelf = function (object) {
    var base = object;
    do {
        object.self = base;
        object = object.parent;
    } while (object && object.parent);
}

// onDOMContentLoaded simulation
document.loaders = [];

document.callLoaders = function () {
    if (document.loaded) {
        for (var i = 0; i < document.loaders.length; i ++) {
            if (document.loaders[i] instanceof Function) {
                document.loaders[i].call(document);
            }
        }
    }
}

if (FF) {
    document.addEventListener('DOMContentLoaded',
        function () {
            document.loaded = true;
            document.callLoaders.call(document);
        }, false
    );
} else if (WEBKIT) {
    function callLoaders () {
        if (['loaded', 'complete'].has(document.readyState)) {
            document.loaded = true;
            document.callLoaders.call(document);
        } else {
            setTimeout(callLoaders, 0);
        }
    }
    setTimeout(callLoaders, 0);
} else {
    document.onreadystatechange = function () {
        if (['loaded', 'complete'].has(document.readyState)) {
            document.loaded = true;
            document.callLoaders();
        }
    }
}

document.addLoader = function (action) {
    if (document.loaded) {
        action.call(document);
    } else {
        if (!document.loaders.has(action)) {
            document.loaders.push(action);
        }
    }
}

document.addLoader(
    function () {
        Mohawk.loadCss();
        Mohawk.loadJs();
    }
);

//
var Pixel = new Class({
    x: 0,
    y: 0,

    __construct: function (x, y) {
        if (typeof x != 'undefined') {
            if (typeof y != 'undefined') {
                self.x = x || 0;
                self.y = y || 0;
            } else {
                self.x = x.x;
                self.y = x.y;
            }
        }
    },

    inside: function (rect) {
        return self.x >= rect.start.x && self.x <= rect.end.x && self.y >= rect.start.y && self.y <= rect.end.y;
    },
    
    toString: function () {
        return [self.x, self.y].toString();
    }
});

var Rect = new Class({
    start: null,
    end: null,

    __construct: function (start, end) {
        if (start && end) {
            self.start = start;
            self.end = end;
        } else {
            self.start = new Pixel;
            self.end = new Pixel;
        }
    },
    
    toString: function () {
        return [self.start.toString(), self.end.toString()].toString();
    }
});

Rect.fromNode = function (node) {
    var start = node.coordinates();
    return new Rect(start, new Pixel(start.x + node.offsetWidth, start.y + node.offsetHeight));
}
//
var NodeEffectInterface = {
    show: function (effect) {
        if (this.style.visibility == 'hidden') {
            this.style.visibility = 'visible';
            if (effect instanceof Function) {
                effect.call(this, this, this.show);
            }
        }
    },

    hide: function (effect) {
        if (this.style.visibility != 'hidden') {
            if (effect instanceof Function) {
                effect.call(this, this, this.hide);
            } else {
                this.style.visibility = 'hidden';
            }
        }
    },

    display: function (effect, display) {
//        if (this.style.display == 'none') {
            this.style.display = display ? display : (this._display ? this._display : 'block');
            if (effect instanceof Function) {
                effect.call(this, this, this.display);
            }
//        }
    },

    collapse: function (effect) {
        if (this.style.display != 'none') {
            if (effect instanceof Function) {
                effect.call(this, this, this.collapse);
            } else {
                this._display = this.style.display;
                this.style.display = 'none';
            }
        }
    },

    toggleDisplay: function (effect) {
        if (e.offsetWidth == 0) {
            this.display(effect);
        } else {
            this.collapse(effect);
        }
    },

    remove: function (effect) {
        if (this.parentNode) {
            if (effect instanceof Function) {
                effect(this, this.remove);
            } else {
                this.parentNode.removeChild(this);
            }
        }
    },

    setOpacity: function (opacity) {
        if (IE) {
            this.style.filter = 'Alpha(Opacity="' + (opacity * 100) + '")';
        } else {
            this.style.opacity = opacity;
        }
    },

    getOpacity: function (opacity) {
        if (IE) {
            // ???
        } else {
            return this.style.opacity;
        }
    }
};

var Effects = new Singletone ({
    opacity_delay: 10,
    opacity_step: 0.025,
    blink_delay: 100,
    blink_number: 3,

    opacity: function (object, start, end, finalize, current) {
        self = Effects;

        if (typeof current == 'undefined') {
            current = start;
            object._opacity = object.getOpacity();
        }

        current += self.opacity_step * Math.sign(end - start);
        object.setOpacity(current);

        if (Math.sign(end - start) != 0 && (Math.sign(end - start) == Math.sign(end - current) || !Math.sign(end - current))) {
            setTimeout(function() {self.opacity(object, start, end, finalize, current)}, self.opacity_delay);
        } else {
            object.setOpacity(end);
            if (finalize instanceof Function) {
                finalize.call(object);
                object.setOpacity(object._opacity);
            }
        }
    },

    appear: function (object, finalize) {
        Effects.opacity(object, 0, 1, finalize);
    },

    vanish: function (object, finalize) {
        Effects.opacity(object, 1, 0, finalize);
    },


    blink: function (object, finalize, start, counter) {
        self = Effects;

        if (typeof counter == 'undefined') {
            counter = 0;
            start = !['hidden', 'off'].has(object.style.visibility);
        }
        counter ++;

        if (counter < self.blink_number * 2) {
            object.style.visibility = (counter + start) % 2 ? 'hidden' : 'visible';
            setTimeout(function() {self.blink(object, finalize, start, counter)}, self.blink_delay);
        } else {
            object.style.visibility = start ? 'visible' : 'hidden';
            if (finalize instanceof Function) {
                finalize.call(object);
            }
        }
    },

    move: function (e, x, y, step_x, step_y, acc_x, acc_y, finalize, offset) {
        self = Effects;

        var cur_x = e.coordinates().x;
        var cur_y = e.coordinates().y;
        
        if (!offset) {
            var temp = document.createElement('DIV');
            temp.style.left = '0px';
            temp.style.top = '0px';
            temp.style.position = 'absolute';
            e.parentNode.appendChild(temp);
            var offset = temp.coordinates();
            temp.remove();
        }

        if (!x && x !== 0) {
            x = cur_x;
        }
        if (!y && y !== 0) {
            y = cur_y;
        }
        if (!step_x) {
            step_x = 1;
        }
        if (!step_y) {
            step_y = 1;
        }
        if (!acc_x) {
            acc_x = 1;
        }
        if (!acc_y) {
            acc_y = 1;
        }

        var moved_x = false;
        if (Math.abs(cur_x - x) >= step_x) {
            e.style.left = (cur_x + Math.sign(x - cur_x) * step_x) + 'px';
            moved_x = true;
        } else {
            e.style.left = x - offset.x + 'px';
        }

        var moved_y = false;
        if (Math.abs(cur_y - y) >= step_y) {
            e.style.top = (cur_y + Math.sign(y - cur_y) * step_y) + 'px';
            moved_y = true;
        } else {
            e.style.top = y - offset.y + 'px';
        }

        if (moved_x || moved_y) {
            setTimeout(function() {self.move(e, x, y, step_x * acc_x, step_y * acc_y, acc_x, acc_y, finalize, offset)}, 0);
        } else {
            if (finalize instanceof Function) {
                finalize.call(e);
            }
        }
    }

});
//
var Template = new Singletone ({
    vars: {},

    assign: function (name, value) {
        if (is_scalar(value)) {
            self.vars[name] = value;
        } else if (typeof value == 'object') {
            for (var i in value) {
                if (is_scalar(value[i])) {
                    self.vars[name + '.' + i] = value[i];
                }
            }
        } else {
            alert(typeof value + ' is not supported');
        }
    },

    transform: function (text) {
        for (var i in self.vars) {
            text = text.replace(new RegExp('\\{%' + RegExp.quote(i) + '\\}', 'g'), self.vars[i]);
        }
        return text;
    },

    /**
    * From http://forum.dklab.ru/viewtopic.php?p=124969&sid=a1dda9147736ad2ae07b859321817e90&noimg=
    *
    * sprintf(format, argument_list)
    *
    * The string function like one in C/C++, PHP, Perl
    * Each conversion specification is defined as below:
    *
    * %[index][alignment][padding][width][precision]type
    *
    * index     An optional index specifier that changes the order of the 
    *       arguments in the list to be displayed.
    * alignment An optional alignment specifier that says if the result should be 
    *       left-justified or right-justified. The default is 
    *       right-justified; a "-" character here will make it left-justified.
    * padding   An optional padding specifier that says what character will be 
    *       used for padding the results to the right string size. This may 
    *       be a space character or a "0" (zero character). The default is to 
    *       pad with spaces. An alternate padding character can be specified 
    *       by prefixing it with a single quote ('). See the examples below.
    * width     An optional number, a width specifier that says how many 
    *       characters (minimum) this conversion should result in.
    * precision An optional precision specifier that says how many decimal digits 
    *       should be displayed for floating-point numbers. This option has 
    *       no effect for other types than float.
    * type      A type specifier that says what type the argument data should be 
    *       treated as. Possible types:
    *
    * % - a literal percent character. No argument is required.  
    * b - the argument is treated as an integer, and presented as a binary number.
    * c - the argument is treated as an integer, and presented as the character 
    *   with that ASCII value.
    * d - the argument is treated as an integer, and presented as a decimal number.
    * u - the same as "d".
    * f - the argument is treated as a float, and presented as a floating-point.
    * o - the argument is treated as an integer, and presented as an octal number.
    * s - the argument is treated as and presented as a string.
    * x - the argument is treated as an integer and presented as a hexadecimal 
    *    number (with lowercase letters).
    * X - the argument is treated as an integer and presented as a hexadecimal 
    *    number (with uppercase letters).
    */
    sprintf: function () {
        var args = arguments;
        var frmt = arguments[0].replace(/%%/g, "\0\0");
        var result = "", prev = 0, index = 0;
        var re = /%(\d+[\$#])?([+-])?('.|0| )?(\d*)(\.\d*)?([bcdfosuxX])/g;
        /*
        * The re.exec() method returns the array with the following properties
        * wich are used in this function
        *   x.index contains the substring position found at the origin string
        *   x[0] contains the found substring
        *   x[1] contains the index specifier (as \d+\$ or \d+#)
        *   x[2] contains the alignment specifier ("+" or "-" or empty)
        *   x[3] contains the padding specifier (space char, "0" or defined as '.)
        *   x[4] contains the width specifier (as \d*)
        *   x[5] contains the floating-point precision specifier (as \.\d*)
        *   x[6] contains the type specifier (as [bcdfosuxX])
        */
        var x;
        while (x = re.exec(frmt)) {
            for (var i = 0; i < x.length; i++) if (x[i] == undefined) x[i] = "";
            index++;
            var ins = (x[1]) ? args[x[1].substring(0, x[1].length - 1)] : args[index];
            switch (x[6]) {
            case "b":
                ins = Number(ins).bin();
                break;
            case "c":
                ins = String.fromCharCode(ins);
                break;
            case "d":
            case "u":
                ins = Number(ins).dec();
                break;
            case "f":
                ins = Number(ins);
                if (x[5]) {
                    ins = ins.toFixed(x[5].substr(1));
                } else if (x[4]) {
                    ins = ins.toExponential(x[4]);
                } else {
                    ins = ins.toExponential();
                }
                break;
            case "o":
                ins = Number(ins).oct();
                break;
            case "s":
                ins = String(ins);
                break;
            case "x":
                ins = Number(ins).hexl();
                break;
            case "X":
                ins = Number(ins).hex();
                break;
            }
            result += frmt.substring(prev, x.index);
            prev = x.index + x[0].length;
            result += ins.padding(x[2] + x[4], x[3].substr(x[3].length - 1));
        }
        if ( prev < frmt.length ) {
            result += frmt.substr(prev);
        }
        result = result.replace(/\0\0/g, "%");
        return result;
    }

});//
var EventInterface = {
    preventDefault: function () {
    	// Cross browser function to prevent the default action from occuring.
        if (this.button == BTN_RIGHT) {
            if (OPERA) {
                // small trick to make opera happy with context menu
                // adds input button under mouse pointer
                no_ctmenu = window.no_ctmenu;
                if (!no_ctmenu) {
                    no_ctmenu = document.createElement('input');
                    no_ctmenu.type='button';
                    document.body.appendChild(no_ctmenu);
                }
                no_ctmenu.style.position = 'fixed';
                no_ctmenu.style.top = this.clientY - 2 + 'px';
                no_ctmenu.style.left = this.clientX - 2 + 'px';
                no_ctmenu.style.width = '5px';
                no_ctmenu.style.height = '5px';
                no_ctmenu.style.opacity = 0;
                no_ctmenu.style.zIndex = 10000;
            }
        }
		if (typeof this.returnValue != 'undefined') {
		    this.returnValue = false;
		} else {
            if (window._preventDefault instanceof Function) {
		        window._preventDefault.call(this);
            }
		}
    	return false;
    },

    stopPropagation: function () {
    	// Cross browser function to prevent the event from bubbling.
		if (typeof this.cancelBubble != 'undefined') {
		    this.cancelBubble = true;
		} else {
            if (window._stopPropagation instanceof Function) {
		        window._stopPropagation.call(this);
            }
		}
    	return false;
    },

    cursor: function () {
    	var x = 0, y = 0;

    	if (this.pageX || this.pageY)	{
    		x = this.pageX;
    		y = this.pageY;
    	} else if (this.clientX || this.clientY) {
    		x = this.clientX + document.scrollLeft();
    		y = this.clientY + document.scrollTop();
    	} else if (this.screenX || this.screenY) {
    	    x = this.screenX;
    	    y = this.screenY;
    	}

        return new Pixel(x, y);
    },

    element: function () {
        return this.srcElement || this.currentTarget;
    }
};

if (window.Event) {
    window._preventDefault = window.Event.prototype.preventDefault;
    window._stopPropagation = window.Event.prototype.stopPropagation;
    extend(window.Event, EventInterface);
}

var NodeClassInterface = {
    //return all classes as an array
    getClasses: function() {
        return this.className ? this.className.split(new RegExp('\\s+')) : [];
    },

    //checks class
    hasClass: function(className) {
        var classes = this.getClasses();
        for (var i = 0; i < arguments.length; i ++) {
            if (classes.has(arguments[i])) {
                return true;
            }
        }
        return false;
    },

    //add class
    addClass: function(className) {
        for (var i = 0; i < arguments.length; i ++) {
            if (!this.hasClass(arguments[i])) {
                this.className += ' ' + arguments[i];
            }
        }
    },

    //remove class
    removeClass: function(className) {
        var classes = this.getClasses();
        for (var i = 0; i < arguments.length; i ++) {
            classes.pull(arguments[i]);
        }
        this.className = classes.join(' ');
    },

    //replace class
    replaceClass: function(find, replace) {
        var classes = this.getClasses();
        if ((key = classes.key(find)) !== false) {
            classes[key] = replace;
            this.className = classes.join(' ');
        } else {
            this.addClass(replace);
        }
    },

    //set class if not isset, otherwise remove it
    flipClass: function(className) {
        if (this.exists(className)) {
            this.remove(className);
        } else {
            this.add(className);
        }
    },

    copyClassesTo: function (element) {
        var classes = this.getClasses();
        classes.forEach (function () {
            element.addClass(this);
        });
    },
    
    setClassesTo: function (element) {
        element.className = this.className;
    }
};


var NodeEventInterface =  {
    _events: null,

    addEvent: function (event, action, useCapture) {
        var source = action.parse();
        var $event = source.params[0] || 'event';
        source.body = 'if (IE) {extend(' + $event + ', EventInterface)};' + source.body;
        action = new Function(source.params, source.body);

        var hash = action.toString();
        if (!this._events) {
            this._events = new Object;
        }

        if (!this._events[event]) {
            this._events[event] = [];
        }

        if (!this._events[event][hash]) {

            this._events[event][hash] = action;

        	if (!useCapture) {
        	    useCapture = false;
        	}
        	if (this.addEventListener) {
        		this.addEventListener(event, action, useCapture);
        	} else if (this.attachEvent) {
        	    this.attachEvent('on' + event, action);
        	}
        }

    	return action;
    },

    removeEvent: function (event, action, useCapture) {
        var source = action.parse();
        var $event = source.params[0] || 'event';
        source.body = 'if (IE) {extend(' + $event + ', EventInterface)};' + source.body;
        action = new Function(source.params, source.body);

        var hash = action.toString();
        if (this._events[event] && this._events[event][hash]) {
            action = this._events[event][hash];
        }

        if (!useCapture) {
    	    useCapture = false;
    	}
    	if (this.removeEventListener) {
    	    this.removeEventListener(event, action, useCapture);
    	} else if (this.detachEvent) {
    	    this.detachEvent('on' + event, action);
    	}
        
        if (this._events[event] && this._events[event][hash]) {
    	   delete(this._events[event][hash]);
       }
    }
};

var NodeStructureInterface = {
    coordinates: function () {
        var x = 0, y = 0;
        var element = this;
        while (element) {
            x += element.offsetLeft;
            y += element.offsetTop;
            element = element.offsetParent;
        }
        return new Pixel(x, y);
    },

    isDescendantOf: function (node) {
        var parent = this.parentNode;
        while (parent) {
            if (parent == node) {
                return true;
            }
            parent = parent.parentNode;
        }
        return false;
    },

    isAncestorOf: function (node) {
        var parent = node.parentNode;
        while (parent) {
            if (parent == this) {
                return true;
            }
            parent = parent.parentNode;
        }
        return false;
    },

    removeChildren: function () {
        while (this.firstChild) {
            this.removeChild(this.firstChild);
        }
    },

    firstTag: function (tag) {
        if (typeof tag == 'undefined') {
            tag = false;
        }
        var current = this.firstChild;
        while (current && (!current.tagName || (tag && current.tagName.toLowerCase() != tag.toLowerCase()))) {
            current = current.nextSibling;
        }
        return current;
    },

    nextTag: function (tag) {
        if (typeof tag == 'undefined') {
            tag = false;
        }
        var current = this.nextSibling;
        while (current && (!current.tagName || (tag && current.tagName.toLowerCase() != tag.toLowerCase()))) {
            current = current.nextSibling;
        }
        return current;
    },

    ancestorTag: function (tag) {
        if (typeof tag == 'undefined') {
            tag = false;
        }
        var current = this.parentNode;
        while (current && (!current.tagName || (tag && current.tagName.toLowerCase() != tag.toLowerCase()))) {
            current = current.parentNode;
        }
        return current;
    },

    previousTag: function (tag) {
        if (typeof tag == 'undefined') {
            tag = false;
        }
        var current = this.previousSibling;
        while (current && (!current.tagName || (tag && current.tagName.toLowerCase() != tag.toLowerCase()))) {
            current = current.previousSibling;
        }
        return current;
    },

    replace: function (node) {
        this.parentNode.replaceChild(node, this);
    },

    getElementsByClassName: function (class_name) {
        var elements = this.getElementsByTagName('*');
        var found = [];
        for (var i = 0; i < elements.length; i ++) {
            if (elements[i].hasClass(class_name)) {
                found.push(elements[i]);
            }
        }
        return found;
    }

};

var DOM = new Singletone ({
    ELEMENT_NODE:        1,
    TEXT_NODE:           3,
    CDATA_SECTION_NODE : 4,
    DOCUMENT_NODE :      9,

    enchaseNode: function (node) {
        node._cloneNode = node.cloneNode;
        node.cloneNode = function (flag) {
            var clone = this._cloneNode(flag);
            self.enchaseNode(clone);
            return clone;
        }
        
        switch (node.nodeName.toUpperCase()) {
        case 'TABLE':
            node._insertRow = node.insertRow;
            node.insertRow = function (index) {
                var row = (HTMLTableElement.prototype.insertRow || this._insertRow).call(this, index);
                self.enchaseNode(row);
                return row;
            }
            
            node._createTHead = node.createTHead;
            node.createTHead = function () {
                var thead = (HTMLTableElement.prototype.insertRow || this._createTHead).call(this);
                self.enchaseNode(thead);
                return thead;
            }
            
            node._createTFoot = node.createTFoot;
            node.createTFoot = function () {
                var tfoot = (HTMLTableElement.prototype.insertRow || this._createTFoot).call(this);
                self.enchaseNode(tfoot);
                return tfoot;
            }
            
            break;
            
        case 'TR':
            node._insertCell = node.insertCell;
            node.insertCell = function (index) {
                var cell = (HTMLTableRowElement || this._insertCell).call(this, index);
                self.enchaseNode(cell);
                return cell;
            }
            break;
        }
        
        if ([self.ELEMENT_NODE, self.DOCUMENT_NODE].has(node.nodeType)) {
            extend(node, NodeClassInterface);
            extend(node, NodeEventInterface);
            extend(node, NodeStructureInterface);
    
            if (NodeEffectInterface instanceof Object) {
                extend(node, NodeEffectInterface);
            }
            if (node.tagName && node.tagName.toUpperCase() == 'FORM') {
                if (typeof FormsInterface != 'undefined') {
                    extend(node, FormsInterface);
                }
            }
        }
    },

    enchaseDocument: function () {
        document._createElement = document.createElement;
        document.createElement = function (tag_name) {
            if (typeof Document == 'undefined') {
                var element = document._createElement(tag_name);
            } else {
                var element = (Document.prototype.createElement || document._createElement).call(document, tag_name);
            }
            self.enchaseNode(element);
            return element;
        }
        
        document._getElementById = document.getElementById;
        document.getElementById = function (id) {
            if (typeof Document == 'undefined') {
                var element = document._getElementById(id);
            } else {
                var element = (Document.prototype.getElementById || document._getElementById).call(document, id);
            }
            if (element) {
                self.enchaseDocumentNodes(element);
            }
            return element;
        }

        document.scrollLeft = function () {
            return self.pageXOffset
                || (document.documentElement && document.documentElement.scrollLeft)
                || (document.body && document.body.scrollLeft);
        }
        document.scrollTop = function () {
            return self.pageYOffset
                || (document.documentElement && document.documentElement.scrollTop)
                || (document.body && document.body.scrollTop);
        }
        document.size = function () {
            var w = 0, h = 0;
            var win = window.window;
    
            if (win.self.innerHeight) {
                w = win.self.innerWidth;
                h = win.self.innerHeight;
            } else if (win.document.documentElement && win.document.documentElement.clientWidth) {
                w = win.document.documentElement.clientWidth;
                h = win.document.documentElement.clientHeight;
            } else if (win.document.body) {
                w = win.document.body.clientWidth;
                h = win.document.body.clientHeight;
            }
            return {width: w, height: h};
        }
        self.enchaseNode(document);

        if (typeof document.defaultView == 'undefined') {
            document.defaultView = {};
        }
        if (typeof document.defaultView.getComputedStyle == 'undefined') {
            document.defaultView.getComputedStyle = function (element, pseudoElement) {
                return element.currentStyle;
            }
        }
    },

    enchaseDocumentNodes: function (node) {
        if (node) {
            self.enchaseNode(node);
        }
        var all = (node || document.body).getElementsByTagName('*');
        for (var i = 0; i < all.length; i ++) {
            self.enchaseNode(all[i]);
        }
    }
});
DOM.enchaseDocument();

document.addLoader(
    function () {
        DOM.enchaseDocumentNodes();
    }
);

function ID(id) {
    return document.getElementById(id);
}//
var Dragdrop = new Singletone ({
    css_class: 'draggable',
    max_index: 1000,
    dragged: false,
    target: {
        elements: [],
        x_boxes: {},
        x_delim: [],
        y_boxes: {},
        y_delim: []
    },
    over: [],
    events: [],

    reset: function () {
        self.target = {
            elements: [],
            x_boxes: {},
            x_delim: [],
            y_boxes: {},
            y_delim: []
        };
        self.over = [];
        self.events = [];
        self.dragged = false;    
    },

    toString: function () {return 'Object: Dragdrop'},

    dragging: function () {
        return !!self.events.length;
    },

    createTargetObjectElement: function (target, element, i, last) {
        self = Dragdrop;

        var n = target.elements.length;
        var box = Rect.fromNode(element[i]);
    
        // x axis
        var start_x = target.x_delim.binSearch(box.start.x);
        if (typeof target.x_boxes[box.start.x] == 'undefined') {
            target.x_boxes[box.start.x] = typeof target.x_boxes[target.x_delim[start_x - 1]] != 'undefined' ? target.x_boxes[target.x_delim[start_x - 1]].concat([]) : [];
            target.x_delim.splice(start_x, 0, box.start.x);
        }
        
        var end_x = target.x_delim.binSearch(box.end.x);
        if (typeof target.x_boxes[box.end.x] == 'undefined') {
            target.x_boxes[box.end.x] = typeof target.x_boxes[target.x_delim[end_x - 1]] != 'undefined' ? target.x_boxes[target.x_delim[end_x - 1]].concat([]) : [];
            target.x_delim.splice(end_x, 0, box.end.x);
        }
        
        for (var j = start_x; j < end_x; j ++) {
            if (typeof target.x_boxes[target.x_delim[j]] == 'undefined') {
                target.x_boxes[target.x_delim[j]] = [];
            }
            target.x_boxes[target.x_delim[j]].push(n);
        }
        
        // y axis
        var start_y = target.y_delim.binSearch(box.start.y);
        if (typeof target.y_boxes[box.start.y] == 'undefined') {
            target.y_boxes[box.start.y] = typeof target.y_boxes[target.y_delim[start_y - 1]] != 'undefined' ? target.y_boxes[target.y_delim[start_y - 1]].concat([]) : [];
            target.y_delim.splice(start_y, 0, box.start.y);
        }
        
        var end_y = target.y_delim.binSearch(box.end.y);
        if (typeof target.y_boxes[box.end.y] == 'undefined') {
            target.y_boxes[box.end.y] = typeof target.y_boxes[target.y_delim[end_y - 1]] != 'undefined' ? target.y_boxes[target.y_delim[end_y - 1]].concat([]) : [];
            target.y_delim.splice(end_y, 0, box.end.y);
        }
        
        for (var j = start_y; j < end_y; j ++) {
            if (typeof target.y_boxes[target.y_delim[j]] == 'undefined') {
                target.y_boxes[target.y_delim[j]] = [];
            }
            target.y_boxes[target.y_delim[j]].push(n);
        }

        var actions = ['ondragover', 'ondragout', 'ondrop'];
        for (var k = 0; k < actions.length; k ++) {
            var action = element[i][actions[k]] || element[i].getAttribute(actions[k]);
            element[i][actions[k]] = action instanceof Function ? new Function (['event'], action.parse().body) : false;
        }

        target.elements[n] = element[i];
        
        if (++i < element.length) {
            setTimeout(function () {self.createTargetObjectElement(target, element, i, last)}, 0);
        } else if (last) {
            //debug('Create target object: ' + T.stop() + ' ms.');
        }
    },

    createTargetObject: function (element) {
        var target = {
            elements: [],
            x_boxes: {},
            x_delim: [],
            y_boxes: {},
            y_delim: []
        };

        // debugging:
        //T = new Mohawk.Utils.Timer;

        for (var n = 0; n < arguments.length; n ++) {
            self.createTargetObjectElement(target, arguments[n], 0, n == arguments.length - 1);
        }
        
        return target;
    },

    setTarget: function (element) {
        self.target = self.createTargetObject.call(self, arguments);
    },
    
    setTargetObject: function (object) {
        self.target = object;
    },
    
    getDraggable: function (node) {
        if (node.hasClass(self.css_class)) {
            return node;
        } else {
            if (node.parentNode) {
                return self.getDraggable(node.parentNode);
            } else {
                return null;
            }
        }
    },


    pick: function (event, elements) {
        self = Dragdrop;

        event = event || window.event;
        extend(event, EventInterface);

        var caller = event.currentTarget || event.srcElement;
        if (!elements || !elements.length) {
            elements = [caller];
        }

        document.addEvent('mousemove', self.drag);
        document.addEvent('mouseup', self.release);

        self.events = [];

        var cur = event.cursor();

        //var T = new Mohawk.Utils.Timer;
        //alert(elements[0].tagName);
        elements.forEach(function (i) {
            var action = this.onpick || this.getAttribute('onpick');
            if (action instanceof Function) {
                var handler = new Function (['event'], action.parse().body);
                handler.call(this, event);
            }
            
            var draggable = self.getDraggable(this);
            if (draggable) {
                var evt = {
                    element: this,
                    draggable: draggable,
                    position: draggable.style.position || 'static', // TODO: empty style bug
                    offset: new Pixel(cur.x - draggable.offsetLeft, cur.y - draggable.offsetTop),
                    start: new Pixel(draggable.offsetLeft, draggable.offsetTop),
                    ondrag: handler instanceof Function ? handler : false
                };
                
                var actions = ['ondrag', 'ondrop', 'onrelease'];
                for (var k = 0; k < actions.length; k ++) {
                    var action = this[actions[k]] || this.getAttribute(actions[k]);
                    evt[actions[k]] = action instanceof Function ? new Function (['event'], action.parse().body) : false;
                }
                
                self.events.push(evt);
                
                draggable.style.position = 'absolute';
                self.bringToFront(draggable);
            }
        });
        //debug('Set elements in: ' + T.stop() + ' ms');
    },

    drag: function (event) {
        self = Dragdrop;

        event = event || window.event;
        extend(event, EventInterface);

        if (!self.dragged) {
            document.removeEvent('mouseup', self.release);
            document.addEvent('mouseup', self.drop);
        }
        self.dragged = true;

        var cur = event.cursor();

        for (var i = 0; i < self.events.length; i ++) {
            var evt = self.events[i];
            if (evt.ondrag) {
                evt.ondrag.call(evt.element, event);
            }

            var new_x = cur.x - evt.offset.x;
            var new_y = cur.y - evt.offset.y;

            evt.draggable.style.left = new_x + 'px';
            evt.draggable.style.top  = new_y + 'px';
        }

        if (self.target.elements.length) {
            var x_boxes = self.target.x_boxes[self.target.x_delim[self.target.x_delim.binSearch(cur.x) - 1]];
            var y_boxes = self.target.y_boxes[self.target.y_delim[self.target.y_delim.binSearch(cur.y) - 1]];
            if (x_boxes && y_boxes) {
                var boxes = x_boxes.intersect(y_boxes, true);
                for (var z = 0; z < boxes.length; z ++) {
                    if (cur.inside(Rect.fromNode(self.target.elements[boxes[z]]))) {
                        if (!self.over.has(self.target.elements[boxes[z]])) {
                            self.over.push(self.target.elements[boxes[z]]);
                        }
                        if (self.target.elements[boxes[z]].ondragover) {
                            self.target.elements[boxes[z]].ondragover.call(self.target.elements[boxes[z]], event);
                        }
                    }
                }
            }
        }
        
        if (self.over.length) {
            for (var i = 0; i < self.over.length; i ++) {
                var over = self.over[i];
                var bound = Rect.fromNode(over);
                if (!cur.inside(bound)) {
                    if (over.ondragout) {
                        over.ondragout.call(over, event);
                    }
                    self.over.pull(over);
                }
            }
        }
    },

    drop: function (event) {
        self = Dragdrop;
        event = event || window.event;
        extend(event, EventInterface);

        document.removeEvent('mousemove', self.drag);
        document.removeEvent('mouseup', self.drop);

        var cur = event.cursor();
        if (self.over.length) {
            for (var i = 0; i < self.over.length; i ++) {
                var over = self.over[i];
                var bound = Rect.fromNode(over);
                if (cur.inside(bound)) {
                    if (over.ondrop) {
                        over.ondrop.call(over, event);
                    }
                }
            }
        }

        for (var i = 0; i < self.events.length; i ++) {
            var evt = self.events[i];
            if (evt.ondrop) {
                evt.element.ondrop.call(evt.element, event);
            }

            // @todo: empty style bug
//            if (evt.position == 'static') {
//                Effects.move(evt.draggable, evt.start.x, evt.start.y, 10, 10, function () {
//                    evt.draggable.style.position = evt.position;
//                });
//            } else {
//                evt.draggable.style.position = evt.position;
//            }
        }
        self.reset();
    },

    release: function (event) {
        self = Dragdrop;

        event = event || window.event;

        document.removeEvent('mousemove', self.drag);
        document.removeEvent('mouseup', self.release);

        for (var i = 0; i < self.events.length; i ++) {
            var evt = self.events[i];
            if (evt.element.onrelease) {
                evt.element.onrelease.call(evt.element, event);
            }
        }
        self.reset();
    },

    bringToFront: function (element) {
        element.style.zIndex = self.max_index;
        self.max_index ++;
    },

    getDragObjects: function () {
        var objects = [];
        for (var i = 0; i < self.events.length; i ++) {
            objects.push(self.events[i].draggable);
        }
        return objects;
    }

});
var Images = {};
var Flash = {};

function viewImage(source) {
    return view('image', source);
}

function viewFlash(source, width, height) {
    return view('flash', source, width, height);
}

function view(type, source, width, height) {
    if (!ID('image-viewer')) {
        var div = document.createElement('DIV');
        document.body.appendChild(div);
        div.id = 'image-viewer';
        div.addClass('draggable');
        div.onmousedown = function (event) {
            Dragdrop.pick(event, [div]);
        }
        /*div.onrelease = function (event) {
            div.remove(Effects.vanish);
        }*/
    } else {
        var div = ID('image-viewer');
    }
    div.innerHTML = '<img src="/public/img/admin/dark/progress.gif" alt="loading&hellip;" />';
    div.style.top = (document.size().height - div.offsetHeight) / 2 + document.scrollTop() + 'px';
    div.style.left = (document.size().width - div.offsetWidth) / 2 + document.scrollLeft() + 'px';

    if (type == 'image') {
        if (Images[source]) {
            var image = Images[source];
            show(div, image);
        } else {
            var image = document.createElement('IMG');
            image.src = source;
            image.onmouseup = function () {
                return false; 
            }
            image.onmousedown = function () {
                return false;
            }
            image.onmousemove = function () {
                return false; 
            }
            image.oncontextmenu = function () {
                return false;
            }
            image.onload = function () {
                show(div, image);
            }
            Images[source] = image;
        }
    } else if (type == 'flash') {
        if (Flash[source]) {
            var object = Flash[source];
        } else {
            var object = document.createElement('OBJECT');
            object.data = source;
            object.type = 'application/x-shockwave-flash';
            object.width = width;
            object.height = height;
            var param = document.createElement('PARAM');
            param.name = 'movie';
            param.value = source;
            object.appendChild(param);
            Flash[source] = object;
        }
        show(div, object);
    }
       
    return false;
}

function show(div, object) {
    div.removeChildren();
    var close = document.createElement('A');
    close.href = '#close';
    close.innerHTML = 'close';
    close.onclick = function () {
        div.remove(Effects.vanish);
        return false;
    }
    div.appendChild(close);
    div.appendChild(object);
    div.style.width = object.offsetWidth + 'px';
    div.style.height = object.offsetHeight + 'px';
    var top = (document.size().height - div.offsetHeight) / 2 + document.scrollTop();
    var left = (document.size().width - div.offsetWidth) / 2 + document.scrollLeft();
    div.style.top = (top > 0 ? top : 0) + 'px';
    div.style.left = (left > 0 ? left: 0) + 'px';
    object.setOpacity(1);    
    object.display(Effects.appear);
}