'use strict';

var _fs = require('fs');

var _fs2 = _interopRequireDefault(_fs);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/**
 * Python tokenizer template.
 */
var PHP_TOKENIZER_TEMPLATE = _fs2.default.readFileSync(__dirname + '/templates/tokenizer.template.php', 'utf-8');

/**
 * The trait is used by parser generators (LL/LR) for PHP.
 */
/**
 * The MIT License (MIT)
 * Copyright (c) 2015-present Dmitry Soshnikov <dmitry.soshnikov@gmail.com>
 */

var PHPParserGeneratorTrait = {

  /**
   * Generates parser class name.
   */
  generateParserClassName: function generateParserClassName(className) {
    this.writeData('PARSER_CLASS_NAME', className);
  },


  /**
   * Generates namespace.
   */
  generateNamespace: function generateNamespace() {
    var ns = this.getOptions().namespace;
    var nsString = '';

    if (ns && ns !== '') {
      nsString = 'namespace ' + ns + ';';
    }

    this.writeData('NAMESPACE', nsString);
  },


  /**
   * Generates parsing table in PHP arrays format.
   */
  generateParseTable: function generateParseTable() {
    this.writeData('TABLE', this._toPHPArray(this.generateParseTableData()));
  },


  /**
   * Generates tokens table in PHP arrays format.
   */
  generateTokensTable: function generateTokensTable() {
    this.writeData('TOKENS', this._toPHPArray(this._tokens));
  },


  /**
   * Production handlers are implemented as private methods
   * on the `yyparse` class.
   */
  buildSemanticAction: function buildSemanticAction(production) {
    var action = this.getSemanticActionCode(production);

    if (!action) {
      return null;
    }

    action = this._scopeVars(action) + ';';

    var args = this._scopeVars(this.getSemanticActionParams(production).join(','));

    // Save the action, they are injected later.
    this._productionHandlers.push({ args: args, action: action });
    return '\'_handler' + this._productionHandlers.length + '\'';
  },


  /**
   * Generates built-in tokenizer instance.
   */
  generateBuiltInTokenizer: function generateBuiltInTokenizer() {
    this.writeData('TOKENIZER', PHP_TOKENIZER_TEMPLATE.replace(/<\?php/g, ''));
  },


  /**
   * Generates rules for tokenizer.
   */
  generateLexRules: function generateLexRules() {
    var _this = this;

    var lexRules = this._grammar.getLexGrammar().getRules().map(function (lexRule) {
      var action = _this._scopeVars(lexRule.getRawHandler()) + ';';
      _this._lexHandlers.push({ args: '', action: action });

      var flags = [];

      if (lexRule.isCaseInsensitive()) {
        flags.push('i');
      }

      var key = lexRule.getRawMatcher().replace(/\\/g, '\\\\').replace(/'/g, "\\'");
      return '[\'/' + key + '/' + flags.join('') + '\', ' + ('\'_lex_rule' + _this._lexHandlers.length + '\']');
    });

    this.writeData('LEX_RULES', '[' + lexRules.join(',\n') + ']');
  },
  generateLexRulesByStartConditions: function generateLexRulesByStartConditions() {
    var lexGrammar = this._grammar.getLexGrammar();
    var lexRulesByConditions = lexGrammar.getRulesByStartConditions();
    var result = {};

    for (var condition in lexRulesByConditions) {
      result[condition] = lexRulesByConditions[condition].map(function (lexRule) {
        return lexGrammar.getRuleIndex(lexRule);
      });
    }

    this.writeData('LEX_RULES_BY_START_CONDITIONS', '' + this._toPHPArray(result));
  },


  /**
   * Replaces global vars like `yytext`, `$$`, etc. to be
   * referred from `yyparse`.
   */
  _scopeVars: function _scopeVars(code) {
    return code.replace(/\$?yytext/g, 'yyparse::$yytext').replace(/\$?yyleng/g, 'yyparse::$yyleng').replace(/\b__\b/g, 'yyparse::$__').replace(/\b__loc\b/g, 'yyparse::$__loc').replace(/yyloc/g, 'yyparse::yyloc').replace(/\b_(\d+)/g, '$_$1');
  },


  /**
   * Converts JS object to PHP's array representation.
   */
  _toPHPArray: function _toPHPArray(object) {
    if (typeof object !== 'object') {
      return JSON.stringify(object);
    }
    var result = [];
    for (var k in object) {
      var value = object[k];

      if (Array.isArray(object)) {
        result.push(this._toPHPArray(value));
      } else {
        var key = k.replace(/'/g, "\\'");
        result.push("'" + key + "' => " + this._toPHPArray(value));
      }
    }
    return 'array(' + result.join(', ') + ')';
  },


  /**
   * Python-specific lex rules handler declarations.
   */
  generateLexHandlers: function generateLexHandlers() {
    var handlers = this._generateHandlers(this._lexHandlers, '_lex_rule', false);
    this.writeData('LEX_RULE_HANDLERS', handlers.join('\n\n'));
  },


  /**
   * Python-specific handler declarations.
   */
  generateProductionHandlers: function generateProductionHandlers() {
    var handlers = this._generateHandlers(this._productionHandlers, '_handler', true);
    this.writeData('PRODUCTION_HANDLERS', handlers.join('\n\n'));
  },


  /**
   * Generates Python's `def` function declarations for handlers.
   */
  _generateHandlers: function _generateHandlers(handlers, name, isStatic) {
    return handlers.map(function (_ref, index) {
      var args = _ref.args,
          action = _ref.action;

      return 'private ' + (isStatic ? 'static ' : '') + 'function ' + name + (index + 1) + ('(' + args + ') {\n' + action + '\n}');
    });
  }
};

module.exports = PHPParserGeneratorTrait;