/*!
 * 
 * Copyright (c) 2009 - 2013, javalikescript@free.fr (SPYL). All rights reserved.
 * JLS is licensed under LGPL, see the license.txt file that accompanied this code.
 * 
 */
/**
 * jls Base framework.
 * All the framework objects are created under the jls namespace.
 * The default objects are extended using prototype like behaviors.
 * 
 * @name jls
 * @namespace
 * @see jls.lang
 */

/**
 * @name jls.lang
 * @namespace Provides base classes for the jls language.
 * @see jls.lang.Exception
 * @see jls.lang.Logger
 */

// Add default option variable
if (typeof jlsOptions == 'undefined') {
    jlsOptions = {};
}

// Native namespace
var _native = {};

/*
 * Library boot
 */
_native.boot = {};
_native.boot.emptyFunction = function() {};
_native.boot.unsupportedFunction = function() {
    throw 'Unsupported native operation';
};
_native.boot.unsupportedClass = function() {
    throw 'Unsupported native class';
};
_native.boot.log = _native.boot.emptyFunction;
if ((typeof console != 'undefined') && ('log' in console)) {
    //_native.boot.log = console.log;
	_native.boot.log = function(msg) { // Chrome has a console but needs wrapping...
		console.log(msg);
	};
}
//_native.boot.log('Bootstrapping...');
// Find the default script path
(function() {
    var bootstrap = 'bootstrap.js';
    var path = '.'; // default
    var scriptTags = document.getElementsByTagName('script');
    for (var i = 0; i < scriptTags.length; i++) {
        var src = scriptTags[i].getAttribute('src');
        //_native.boot.log('script[' + i + '].src="' + src + '"');
        if (!src) {
            continue;
        }
        var j = src.lastIndexOf('/' + bootstrap);
        if ((j >= 0) && (j == src.length - bootstrap.length - 1)) {
            path = src.substring(0, src.length - bootstrap.length - 1);
            break;
        } else if (src == bootstrap) {
            path = '.';
            break;
        }
    }
    _native.boot.defaultScriptPath = path;
})();

