/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

/*global exports:true*/
/*jslint node:true*/
'use strict';

var Syntax = require('esprima-fb').Syntax;

/**
 * Executes visitor on the object and its children (recursively).
 * While traversing the tree, a scope chain is built and passed to the visitor.
 *
 * If the visitor returns false, the object's children are not traversed.
 *
 * @param {object} object
 * @param {function} visitor
 * @param {?array} scopeChain
 */
function traverse(object, visitor, scopeChain) {
  scopeChain = scopeChain || [{}];

  var scope = scopeChain[0];

  switch (object.type) {
    case Syntax.VariableDeclaration:
      object.declarations.forEach(function(decl) {
        scope[decl.id.name] = decl.init;
      });
      break;
    case Syntax.ClassDeclaration:
      scope[object.id.name] = object;
      break;
    case Syntax.FunctionDeclaration:
      // A function declaration creates a symbol in the current scope
      scope[object.id.name] = object;
      /* falls through */
    case Syntax.FunctionExpression:
    case Syntax.Program:
      scopeChain = [{}].concat(scopeChain);
      break;
  }

  if (object.type === Syntax.FunctionExpression ||
      object.type === Syntax.FunctionDeclaration) {
    // add parameters to the new scope
    object.params.forEach(function(param) {
      // since the value of the parameters are  unknown during parsing time
      // we set the value to `undefined`.
      scopeChain[0][param.name] = undefined;
    });
  }

  if (object.type) {
    if (visitor.call(null, object, scopeChain) === false) {
      return;
    }
  }

  for (var key in object) {
    if (object.hasOwnProperty(key)) {
      var child = object[key];
      if (typeof child === 'object' && child !== null) {
        traverse(child, visitor, scopeChain);
      }
    }
  }
}

/**
 * Executes visitor on the object and its children, but only traverses into
 * children which can be statically analyzed and don't depend on runtime
 * information.
 *
 * @param {object} object
 * @param {function} visitor
 * @param {?array} scopeChain
 */
function traverseFlat(object, visitor, scopeChain) {
  traverse(object, function(node, scopeChain) {
    switch (node.type) {
      case Syntax.FunctionDeclaration:
      case Syntax.FunctionExpression:
      case Syntax.IfStatement:
      case Syntax.WithStatement:
      case Syntax.SwitchStatement:
      case Syntax.TryStatement:
      case Syntax.WhileStatement:
      case Syntax.DoWhileStatement:
      case Syntax.ForStatement:
      case Syntax.ForInStatement:
        return false;
    }
    return visitor(node, scopeChain);
  }, scopeChain);
}

module.exports = traverseFlat;
