"use strict";
var NameStack = require("./name-stack.js");

var state = {
  syntax: {},

  /**
   * Determine if the code currently being linted is strict mode code.
   *
   * @returns {boolean}
   */
  isStrict: function() {
    return !!this.directive["use strict"] || this.inClassBody ||
      this.option.module || this.option.strict === "implied";
  },

  /**
   * Determine if the current state warrants a warning for statements outside
   * of strict mode code.
   *
   * While emitting warnings based on function scope would be more intuitive
   * (and less noisy), JSHint observes statement-based semantics in order to
   * preserve legacy behavior.
   *
   * This method does not take the state of the parser into account, making no
   * distinction between global code and function code. Because the "missing
   * 'use strict'" warning is *also* reported at function boundaries, this
   * function interprets `strict` option values `true` and `undefined` as
   * equivalent.
   */
  stmtMissingStrict: function() {
    if (this.option.strict === "global") {
      return true;
    }

    if (this.option.strict === false) {
      return false;
    }

    if (this.option.globalstrict) {
      return true;
    }

    return false;
  },

  allowsGlobalUsd: function() {
    return this.option.strict === "global" || this.option.globalstrict ||
      this.option.module || this.impliedClosure();
  },

  /**
   * Determine if the current configuration describes an environment that is
   * wrapped in an immediately-invoked function expression prior to evaluation.
   *
   * @returns {boolean}
   */
  impliedClosure: function() {
    return this.option.node || this.option.phantom || this.option.browserify;
  },

  // Assumption: chronologically ES3 < ES5 < ES6 < Moz

  inMoz: function() {
    return this.option.moz;
  },

  /**
   * Determine if constructs introduced in ECMAScript 11 should be accepted.
   *
   * @returns {boolean}
   */
  inES11: function() {
    return this.esVersion >= 11;
  },

  /**
   * Determine if constructs introduced in ECMAScript 10 should be accepted.
   *
   * @returns {boolean}
   */
  inES10: function() {
    return this.esVersion >= 10;
  },

  /**
   * Determine if constructs introduced in ECMAScript 9 should be accepted.
   *
   * @returns {boolean}
   */
  inES9: function() {
    return this.esVersion >= 9;
  },

  /**
   * Determine if constructs introduced in ECMAScript 8 should be accepted.
   *
   * @returns {boolean}
   */
  inES8: function() {
    return this.esVersion >= 8;
  },

  /**
   * Determine if constructs introduced in ECMAScript 7 should be accepted.
   *
   * @returns {boolean}
   */
  inES7: function() {
    return this.esVersion >= 7;
  },

  /**
   * Determine if constructs introduced in ECMAScript 6 should be accepted.
   *
   * @param {boolean} strict - When `true`, do not interpret the `moz` option
   *                           as ECMAScript 6
   *
   * @returns {boolean}
   */
  inES6: function(strict) {
    if (!strict && this.option.moz) {
      return true;
    }

    return this.esVersion >= 6;
  },

  /**
   * Determine if constructs introduced in ECMAScript 5 should be accepted.
   *
   * @returns {boolean}
   */
  inES5: function() {
    return !this.esVersion || this.esVersion >= 5 || this.option.moz;
  },

  /**
   * Determine the current version of the input language by inspecting the
   * value of all ECMAScript-version-related options. This logic is necessary
   * to ensure compatibility with deprecated options `es3`, `es5`, and
   * `esnext`, and it may be drastically simplified when those options are
   * removed.
   *
   * @returns {string|null} - the name of any incompatible option detected,
   *                          `null` otherwise
   */
  inferEsVersion: function() {
    var badOpt = null;

    if (this.option.esversion) {
      if (this.option.es3) {
        badOpt = "es3";
      } else if (this.option.es5) {
        badOpt = "es5";
      } else if (this.option.esnext) {
        badOpt = "esnext";
      }

      if (badOpt) {
        return badOpt;
      }

      if (this.option.esversion === 2015) {
        this.esVersion = 6;
      } else {
        this.esVersion = this.option.esversion;
      }
    } else if (this.option.es3) {
      this.esVersion = 3;
    } else if (this.option.esnext) {
      this.esVersion = 6;
    }

    return null;
  },

  reset: function() {
    this.tokens = {
      prev: null,
      next: null,
      curr: null
    };

    this.option = { unstable: {} };
    this.esVersion = 5;
    this.funct = null;
    this.ignored = {};
    /**
     * A lookup table for active directives whose keys are the value of the
     * directives and whose values are the tokens which enabled the directives.
     */
    this.directive = Object.create(null);
    this.jsonMode = false;
    this.lines = [];
    this.tab = "";
    this.cache = {}; // Node.JS doesn't have Map. Sniff.
    this.ignoredLines = {};
    this.forinifcheckneeded = false;
    this.nameStack = new NameStack();
    this.inClassBody = false;
  }
};

exports.state = state;
