(function(mod) {
  if (typeof exports == "object" && typeof module == "object") // CommonJS
    return mod(require("../lib/infer"), require("../lib/tern"), require("./modules"))
  if (typeof define == "function" && define.amd) // AMD
    return define(["../lib/infer", "../lib/tern", "./modules"], mod)
  mod(tern, tern)
})(function(infer, tern) {
  "use strict"

  var WG_DEFAULT_EXPORT = 95

  function initScope(scope) {
  	/* TODO ORION
  		Orion filters based on eslint-env name, preventing commonjs defs from showing up.  Below the plugin adds
  		modules/exports into every scope which is a problem if you are using RequireJS.  For now we add require
  		to every scope, and only add any of the types if we know the file uses a node environment.  A better solution
  		would be to always skip adding modules/exports to every scope, and remove the eslint-env filtering that prevents
  		commonjs defs infos from showing up.
  	*/
  	// ORION: This puts exports and modules into every scope's completions, we only want them when inside a file using node
    if (scope.originNode && scope.originNode.environments && scope.originNode.environments.node){
	    var defs = infer.cx().definitions.commonjs
	    defs.require.propagate(scope.defProp("require"))
	    
	    // ORION: Add require to the list of completions available when node is being used
	    var require = new infer.Obj(true);
	    require.propagate(scope.defProp("require"));
	    
	    var module = new infer.Obj(defs.Module.getProp("prototype").getType())
	    module.propagate(scope.defProp("module"))
	    var exports = new infer.Obj(true)
	    module.origin = exports.origin = scope.origin
	    module.originNode = exports.originNode = scope.originNode
	    exports.propagate(scope.defProp("exports"))
	    var moduleExports = scope.exports = module.defProp("exports")
	    exports.propagate(moduleExports, WG_DEFAULT_EXPORT)
    }
  }

  infer.registerFunction("require", function(_self, _args, argNodes) {
    if (!argNodes || !argNodes.length || argNodes[0].type != "Literal" || typeof argNodes[0].value != "string")
      return infer.ANull
    var cx = infer.cx(), server = cx.parent
    var currentFile = argNodes[0].sourceFile.name

    var name = argNodes[0].value
    var resolved = server.mod.modules.resolveModule(name, currentFile)
    return resolved
  })

  function isStaticRequire(node) {
    if (node.type != "CallExpression" || node.callee.type != "Identifier" || node.callee.name != "require") return
    var arg = node.arguments[0]
    if (arg && arg.type == "Literal" && typeof arg.value == "string") return arg.value
  }
  
  function _getAST(node){
  	// ORION In our version of Acorn the AST is not available on the given node
    var ast = node.sourceFile.ast;
    if (!ast){
        var server = infer.cx().parent;
        ast = server.fileMap[node.sourceFile.name];
        if (!ast) return;
        ast = ast.ast;
    }
    return ast;
  }

  function isModuleName(node) {
    if (node.type != "Literal" || typeof node.value != "string") return
    var call = infer.findExpressionAround(_getAST(node), null, node.end, null,
                                          function(_, n) { return isStaticRequire(n) != null })
    if (call && call.node.arguments[0] == node) return node.value
  }

  function isImport(node) {
    if (node.type != "Identifier") return
    
    var decl = infer.findExpressionAround(_getAST(node), null, node.end, null, "VariableDeclarator"), name
    if (!decl || decl.node.id != node) return
    var init = decl.node.init
    if (init && (name = isStaticRequire(init)) != null)
      return {name: name, prop: null}
    if (init && init.type == "MemberExpression" && !init.computed && (name = isStaticRequire(init.object)) != null)
      return {name: name, prop: init.property.name}
  }

  function hasProps(obj) {
    if (obj) for (var _prop in obj) return true
  }

  tern.registerPlugin("commonjs", function(server) {
    server.loadPlugin("modules")
    server.mod.modules.on("wrapScope", initScope)
    server.mod.modules.on("getExports", function(file, mod) {
      var exports = file.scope.exports
      // TODO ORION: Because of changes to initScope, scope.exports may not exist
      if (exports){
      	if (exports.types.length > 1 || hasProps(exports.getObjType()))
	        exports.propagate(mod)
        }
    })

    server.mod.modules.modNameTests.push(isModuleName)
    server.mod.modules.importTests.push(isImport)
    server.mod.modules.completableTypes.Identifier = true
    server.mod.modules.completableTypes.Literal = true

    server.addDefs(defs)
  })

  var defs = {
    "!name": "commonjs",
    "!define": {
      require: {
        "!type": "fn(id: string) -> !custom:require",
        resolve: {
          "!type": "fn() -> string",
          "!url": "https://nodejs.org/api/globals.html#globals_require_resolve",
          "!doc": "Use the internal require() machinery to look up the location of a module, but rather than loading the module, just return the resolved filename."
        },
        cache: {
          "!url": "https://nodejs.org/api/globals.html#globals_require_cache",
          "!doc": "Modules are cached in this object when they are required. By deleting a key value from this object, the next require will reload the module."
        },
        extensions: {
          "!url": "https://nodejs.org/api/globals.html#globals_require_extensions",
          "!doc": "Instruct require on how to handle certain file extensions."
        },
        "!url": "https://nodejs.org/api/globals.html#globals_require",
        "!doc": "To require modules."
      },
      Module: {
        "!type": "fn()",
        "!url": "https://nodejs.org/api/modules.html",
        "!doc": "Node has a simple module loading system. In Node, files and modules are in one-to-one correspondence.",
        prototype: {
          exports: {
            "!type": "?",
            "!url": "https://nodejs.org/api/modules.html#modules_module_exports",
            "!doc": "The exports object is created by the Module system. Sometimes this is not acceptable, many want their module to be an instance of some class. To do this assign the desired export object to module.exports. For example suppose we were making a module called a.js"
          },
          require: {
            "!type": "require",
            "!url": "https://nodejs.org/api/modules.html#modules_module_require_id",
            "!doc": "The module.require method provides a way to load a module as if require() was called from the original module."
          },
          id: {
            "!type": "string",
            "!url": "https://nodejs.org/api/modules.html#modules_module_id",
            "!doc": "The identifier for the module. Typically this is the fully resolved filename."
          },
          filename: {
            "!type": "string",
            "!url": "https://nodejs.org/api/modules.html#modules_module_filename",
            "!doc": "The fully resolved filename to the module."
          },
          loaded: {
            "!type": "bool",
            "!url": "https://nodejs.org/api/modules.html#modules_module_loaded",
            "!doc": "Whether or not the module is done loading, or is in the process of loading."
          },
          parent: {
            "!type": "+Module",
            "!url": "https://nodejs.org/api/modules.html#modules_module_parent",
            "!doc": "The module that required this one."
          },
          children: {
            "!type": "[+Module]",
            "!url": "https://nodejs.org/api/modules.html#modules_module_children",
            "!doc": "The module objects required by this one."
          }
        }
      },
      module: {}
    },
    module: {
      "!type": "+Module",
      "!url": "https://nodejs.org/api/globals.html#globals_module",
      "!doc": "A reference to the current module. In particular module.exports is the same as the exports object. module isn't actually a global but rather local to each module."
    }
  }
})
