// This file is part of nbind, copyright (C) 2014-2016 BusFaster Ltd.
// Released under the MIT license, see LICENSE.
"use strict";
var path = require('path'); // tslint:disable-line:no-var-requires
var Binding = (function () {
    function Binding() {
    }
    return Binding;
}());
exports.Binding = Binding;
/** Default callback that throws any error given to it. */
function rethrow(err, result) {
    if (err)
        throw (err);
}
/** Make list of possible paths for a single compiled output file name. */
function makeModulePathList(root, name) {
    return ([
        // Binary copied using copyasm
        [root, name],
        // node-gyp's linked version in the "build" dir
        [root, 'build', name],
        // node-waf and gyp_addon (a.k.a node-gyp)
        [root, 'build', 'Debug', name],
        [root, 'build', 'Release', name],
        // Debug files, for development (legacy behavior, remove for node v0.9)
        [root, 'out', 'Debug', name],
        [root, 'Debug', name],
        // Release files, but manually compiled (legacy behavior, remove for node v0.9)
        [root, 'out', 'Release', name],
        [root, 'Release', name],
        // Legacy from node-waf, node <= 0.4.x
        [root, 'build', 'default', name],
        [
            root,
            process.env['NODE_BINDINGS_COMPILED_DIR'] || 'compiled',
            process.versions.node,
            process.platform,
            process.arch,
            name
        ]
    ]);
}
function findCompiledModule(basePath, specList, callback) {
    var resolvedList = [];
    var ext = path.extname(basePath);
    /** If basePath has a known extension, check if it's a loadable module. */
    for (var _i = 0, specList_1 = specList; _i < specList_1.length; _i++) {
        var spec = specList_1[_i];
        if (ext == spec.ext) {
            try {
                spec.path = require.resolve(basePath);
                // Stop if a module was found.
                callback(null, spec);
                return (spec);
            }
            catch (err) {
                resolvedList.push(basePath);
            }
        }
    }
    /** Try all possible subdirectories of basePath. */
    for (var _a = 0, specList_2 = specList; _a < specList_2.length; _a++) {
        var spec = specList_2[_a];
        // Check if any possible path contains a loadable module,
        // and store unsuccessful attempts.
        for (var _b = 0, _c = makeModulePathList(basePath, spec.name); _b < _c.length; _b++) {
            var pathParts = _c[_b];
            var resolvedPath = path.resolve.apply(path, pathParts);
            try {
                spec.path = require.resolve(resolvedPath);
            }
            catch (err) {
                resolvedList.push(resolvedPath);
                continue;
            }
            // Stop if a module was found.
            callback(null, spec);
            return (spec);
        }
    }
    var err = new Error('Could not locate the bindings file. Tried:\n' +
        resolvedList.join('\n'));
    err.tries = resolvedList;
    callback(err);
    return (null);
}
function find(basePath, cb) {
    var callback = arguments[arguments.length - 1];
    if (typeof (callback) != 'function')
        callback = rethrow;
    return (findCompiledModule((basePath != callback && basePath) || process.cwd(), [
        { ext: '.node', name: 'nbind.node', type: 'node' },
        { ext: '.js', name: 'nbind.js', type: 'emcc' }
    ], callback));
}
exports.find = find;
function init(basePath, lib, cb) {
    var callback = arguments[arguments.length - 1];
    if (typeof (callback) != 'function')
        callback = rethrow;
    var binding = new Binding();
    find(basePath != callback && basePath, function (err, binary) {
        if (err) {
            callback(err);
            return;
        }
        binding.binary = binary;
        binding.lib = (lib != callback && lib) || {};
        if (binary.type == 'emcc') {
            initAsm(binding, callback);
        }
        else {
            initNode(binding, callback);
        }
    });
    return (binding);
}
exports.init = init;
/** Initialize asm.js module. */
function initAsm(binding, callback) {
    var lib = binding.lib;
    lib.locateFile = lib.locateFile || function (name) {
        return (path.resolve(path.dirname(binding.binary.path), name));
    };
    // Load the Asm.js module.
    require(binding.binary.path)(lib, function (err, parts) {
        if (!err) {
            for (var _i = 0, _a = Object.keys(parts); _i < _a.length; _i++) {
                var key = _a[_i];
                binding[key] = parts[key];
            }
        }
        callback(err, binding);
    });
}
/** Initialize native Node.js addon. */
function initNode(binding, callback) {
    // Load the compiled addon.
    var lib = require(binding.binary.path);
    if (!lib || typeof (lib) != 'object') {
        callback(new Error('Error loading addon'));
        return;
    }
    binding.bind = lib.NBind.bind_value;
    binding.reflect = lib.NBind.reflect;
    binding.queryType = lib.NBind.queryType;
    binding.toggleLightGC = function (enable) { }; // tslint:disable-line:no-empty
    Object.keys(lib).forEach(function (key) {
        binding.lib[key] = lib[key];
    });
    callback(null, binding);
}