// TODO use only one callback that take an object as argument that can get the response/throw an exception...
_native.boot.httpRequest = function(url, options) {
	this._url = url;
	this._options = {
	    method : 'post',
	    asynchronous : true,
	    contentType : 'application/x-www-form-urlencoded',
	    encoding : 'UTF-8',
	    parameters : '',
	    user : null,
	    password : null,
	    onComplete : _native.boot.emptyFunction,
        onException : _native.boot.emptyFunction,
        headers : null,
        mimeType : 'auto',
        context : null
	};
	if (options) {
		for (var key in options) {
			this._options[key] = options[key];
		}
	}
	this._xhr = _native.boot.httpRequest.newXHR();
};
_native.boot.httpRequest.newXHR = function() {
	try {
	    return new XMLHttpRequest()
	}
	catch (e) {}
	try {
	    return new ActiveXObject('Msxml2.XMLHTTP')
	}
	catch (e) {}
	try {
	    return new ActiveXObject('Microsoft.XMLHTTP')
	}
	catch (e) {}
	return false;
};
_native.boot.httpRequest.objectToQuery = function(obj) {
    var params = [];
    for (var key in obj) {
        var value = obj[key];
        var key = encodeURIComponent(key);
        if (typeof value == 'undefined') {
            params.push(key);
        } else if (value == null) {
            params.push(key + '=null');
        } else {
            params.push(key + '=' + encodeURIComponent(value));
        }
    }
    return params.join('&');
};
_native.boot.httpRequest.prototype.getStatus = function() {
	return this._xhr.status || 0;
};
_native.boot.httpRequest.prototype.succeed = function() {
    var status = this.getStatus();
    //if (this._xhr.responseText) return true;
    return ((status >= 200) && (status < 300)) || (status == 304);
};
_native.boot.httpRequest.prototype.getResponseText = function() {
    if (! this.succeed()) {
        throw 'Cannot get responseText, the HTTP request has failed (' + this.getStatus() + ')';
    }
    return this._xhr.responseText;
};
_native.boot.httpRequest.prototype.getResponseXML = function() {
    if (! this.succeed()) {
        throw 'Cannot get responseXML, the HTTP request has failed (' + this.getStatus() + ')';
    }
    var responseXML = this._xhr.responseXML;
    if (responseXML) {
        return responseXML;
    }
    throw 'No XML response available';
};
_native.boot.httpRequest.prototype.onStateChange = function() {
    var readyState = this._xhr.readyState;
    //_native.boot.log('_native.boot.xmlHttpRequest.onStateChange(' + readyState + ')');
    if (readyState != 4) { // Not completed
    	return;
    }
    try {
        this._options.onComplete.call(this, this._options.context);
    }
    catch (e) {
    	this._options.onException.call(this, e, this._options.context);
    }
    finally {
        this._options.onComplete = _native.boot.emptyFunction;
        this._xhr.onreadystatechange = _native.boot.emptyFunction;
    }
};
_native.boot.httpRequest.prototype.send = function() {
    //_native.boot.log('_native.boot.xmlHttpRequest.send(), ' + this._options.method + ': "' + this._url + '" ' + this._options.user + '/' + this._options.password);
    try {
        if ((this._options.user != null) && (this._options.password != null)) {
            this._xhr.open(this._options.method.toUpperCase(), this._url,
                    this._options.asynchronous, this._options.user,
                    this._options.password);
        } else {
            this._xhr.open(this._options.method.toUpperCase(), this._url,
                    this._options.asynchronous);
        }
        var self = this;
        this._xhr.onreadystatechange = function () {
            self.onStateChange();
        };
        var accept = 'text/javascript, text/html, application/xml, text/xml, */*';
        if (this._options.mimeType == 'auto') {
        	var extIndex = this._url.lastIndexOf('.');
        	var ext = extIndex > 0 ? this._url.substring(extIndex) : '';
        	switch (ext) {
        	case '.js':
        		accept = 'text/javascript, text/plain';
        		this._xhr.overrideMimeType('text/plain');
        		break;
        	}
        }
        var headers = {
                'Accept': accept
        };
        if (this._options.method == 'post') {
            headers['Content-type'] = this._options.contentType + (this._options.encoding ? '; charset=' + this._options.encoding : '');
        }
        for (var name in headers) {
            this._xhr.setRequestHeader(name, headers[name]);
        }
        var headers = this._options.headers;
        if (headers != null) {
            for (var name in headers) {
                this._xhr.setRequestHeader(name, headers[name]);
            }
        }
        var params = (typeof this._options.parameters == 'string') ? this._options.parameters : _native.boot.httpRequest.objectToQuery(this._options.parameters);
        var body = (this._options.method == 'post') ? (this._options.postBody || params) : null;
        this._xhr.send(body);
        /* Force Firefox to handle ready state 4 for synchronous requests */
        if (!this._options.asynchronous && this._xhr.overrideMimeType) {
            this.onStateChange();
        }
    }
    catch (e) {
        //_native.boot.log('_native.boot.xmlHttpRequest.send() "' + this._url + '" raised: ' + e);
        this._options.onException.call(this, e, this._options.context);
    }
	return this;
};


/*
 * Library core From "jls generateNativeLibraryAPI.js"
 */
_native.core = {};

_native.core.log = function(level, msg) {
    _native.boot.log(msg);
};
_native.core.logLevel = _native.boot.emptyFunction;
_native.core.exceptionHandler = _native.boot.emptyFunction; // alert
_native.core.sleep = _native.boot.emptyFunction;
_native.core._scriptPath = [];
_native.core._getResource = function(path, callback, sync, xml) {
    xml = xml || false;
    var scriptPaths = [].concat(_native.core._scriptPath);
    //_native.core.log(0, 'boot> _native.core._getResource("' + path + '", ' + xml + ') [' + scriptPaths + ']');
    var next;
    next = function() {
        if (scriptPaths.length == 0) {
            callback(null, 'Resource not found "' + path + '"');
            return;
        }
        var url = scriptPaths.shift() + '/' + path;
        //_native.core.log(0, 'boot> trying url "' + url + '"...');
        new _native.boot.httpRequest(url, {
                method :'get',
                asynchronous : ! sync,
                onException : function(e) {
                    callback(null, e);
                },
                onComplete : function() {
                    //_native.core.log(0, 'boot> onComplete() ' + this.getStatus() + ', ' + this.succeed());
                    if (this.succeed()) {
                        //_native.core.log(0, 'boot> Resource found "' + path + '", calling callback...');
                        callback(xml ? this.getResponseXML() : this.getResponseText());
                    } else {
                        if (this.getStatus() != 404) {
                            throw 'Unsupported status ' + this.getStatus() + ' for path "' + path + '"';
                        }
                        next();
                    }
                }
        }).send();
    };
    next();
};
_native.core.getResourceAsString = function(path, xml) {
    var res, err;
    _native.core._getResource(path, function(content, e) {
      res = content;
      err = e;
    }, true, xml);
    if ((res == null) || (err)) {
      throw err;
    }
    return res;
};
_native.core.getResourceAsByteArray = _native.boot.emptyFunction;
_native.core.evalScript = function(path, prefix, suffix) {
    //_native.core.log(0, '_native.core.evalScript("' + path + '")');
    var content = _native.core.getResourceAsString(path);
    if (prefix || suffix) {
        content = (prefix ? prefix : '') + content + (suffix ? suffix : '');
    }
    /* with (object) {
      eval(string);
    }*/
    //_native.core.log(0, 'eval(-->' + content + '<--)');
    var result = eval(content);
    //_native.core.log(0, 'eval() => ' + result);
    return result;
};

