'use strict';

var _codeUnit = require('../../code-unit');

var _codeUnit2 = _interopRequireDefault(_codeUnit);

var _fs = require('fs');

var _fs2 = _interopRequireDefault(_fs);

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

/**
 * Python tokenizer template.
 */
/**
 * The MIT License (MIT)
 * Copyright (c) 2015-present Dmitry Soshnikov <dmitry.soshnikov@gmail.com>
 */

var PY_TOKENIZER_TEMPLATE = _fs2.default.readFileSync(__dirname + '/templates/tokenizer.template.py', 'utf-8');

/**
 * Standard Python's indentation.
 */
var STANDARD_SPACES = ' '.repeat(4);

/**
 * The trait is used by parser generators (LL/LR) for Python.
 */
var PythonParserGeneratorTrait = {

  /**
   * Module include code.
   */
  generateModuleInclude: function generateModuleInclude() {
    this.writeData('MODULE_INCLUDE', this._formatIndent(this._grammar.getModuleInclude(), /* no ident */''));
  },


  /**
   * Whether locations should be captured, and propagated.
   */
  generateCaptureLocations: function generateCaptureLocations() {
    this.writeData('CAPTURE_LOCATIONS', this._grammar.shouldCaptureLocations() ? 'True' : 'False');
  },


  /**
   * Creates handler prologue for locations. Use default implementation
   * from CodeUnit, plugins may implement custom logic.
   */
  createLocationPrologue: function createLocationPrologue(production) {
    if (production.isEpsilon()) {
      return '__loc = None;';
    }
    return _codeUnit2.default.createLocationPrologue(production);
  },


  /**
   * Since Python's lambdas are one-liners, we use normal `def`
   * functiona declarations, and put a reference it them in the table.
   */
  buildSemanticAction: function buildSemanticAction(production) {
    var action = this.getSemanticActionCode(production);

    if (!action) {
      return null;
    }

    var args = 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', PY_TOKENIZER_TEMPLATE);
  },


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

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

      var flags = [];

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

      if (flags.length > 0) {
        flags = '(?' + flags.join('') + ')';
      } else {
        flags = '';
      }

      return '[\'' + flags + lexRule.getRawMatcher() + '\', ' + ('_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', '' + JSON.stringify(result));
  },


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


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


  /**
   * Generates Python's `def` function declarations for handlers.
   */
  _generateHandlers: function _generateHandlers(handlers, name) {
    var _this2 = this;

    return handlers.map(function (_ref, index) {
      var args = _ref.args,
          action = _ref.action;

      var formatted = _this2._formatIndent(action);
      return 'def ' + name + (index + 1) + '(' + args + '):\n' + (STANDARD_SPACES + 'global __, __loc, yytext, yyleng\n') + formatted;
    });
  },


  /**
   * Formats Python's indentation.
   */
  _formatIndent: function _formatIndent(code) {
    var indent = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : STANDARD_SPACES;

    var lines = code.split('\n');
    var firstNonEmptyLine = void 0;

    var _iteratorNormalCompletion = true;
    var _didIteratorError = false;
    var _iteratorError = undefined;

    try {
      for (var _iterator = lines[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
        var line = _step.value;

        if (line.trim() !== '') {
          firstNonEmptyLine = line;
          break;
        }
      }

      // First line defines indentation.
    } catch (err) {
      _didIteratorError = true;
      _iteratorError = err;
    } finally {
      try {
        if (!_iteratorNormalCompletion && _iterator.return) {
          _iterator.return();
        }
      } finally {
        if (_didIteratorError) {
          throw _iteratorError;
        }
      }
    }

    var spaceMatch = (firstNonEmptyLine || '').match(/^\s+/);
    var spacesCount = spaceMatch ? spaceMatch[0].length : 0;

    var formatted = lines.map(function (line) {
      return indent + line.substring(spacesCount);
    });

    if (formatted.length === 0) {
      formatted.push(STANDARD_SPACES + 'pass');
    }

    return formatted.join('\n');
  }
};

module.exports = PythonParserGeneratorTrait;