var Mohawk = {
    version: '0.1',
    UI: {
        selection: false,
        selectables: []
    },
    
    Utils: {
    },
    
    DOM: {
    },
    
    url: URL.js,
    
    XHR: function () {
    	var XHR = null
        if (window.XMLHttpRequest) {
            XHR = new XMLHttpRequest();
        } else if (window.ActiveXObject) {
            XHR = new ActiveXObject('Microsoft.XMLHTTP');
        } else {
            alert('XMLHttpRequest is disabled in your browser');
            return false;
        }
    	
    	return XHR;
    }
};
    
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 === ''
        || variable === false
        || variable === null
        || typeof variable == 'undefined'
        || variable === []
        || variable === {}
        || !variable;
};

function extend(target, source, directly) {
    if (target.prototype && !directly) {
        extend(target.prototype, source);
    } else {
    	if (typeof(target.base) == 'undefined') {
    		target.base = {};
    	}
        for (var i in source) {
        	if (typeof(target[i]) != 'undefined') {
        		if (typeof(target.base[i]) == 'undefined') {
        			target.base[i] = target[i];
        		}
        	}
            target[i] = source[i];
        }
    }
};

Function.prototype.parse = function () {
    //var source = this.toSource instanceof Function ? this.toSource() : this.toString().replace(new RegExp('\n', 'g'), ' ');
    var source = this.toSource instanceof Function ? this.toSource() : this.toString();
    // 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') {
                var value = action.call(this[i], i, this[i]);
                if (value === false) {
                    break;
                }
            }
        }
    }
};

var ArrayInterface = {
    keys: function () {
        var keys = [];
        for (var i in this) {
            if (typeof Object.prototype[i] == 'undefined' && typeof Array.prototype[i] == 'undefined' ) {
                keys.push(i);
            }
        }
        return keys;
    },
    
    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 ++) {
            var value = action.call(this[i], i, this[i]);
            if (value === false) {
                break;
            }
        }
    },
    
    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;
    },
    
    divide: function (num) {
        var length = Math.ceil(this.length / num);
        var chunks = [];
        for (var i = 0; i < num; i ++) {
            chunks.push(this.slice(i * length, (i + 1) * length));
        }
        return chunks;
    }
};

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;
    },
    
    round: function (num, precision) {
    	if (!precision || precision < 0) {
    		return Math.base.round(num);
    	} else {
    		var base = Math.pow(10, precision);
    		return Math.base.round(num * base) / base;
    	}
    }
};
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 = {

    trim: function () {
        return this.rtrim().ltrim();
    },
    
    ltrim: function() {
        return this.replace(new RegExp('^\\s+'), '');
    },
    
    rtrim: function() {
        return this.replace(new RegExp('\\s+$'), '');
    },
    
    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();
    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; this.static = arguments.callee; var static = this.static; ');

        // 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; var static = this.static; ' + 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 = 'f = function (' + source.params.join(',') + ') {' + source.body + '};';
        //str = str.replace(new RegExp('\r\n', 'g'), '\n');
        //str = str.replace(new RegExp('\n', 'g'), '\r\n');
        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) {
    	while (document.loaders.length) {
    		var loader = document.loaders.shift();
            if (loader instanceof Function) {
                loader.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);
        }
    }
};

Mohawk.included = [];
function include(path) {
	var first = path.substr(0, path.indexOf('.'));
	var url = Mohawk.url;
	if (typeof(URL[first]) != 'undefined') {
		url = URL[first];
		path = path.substr(path.indexOf('.') + 1);
	}
    var include_path = url + path.replace(new RegExp('\\.', 'g'), '/') + '.js';
    if (!Mohawk.included.has(path)) {
        // create XHR
    	var XHR = Mohawk.XHR();
        XHR.open('GET', include_path, false);
        XHR.send(null);
        eval(XHR.responseText);
        Mohawk.included.push(path);
    }
}

function includeTemplate(path) {
    var include_path = URL.html + path.replace(new RegExp('\\.', 'g'), '/') + '.tmpl';
    if (!Mohawk.included.has(path)) {
        // create XHR
    	var XHR = Mohawk.XHR();
        XHR.open('GET', include_path, false);
        XHR.send(null);
        
        var str = XHR.responseText;
        str = str.replace(new RegExp('"', 'g'), '\\"');
        // str = str.replace(new RegExp('/', 'g'), '\/');
        str = str.replace(new RegExp('([\n\r]+)', 'g'), '" + "\\n');
        str = '"' + str + '"';
        
        var name = path.substr(path.lastIndexOf('.') + 1).toUpperCase();
        name = name.replace(new RegExp('\\W', 'g'), '_');
        eval('window.' + name + ' = ' + str);

        Mohawk.included.push(path);
    }
}

include('mohawk.kernel.geom');
include('mohawk.kernel.Template');
include('mohawk.kernel.Effects');
include('mohawk.kernel.DOM');
include('mohawk.kernel.Loader');

var DOM = Mohawk.DOM;
var Loader = Mohawk.Loader;
var Template = Mohawk.Template;
Template.assign('URL', URL);
var Effects = Mohawk.Effects;