_native.core._evalScript = function(path, callback, sync, prefix, suffix) {
    try {
        // TODO Support async eval...
        var res = _native.core.evalScript(path, prefix, suffix);
        callback(res, null);
    } catch (e) {
        callback(null, e);
    }
};
_native.core.requireLibrary = _native.boot.unsupportedFunction;
_native.core.loadLibrary = _native.boot.unsupportedFunction;
_native.core.visitPaths = _native.boot.emptyFunction;
_native.core.gc = _native.boot.unsupportedFunction;
_native.core.dumpHead = _native.boot.unsupportedFunction;
_native.core.exit = _native.boot.unsupportedFunction;
_native.core.halt = _native.boot.unsupportedFunction;
_native.core.system = _native.boot.unsupportedFunction;
_native.core.signal = _native.boot.unsupportedFunction;
_native.core.raise = _native.boot.unsupportedFunction;
_native.core.getSignalCounters = _native.boot.unsupportedFunction;
_native.core.registerSignalMonitor = _native.boot.unsupportedFunction;
_native.core.handleSignal = _native.boot.unsupportedFunction;
_native.core.getEnv = _native.boot.unsupportedFunction;
_native.core.boot = _native.boot.unsupportedFunction;
_native.core.addPath = function(path) {
    // Check for duplicate
    var i = _native.core._scriptPath.length;
    while (--i >= 0) {
        if (_native.core._scriptPath[i] == path) {
            break;
        }
    }
    if (i < 0) {
        _native.core._scriptPath.push(path);
    }
};
_native.core.arguments = null;

_native.core.properties = { // From "jls systemProperties.js json"
  'browser' : navigator.appName,
  'browser.vendor' : navigator.userAgent, // TODO Use Crome, Gecko, IE, Opera, WebKit
  'browser.version' : navigator.appVersion,
  'browser.cookieEnabled' : navigator.cookieEnabled,
  'cpu.endian' : 'little',
  'cpu.pointer.size' : '4',
  'file.encoding' : 'UTF-8',
  'file.separator' : '/',
  'gui.toolkit' : 'html',
  'javascript.engine' : '?',
  'javascript.version' : '180',
  'jls.bootstrap.filename' : 'bootstrap.js',
  'jls.extension.path' : '',
  'jls.home' : '.',
  'jls.library.path' : '.',
  'jls.logger.logLevel' : 'warn',
  'jls.programname' : 'jls',
  'jls.script.path' : _native.boot.defaultScriptPath,
  'jls.vendor' : 'spyl',
  'jls.vendor.url' : 'http://javalikescript.free.fr/',
  'jls.version' : '0.6',
  'line.separator' : '\n',
  'os.architecture' : 'n/a',
  'os.hostname' : 'n/a',
  'os.release' : 'n/a',
  'os.sysname' : navigator.platform,
  'path.separator' : ';',
  'user.dir' : '.',
  'user.home' : '.',
  'user.language' : 'en',
  'user.name' : 'n/a',
  'user.region' : 'FR',
  'user.timezone' : 'n/a'
};
// Add browser detection facility
/*_native.core.properties['browser.id'] = window.opera ? 'Opera' :
	(window.attachEvent ? 'IE' :
		(navigator.userAgent.indexOf('AppleWebKit/') > -1 ? 'WebKit' :
			(navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1 ? 'Gecko' : '?')));*/

