"use strict";

var traverse = require("../../traverse");
var util     = require("../../util");
var t        = require("../../types");

exports.Function = function (node, parent, scope, context, file) {
  if (!node.defaults || !node.defaults.length) return;
  t.ensureBlock(node);

  var ids = node.params.map(function (param) {
    return t.getIds(param);
  });

  var iife = false;
  var def;

  var checkTDZ = function (param, def, ids) {
    var check = function (node, parent) {
      if (!t.isReferencedIdentifier(node, parent)) return;

      if (ids.indexOf(node.name) >= 0) {
        throw file.errorWithNode(node, "Temporal dead zone - accessing a variable before it's initialized");
      }

      if (scope.has(node.name, true)) {
        iife = true;
      }
    };

    check(def, node);

    if (!t.isPattern(param)) {
      traverse(def, { enter: check });
    }
  };

  for (var i = 0; i < node.defaults.length; i++) {
    def = node.defaults[i];
    if (!def) continue;

    var param = node.params[i];

    // temporal dead zone check - here we prevent accessing of params that
    // are to the right - ie. uninitialized parameters
    var rightIds = ids.slice(i);
    for (var i2 = 0; i2 < rightIds.length; i2++) {
      checkTDZ(param, def, rightIds[i2]);
    }

    // we're accessing a variable that's already defined within this function
    var has = scope.get(param.name, true);
    if (has && node.params.indexOf(has) < 0) {
      iife = true;
    }
  }

  var body = [];

  var argsIdentifier = t.identifier("arguments");
  argsIdentifier._ignoreAliasFunctions = true;

  var lastNonDefaultParam = 0;

  for (i = 0; i < node.defaults.length; i++) {
    def = node.defaults[i];
    if (!def) {
      lastNonDefaultParam = +i + 1;
      continue;
    }

    var def = util.template("default-parameter", {
      VARIABLE_NAME: node.params[i],
      DEFAULT_VALUE: def,
      ARGUMENT_KEY:  t.literal(+i),
      ARGUMENTS:     argsIdentifier
    }, true);
    def._blockHoist = node.defaults.length - i;
    body.push(def);
  }

  // we need to cut off all trailing default parameters
  node.params = node.params.slice(0, lastNonDefaultParam);

  if (iife) {
    var container = t.functionExpression(null, [], node.body, node.generator);
    container._aliasFunction = true;

    body.push(t.returnStatement(t.callExpression(container, [])));

    node.body = t.blockStatement(body);
  } else {
    node.body.body = body.concat(node.body.body);
  }

  node.defaults = [];
};

"use strict";

var traverse = require("../../traverse");
var util     = require("../../util");
var t        = require("../../types");

exports.Function = function (node, parent, scope, context, file) {
  if (!node.defaults || !node.defaults.length) return;
  t.ensureBlock(node);

  var ids = node.params.map(function (param) {
    return t.getIds(param);
  });

  var iife = false;
  var def;

  var checkTDZ = function (param, def, ids) {
    var check = function (node, parent) {
      if (!t.isReferencedIdentifier(node, parent)) return;

      if (ids.indexOf(node.name) >= 0) {
        throw file.errorWithNode(node, "Temporal dead zone - accessing a variable before it's initialized");
      }

      if (scope.has(node.name, true)) {
        iife = true;
      }
    };

    check(def, node);

    if (!t.isPattern(param)) {
      traverse(def, { enter: check });
    }
  };

  for (var i = 0; i < node.defaults.length; i++) {
    def = node.defaults[i];
    if (!def) continue;

    var param = node.params[i];

    // temporal dead zone check - here we prevent accessing of params that
    // are to the right - ie. uninitialized parameters
    var rightIds = ids.slice(i);
    for (var i2 = 0; i2 < rightIds.length; i2++) {
      checkTDZ(param, def, rightIds[i2]);
    }

    // we're accessing a variable that's already defined within this function
    var has = scope.get(param.name, true);
    if (has && node.params.indexOf(has) < 0) {
      iife = true;
    }
  }

  var body = [];

  var argsIdentifier = t.identifier("arguments");
  argsIdentifier._ignoreAliasFunctions = true;

  var lastNonDefaultParam = 0;

  for (i = 0; i < node.defaults.length; i++) {
    def = node.defaults[i];
    if (!def) {
      lastNonDefaultParam = +i + 1;
      continue;
    }

    var defNode = util.template("default-parameter", {
      VARIABLE_NAME: node.params[i],
      DEFAULT_VALUE: def,
      ARGUMENT_KEY:  t.literal(+i),
      ARGUMENTS:     argsIdentifier
    }, true);
    defNode._blockHoist = node.defaults.length - i;
    body.push(defNode);
  }

  // we need to cut off all trailing default parameters
  node.params = node.params.slice(0, lastNonDefaultParam);

  if (iife) {
    var container = t.functionExpression(null, [], node.body, node.generator);
    container._aliasFunction = true;

    body.push(t.returnStatement(t.callExpression(container, [])));

    node.body = t.blockStatement(body);
  } else {
    node.body.body = body.concat(node.body.body);
  }

  node.defaults = [];
};

fix default parameter node variable name