// LICENSE_CODE ZON
'use strict'; /*zlint node*/
// require('./config.js') is not allowed here
var assert = require('assert');
var binding = process.binding('zutil');
var fs = require('fs');
var dns = require('dns');
var _path = require('path');
var path = require('path').posix;
var node_module = require('module');
var S_IFDIR = fs.constants.S_IFDIR;
var S_IFREG = fs.constants.S_IFREG;
var vfs_re = /^\/vfs:(\/.*)?$/;
var tmp_path = {};

var orig_readFileSync = fs.readFileSync;
fs.readFileSync = function(filename, opt){
    var m = vfs_re.exec(filename);
    if (!m)
        return orig_readFileSync(filename, opt);
    return binding.vfs_res(m[1]||'/');
};

var orig_statSync = fs.statSync;
fs.statSync = function(filename){
    var m = vfs_re.exec(filename);
    if (!m)
        return orig_statSync(filename);
    return binding.vfs_stat(m[1]||'/');
};

var orig_lstatSync = fs.lstatSync;
fs.lstatSync = function(filename){
    var m = vfs_re.exec(filename);
    if (!m)
        return orig_lstatSync(filename);
    return binding.vfs_stat(m[1]||'/');
};

var orig_resolveLookupPaths = node_module._resolveLookupPaths;
if (0) // XXX: re-enable in release builds
node_module._resolveLookupPaths = function(request, parent){
    var res = orig_resolveLookupPaths(request, parent);
    var orig = res[1], filtered = [];
    // filter out all lookup paths outside VFS
    for (var i = 0; i<orig.length; i++)
    {
        if (vfs_re.exec(orig[i]))
            filtered.push(orig[i]);
    }
    res[1] = filtered;
    return res;
};

var pkg_main_cache = {};
function read_pkg(req_path){
    if (Object.prototype.hasOwnProperty.call(pkg_main_cache, req_path))
        return pkg_main_cache[req_path];
    var jpath = path.resolve(req_path, 'package.json');
    if (!try_file(jpath))
        return false;
    var json = fs.readFileSync(jpath);
    if (!json)
        return false;
    var pkg;
    try { pkg = pkg_main_cache[req_path] = JSON.parse(json).main; }
    catch(e){ return false; }
    return pkg;
}

function try_pkg(req_path, exts){
    var pkg = read_pkg(req_path);
    if (!pkg)
        return false;
    var filename = path.resolve(req_path, pkg);
    return try_file(filename) || try_exts(filename, exts) ||
        try_exts(path.resolve(filename, 'index'), exts);
}

function vfs_stat(filename){
    var st, m = vfs_re.exec(filename);
    try { st = binding.vfs_stat(m ? m[1] : filename); }
    catch(e){ return null; }
    return st;
}

function try_file(req_path){
    var st = vfs_stat(req_path);
    return st && st.mode&S_IFREG ? req_path : null;
}

function try_exts(p, exts){
    for (var i=0; i<exts.length; i++)
    {
        var filename = try_file(p+exts[i]);
        if (filename)
            return filename;
    }
    return false;
}

var orig_findPath = node_module._findPath;
node_module._findPath = function(request, paths){
    if (path.isAbsolute(request))
        paths = [''];
    var cacheKey = JSON.stringify({request: request, paths: paths});
    if (node_module._pathCache[cacheKey])
        return node_module._pathCache[cacheKey];
    var trailing_slash = request.slice(-1)=='/';
    var exts = Object.keys(node_module._extensions);
    for (var i=0; i<paths.length; i++)
    {
        var basepath = path.resolve(paths[i], request);
        var m = vfs_re.exec(basepath);
        if (!m)
            continue;
        var filename, st;
        if (!trailing_slash)
        {
            if (!(st=vfs_stat(m[1])));
            else if (st.mode&S_IFREG)
                filename = basepath;
            else if (st.mode&S_IFDIR)
                filename = try_pkg(basepath, exts);
            if (!filename)
                filename = try_exts(basepath, exts);
        }
        if (!filename)
            filename = try_pkg(basepath, exts);
        if (!filename)
            filename = try_exts(path.resolve(basepath, 'index'), exts);
        if (filename)
        {
            node_module._pathCache[cacheKey] = filename;
            console.log('vfs module loaded '+filename);
            return filename;
        }
    }
    return orig_findPath(request, paths);
};

var main;

process.zon.init = function(){
    var zerr = node_module._load('/vfs:/util/zerr.js', main);
    zerr.set_logger(function(level, text){
        return binding.zerr(level, ''+text); });
    function remove_nl(text){
        return text && text[text.length-1]=='\n' ?
            text.substr(0, text.length-1) : text;
    }
    process.stdout.write = function(text){
        binding.zerr(zerr.L.NOTICE, remove_nl(text)); };
    process.stderr.write = function(text){
        binding.zerr(zerr.L.ERR, remove_nl(text)); };
    process.on('uncaughtException', zerr.zexit);
};

function copy_path(from, to){
    ['basename', 'dirname', 'resolve', 'sep', 'extname'].forEach(name=>{
        to[name] = from[name]; });
}
copy_path(_path, tmp_path);
copy_path(path, _path);
main = node_module._load(process.zon.main, null, true);
copy_path(tmp_path, _path);

function undo_node_signals_handlers(){
    // Sanity check: only signals handler has installed the listener
    assert.equal(process.listenerCount('newListener'), 1);
    assert.equal(process.listenerCount('removeListener'), 1);

    process.removeAllListeners('newListener');
    process.removeAllListeners('removeListener');
}

let _lazy_signal_names;
function signal_names(){
    if (!_lazy_signal_names)
        _lazy_signal_names = process.binding('constants').os.signals;
    return _lazy_signal_names;
}

function install_limited_signals_handlers(){
    process.on('newListener', function(name, listener){
        if (typeof name=='string' && signal_names()[name])
        {
            if (name!='SIGTERM' && name!='SIGUSR1')
            {
                throw Error('Only SIGTERM and SIGUSR1 handlers are allowed in '
                    +'embedded Node');
            }
        }
    });
}

function setup_signals(){
    // bootstrap_node.js already set up handlers for signals, undo it to avoid
    // interfering with C code and only allow a few signals to be listened to
    // to avoid unpleant surprises where signals are expected, but not
    // delivered.
    undo_node_signals_handlers();
    // C code delivers signals SIGTERM and SIGUSR1 by directly invoking
    // process.emit(SIGNUM)
    install_limited_signals_handlers();
}

setup_signals();
