var natives = process.binding('natives')
var module = require('module')
var normalRequire = require
exports.source = src
exports.require = req
var vm = require('vm')

// fallback for 0.x support
var runInThisContext, ContextifyScript, Script
/*istanbul ignore next*/
try {
  ContextifyScript = process.binding('contextify').ContextifyScript;
  /*istanbul ignore next*/
  if (process.version.split('.')[0].length > 2) {  // v10.0.0 and above
    runInThisContext = vm.runInThisContext;
  } else {
    runInThisContext = function runInThisContext(code, options) {
      var script = new ContextifyScript(code, options);
      return script.runInThisContext();
    }
  }
} catch (er) {
  Script = process.binding('evals').NodeScript;
  runInThisContext = Script.runInThisContext;
}

var wrap = [
  '(function (internalBinding) {' +
    ' return function (exports, require, module, __filename, __dirname) { ',
  '\n  };\n});'
];


// Basically the same functionality as node's (buried deep)
// NativeModule class, but without caching, or internal/ blocking,
// or a class, since that's not really necessary.  I assume that if
// you're loading something with this module, it's because you WANT
// a separate copy.  However, to preserve semantics, any require()
// calls made throughout the internal module load IS cached.
function req (id, whitelist) {
  var cache = Object.create(null)

  if (Array.isArray(whitelist)) {
    // a whitelist of things to pull from the "actual" native modules
    whitelist.forEach(function (id) {
      cache[id] = {
        loading: false,
        loaded: true,
        filename: id + '.js',
        exports: require(id)
      }
    })
  }

  return req_(id, cache)
}

function req_ (id, cache) {
  // Buffer is special, because it's a type rather than a "normal"
  // class, and many things depend on `Buffer.isBuffer` working.
  if (id === 'buffer') {
    return require('buffer')
  }

  // native_module isn't actually a natives binding.
  // weird, right?
  if (id === 'native_module') {
    return {
      getSource: src,
      wrap: function (script) {
        return wrap[0] + script + wrap[1]
      },
      wrapper: wrap,
      _cache: cache,
      _source: natives,
      nonInternalExists: function (id) {
        return id.indexOf('internal/') !== 0;
      }
    }
  }

  var source = src(id)
  if (!source) {
    return undefined
  }
  source = wrap[0] + source + wrap[1]

  var internalBinding = function(name) {
    if (name === 'types') {
      return process.binding('util');
    } else {
      try {
        return process.binding(name);
      } catch (e) {}
      return {};
    }
  }

  var cachingRequire = function require (id) {
    if (cache[id]) {
      return cache[id].exports
    }
    if (id === 'internal/bootstrap/loaders' || id === 'internal/process') {
      // Provide just enough to keep `graceful-fs@3` working and tests passing.
      // For now.
      return {
        internalBinding: internalBinding,
        NativeModule: {
          _source: process.binding('natives'),
          nonInternalExists: function(id) {
            return !id.startsWith('internal/');
          }
        }
      };
    }
    return req_(id, cache)
  }

  var nm = {
    exports: {},
    loading: true,
    loaded: false,
    filename: id + '.js'
  }
  cache[id] = nm
  var fn
  var setV8Flags = false
  try {
    require('v8').setFlagsFromString('--allow_natives_syntax')
    setV8Flags = true
  } catch (e) {}
  try {
    /* istanbul ignore else */
    if (ContextifyScript) {
      fn = runInThisContext(source, {
        filename: nm.filename,
        lineOffset: 0,
        displayErrors: true
      });
    } else {
      fn = runInThisContext(source, nm.filename, true);
    }
    fn(internalBinding)(nm.exports, cachingRequire, nm, nm.filename, '<no dirname available>')
    nm.loaded = true
  } finally {
    nm.loading = false
    /*istanbul ignore next*/
    if (setV8Flags) {
      // Ref: https://github.com/nodejs/node/blob/591a24b819d53a555463b1cbf9290a6d8bcc1bcb/lib/internal/bootstrap_node.js#L429-L434
      var re = /^--allow[-_]natives[-_]syntax$/
      if (!process.execArgv.some(function (s) { return re.test(s) }))
        require('v8').setFlagsFromString('--noallow_natives_syntax')
    }
  }

  return nm.exports
}

function src (id) {
  return natives[id]
}