// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

/**
 * @fileoverview The base module responsible for bootstrapping the Closure
 * library and providing a means of loading Closure-based modules.
 *
 * <p>Each script loaded by this module will be granted access to this module's
 * {@code require} function; all required non-native modules must be specified
 * relative to <em>this</em> module.
 *
 * <p>This module will load all scripts from the "lib" subdirectory, unless the
 * SELENIUM_DEV_MODE environment variable has been set to 1, in which case all
 * scripts will be loaded from the Selenium client containing this script.
 */

'use strict';

var fs = require('fs'),
    path = require('path'),
    vm = require('vm');


/**
 * If this script was loaded from the Selenium project repo, it will operate in
 * development mode, adjusting how it loads Closure-based dependencies.
 * @type {boolean}
 */
var devMode = (function() {
  var buildDescFile = path.join(__dirname, '..', '..', 'build.desc');
  return fs.existsSync(buildDescFile);
})();


/** @return {boolean} Whether this script was loaded in dev mode. */
function isDevMode() {
  return devMode;
}


/**
 * @type {string} Path to Closure's base file, relative to this module.
 * @const
 */
var CLOSURE_BASE_FILE_PATH = (function() {
  var relativePath = isDevMode() ?
      '../../../../third_party/closure/goog/base.js' :
      './goog/base.js';
  return path.join(__dirname, relativePath);
})();


/**
 * @type {string} Path to Closure's base file, relative to this module.
 * @const
 */
var DEPS_FILE_PATH = (function() {
  var relativePath = isDevMode() ?
      '../../../../javascript/deps.js' :
      './goog/deps.js';
  return path.join(__dirname, relativePath);
})();


/**
 * Maintains a unique context for Closure library-based code.
 * @param {boolean=} opt_configureForTesting Whether to configure a fake DOM
 *     for Closure-testing code that (incorrectly) assumes a DOM is always
 *     present.
 * @constructor
 */
function Context(opt_configureForTesting) {
  var closure = this.closure = vm.createContext({
    console: console,
    setTimeout: setTimeout,
    setInterval: setInterval,
    clearTimeout: clearTimeout,
    clearInterval: clearInterval,
    process: process,
    require: require,
    Buffer: Buffer,
    Error: Error,
    TypeError: TypeError,
    CLOSURE_BASE_PATH: path.dirname(CLOSURE_BASE_FILE_PATH) + '/',
    CLOSURE_IMPORT_SCRIPT: function(src, opt_srcText) {
      if (opt_srcText !== undefined) {
        // Windows paths use backslashes, which must be properly escaped before
        // evaluated with vm.runInContext.
        opt_srcText = opt_srcText.replace(/\\/g, '/');
        vm.runInContext(opt_srcText, closure, src);
      } else {
        loadScript(src);
      }
      return true;
    },
    CLOSURE_NO_DEPS: !isDevMode(),
    CLOSURE_UNCOMPILED_DEFINES: {'goog.json.USE_NATIVE_JSON': true},
    goog: {},
    webdriver: {
      get Command() { return require('./command').Command; },
      get CommandName() { return require('./command').Name; },
      get CommandExecutor() { return require('./command').Executor; },
      get logging() { return require('./logging'); }
    }
  });
  closure.window = closure.top = closure;

  if (opt_configureForTesting) {
    closure.document = {
      body: {},
      createElement: function() { return {}; },
      getElementsByTagName: function() { return []; }
    };
    closure.document.body.ownerDocument = closure.document;
  }

  loadScript(CLOSURE_BASE_FILE_PATH);
  loadScript(DEPS_FILE_PATH);

  let provide = closure.goog.provide;
  closure.goog.provide = function(symbol) {
    if (symbol.startsWith('webdriver.Command')
        || symbol.startsWith('webdriver.logging')) {
      return;
    }
    provide(symbol);
  };

  // Redefine retrieveAndExecModule_ to load modules. Closure's version
  // assumes XMLHttpRequest is defined (and by extension that scripts
  // are being loaded from a server).
  closure.goog.retrieveAndExecModule_ = function(src) {
    var normalizedSrc = path.normalize(src);
    var contents = fs.readFileSync(normalizedSrc, 'utf8');
    contents = closure.goog.wrapModule_(src, contents);
    vm.runInContext(contents, closure, normalizedSrc);
  };

  /**
   * Synchronously loads a script into the protected Closure context.
   * @param {string} src Path to the file to load.
   */
  function loadScript(src) {
    src = path.normalize(src);
    var contents = fs.readFileSync(src, 'utf8');
    vm.runInContext(contents, closure, src);
  }
}


var context = new Context();


/**
 * Loads a symbol by name from the protected Closure context.
 * @param {string} symbol The symbol to load.
 * @return {?} The loaded symbol, or {@code null} if not found.
 * @throws {Error} If the symbol has not been defined.
 */
function closureRequire(symbol) {
  context.closure.goog.require(symbol);
  return context.closure.goog.getObjectByName(symbol);
}


// PUBLIC API


/**
 * Loads a symbol by name from the protected Closure context and exports its
 * public API to the provided object. This function relies on Closure code
 * conventions to define the public API of an object as those properties whose
 * name does not end with "_".
 * @param {string} symbol The symbol to load. This must resolve to an object.
 * @return {!Object} An object with the exported API.
 * @throws {Error} If the symbol has not been defined or does not resolve to
 *     an object.
 */
exports.exportPublicApi = function(symbol) {
  var src = closureRequire(symbol);
  if (typeof src != 'object' || src === null) {
    throw Error('"' + symbol + '" must resolve to an object');
  }

  var dest = {};
  Object.keys(src).forEach(function(key) {
    if (key[key.length - 1] != '_') {
      dest[key] = src[key];
    }
  });

  return dest;
};


if (isDevMode()) {
  exports.closure = context.closure;
}
exports.Context = Context;
exports.isDevMode = isDevMode;
exports.require = closureRequire;

@@ -3681,281 +3681,8 @@
 : %7B%7D
-,%0A    webdriver: %7B%0A      get Command() %7B return require('./command').Command; %7D,%0A      get CommandName() %7B return require('./command').Name; %7D,%0A      get CommandExecutor() %7B return require('./command').Executor; %7D,%0A      get logging() %7B return require('./logging'); %7D%0A    %7D
 %0A  %7D
@@ -4048,237 +4048,8 @@
 );%0A%0A
-  let provide = closure.goog.provide;%0A  closure.goog.provide = function(symbol) %7B%0A    if (symbol.startsWith('webdriver.Command')%0A        %7C%7C symbol.startsWith('webdriver.logging')) %7B%0A      return;%0A    %7D%0A    provide(symbol);%0A  %7D;%0A%0A
   //

Stop patching in modules that have been converted from closure to node as it complicates instanceof checks in the remaining closure-based modules.