var Mohawk = {
    version: '0.1',
    UI: {
        selection: false,
        selectables: []
    },
    
    Utils: {
    },
    
    css: [],
    css_url: '{@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: '{@home}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 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 = [];
if (FF) {
    document.addEventListener('DOMContentLoaded',
        function () {
            document.loaded = true;
        }, false
    );
} else {
    document.onreadystatechange = function () {
        if (['loaded', 'complete'].has(document.readyState)) {
            for (var i = 0; i < document.loaders.length; i ++) {
                document.loaders[i].call(document);
            }
        }
    }
}

document.addLoader = function (action) {

    document.loaders.push(action);
    
    if (FF) {
        document.addEventListener('DOMContentLoaded', action, false);
        if (document.loaded) {
            action.call(document);
        }
    } else {
        if (['loaded', 'complete'].has(this.readyState)) {
            action.call(document);
        }
    }
}

document.addLoader(
    function () {
        Mohawk.loadCss();
        Mohawk.loadJs();
    }
);

//{import path="mohawk.kernel.geom" type="js" /}
//{import path="mohawk.kernel.Effects" type="js" /}
//{import path="mohawk.kernel.Template" type="js" /}
//{import path="mohawk.kernel.DOM" type="js" /}