_native.core.SIGINT = 2;
_native.core.SIGILL = 4;
_native.core.SIGFPE = 8;
_native.core.SIGSEGV = 11;
_native.core.SIGTERM = 15;
_native.core.SIGBREAK = 21;
_native.core.SIGABRT = 22;
_native.core.SIG_ERR = -1;
_native.core.SIG_DFL = 0;
_native.core.SIG_IGN = 1;
_native.core.SIG_USR = 2;
/*
 * Class _native.core.ByteArray
 */
_native.core.ByteArray = function(capacity, adoptable) {
    this._array = null;
    this._readOnly = false;
    if (typeof capacity == 'number') {
        if (typeof adoptable == 'number') {
            throw 'Unsupported native operation (new ByteArray from pointer)';
        }
        this._array = new Array(capacity);
    } else if (typeof capacity == 'string') {
        this._array = new Array(/*capacity.length * 2*/);
    	for (var i = 0; i < capacity.length; i++) {
    		var c = capacity.charCodeAt(i);
            this._array.push(c & 0xff);
            this._array.push((c >>> 8) & 0xff);
    	}
        //_native.core.log(0, 'ByteArray.construct(), this._array.length: ' + this._array.length);
        this._readOnly = ! adoptable;
    } else {
        throw 'Illegal argument type ' + (typeof capacity);
    }
};
_native.core.ByteArray.prototype.free = function() {
    this._array = null;
};
_native.core.ByteArray.prototype.size = function() {
    return this._array.length;
};
_native.core.ByteArray.prototype.put = function(offset, value) {
    this._array[offset] = value & 0xff;
};
_native.core.ByteArray.prototype.get = function(offset) {
    return this._array[offset];
};
_native.core.ByteArray.prototype.putString = _native.boot.unsupportedFunction; // TODO
_native.core.ByteArray.prototype.getString = _native.boot.unsupportedFunction; // TODO
_native.core.ByteArray.prototype.putChars = _native.boot.unsupportedFunction; // TODO
_native.core.ByteArray.prototype.getChars = function(at, length) {
    var s = '';
    for (var i = at; i < length; i++) {
        var c = this._array[i * 2] | (this._array[i * 2 + 1] << 8);
        s += String.fromCharCode(c);
    }
    return s;
};
_native.core.ByteArray.prototype.toNewChars = function(length) {
    return this.getChars(0, length);
};
_native.core.ByteArray.prototype.pointer = _native.boot.unsupportedFunction;
_native.core.ByteArray.prototype.memcpy = function(offset, byteArray, at, length) {
    //_native.core.log(0, 'ByteArray.memcpy(' + offset + ', ?, ' + at + ', ' + length + ')');
    for (var i = 0; i < length; i++) {
        this._array[offset + i] = byteArray._array[at + i];
    }
};
_native.core.ByteArray.prototype.setPointer = _native.boot.unsupportedFunction;
_native.core.ByteArray.prototype.resetPointer = _native.boot.unsupportedFunction;
_native.core.ByteArray.prototype.isReadOnly = function() {
    return this._readOnly;
};
/*
 * Unsupported classes
 */
_native.core.Thread = _native.boot.unsupportedClass;
_native.core.Lock = _native.boot.unsupportedClass;
_native.core.Monitor = _native.boot.unsupportedClass;
_native.core.Process = _native.boot.unsupportedClass;
_native.core.ProcessAttr = _native.boot.unsupportedClass;

// Update script path
if ('arguments' in jlsOptions) {
    _native.core.arguments = jlsOptions.arguments;
}
// Update properties
if ('properties' in jlsOptions) {
	for (var name in jlsOptions.properties) {
		_native.core.properties[name] = jlsOptions.properties[name];
	}
}
// Update script path
_native.core._scriptPath = _native.core.properties['jls.script.path'].split(/[;:]/);

// Load Prototype light
if (! (('from' in Array) && ('extend' in Object))) {
	_native.core.evalScript('plight.js');
}
// Load AMD
if (typeof require != 'function') {
	_native.core.evalScript('amdimpl.js');
}

require(['_AMD'], function(_AMD) {
    _AMD.setLogFunction(function(msg) {
        _native.core.log(4, msg);
    });
    _AMD.setEvalScriptFunction(_native.core._evalScript);
    if ('jls.logger.logLevel' in _native.core.properties) {
    	_AMD.lazyRequire(['jls/lang/Logger'], function(Logger) {
            Logger.getInstance().setLogLevel(_native.core.properties['jls.logger.logLevel']);
        });
    }
});

//_native.boot.log('... Bootstrapping done');
