'use strict';

var _fs = require('fs');

var _fs2 = _interopRequireDefault(_fs);

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

/**
 * C# tokenizer template.
 */
var CSHARP_TOKENIZER_TEMPLATE = _fs2.default.readFileSync(__dirname + '/templates/tokenizer.template.cs', 'utf-8');

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

var CSharpParserGeneratorTrait = {

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


  /**
   * Generates parsing table in C# Dictionary format.
   */
  generateParseTable: function generateParseTable() {
    this.writeData('TABLE', this._buildTable(this.generateParseTableData()));
  },


  /**
   * Converts JS object into C# Dictionary.
   *
   * In C# we represent a table as an array, where index is a state number,
   * and a value is a record of LR entries (shift/reduce/etc).
   *
   * Example:
   * {
   *     new Dictionary<int, string>()
   *     {
   *         {0, "1"},
   *         {3, "s8"},
   *         {4, "s2"},
   *     },
   * }
   */
  _buildTable: function _buildTable(table) {
    var _this = this;

    var entries = [];
    Object.keys(table).forEach(function (state) {
      entries.push('new Dictionary<int, string>() ' + _this._toCSharpDictionary(table[state], 'number', 'string'));
    });
    return '{ ' + entries.join(',\n\n') + ' }';
  },


  /**
   * Generates tokens table in C# Dictionary format.
   */
  generateTokensTable: function generateTokensTable() {
    this.writeData('TOKENS', this._toCSharpDictionary(this._tokens, 'string', 'number'));
  },


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

    if (!action) {
      return null;
    }

    action = this._actionFromHandler(action);

    var args = this.getSemanticActionParams(production)
    // Append type information for C#.
    .map(function (arg) {
      return 'dynamic ' + arg;
    }).join(',');

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


  /**
   * Default format in the [ ] array notation.
   */
  generateProductionsData: function generateProductionsData() {
    return this.generateRawProductionsData().map(function (data) {
      return 'new object[] { ' + data + ' }';
    });
  },


  /**
   * Generates built-in tokenizer instance.
   */
  generateBuiltInTokenizer: function generateBuiltInTokenizer() {
    this.writeData('TOKENIZER', CSHARP_TOKENIZER_TEMPLATE);
  },


  /**
   * Creates an action from raw handler.
   */
  _actionFromHandler: function _actionFromHandler(handler) {
    var action = (this._scopeVars(handler) || '').trim();

    if (!action) {
      return 'return null;';
    }

    if (!/;\s*$/.test(action)) {
      action += ';';
    }

    return action;
  },


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

    var lexRules = this._grammar.getLexGrammar().getRules().map(function (lexRule) {
      var action = _this2._actionFromHandler(lexRule.getRawHandler());

      _this2._lexHandlers.push({ args: '', action: action });

      var flags = [];

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

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

      // Example: new string[] {@"^\s+", "_lexRule1"},
      return 'new string[] { @"' + flags + lexRule.getRawMatcher() + '", ' + ('"_lexRule' + _this2._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._toCSharpDictionary(result, 'string'));
  },


  /**
   * 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(/yyloc/g, 'YyLoc.yyloc');
  },


  /**
   * Type-converts a key of a C# dictionary, e.g. string or number, etc.
   */
  _dictKey: function _dictKey(key, keyType) {
    switch (keyType) {
      case 'string':
        return '"' + key + '"';
      case 'number':
        return Number(key);
      default:
        throw new Error('_dictKey: Incorrect type ' + keyType);
    }
  },


  /**
   * Type-converts a value of a C# dictionary, e.g. string or number, etc.
   */
  _dictValue: function _dictValue(value, valueType) {
    if (Array.isArray(value)) {
      // Support only int arrays here for simplicity.
      return 'new int[] { ' + value.join(', ') + ' }';
    }

    switch (valueType) {
      case 'string':
        return '"' + value + '"';
      case 'number':
        return Number(value);
      default:
        throw new Error('_dictValue: Incorrect value ' + valueType);
    }
  },


  /**
   * Converts JS object to C#'s Dictionary representation.
   *
   * {x: 10, y: 20} -> {{"x", 10}, {"y", 20}}
   *
   * The `keyTransform`, and `valueTransform` are used to put
   * the data in needed types, e.g. strings, numbers, arrays, etc.
   */
  _toCSharpDictionary: function _toCSharpDictionary(object, keyType, valueType) {
    var result = [];
    for (var k in object) {
      var value = object[k];
      var key = k.replace(/"/g, '\\"');
      result.push('{ ' + this._dictKey(key, keyType) + ', ' + (this._dictValue(value, valueType) + ' }'));
    }
    return '{ ' + result.join(', ') + ' }';
  },


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


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


  /**
   * Productions array in C# format.
   */
  generateProductions: function generateProductions() {
    this.writeData('PRODUCTIONS', '{ ' + this.generateProductionsData().join(',\n') + ' }');
  },


  /**
   * Injects the code passed in the module include directive.
   * Default is class Init { public void run() { ... }}
   * which is used to setup parsing hooks, etc.
   */
  generateModuleInclude: function generateModuleInclude() {
    var moduleInclude = this._grammar.getModuleInclude();

    var defaultModuleInclude = '\n      namespace SyntaxParser\n      {\n          public class Init\n          {\n              public static void run()\n              {\n                  // Put init code here.\n                  // E.g. yyparse.onParseBegin = (string code) => { ... };\n              }\n          }\n      }\n    ';

    if (!moduleInclude) {
      moduleInclude = defaultModuleInclude;
    } else if (!/class Init\s+{/.test(moduleInclude)) {
      moduleInclude += defaultModuleInclude;
    }

    this.writeData('MODULE_INCLUDE', moduleInclude);
  },


  /**
   * Generates C#'s void function declarations for handlers.
   *
   * Example:
   *
   * public void _handler1(dynamic _1, dynamic _2)
   * {
   *     __ = _1 + _2;
   * }
   */
  _generateHandlers: function _generateHandlers(handlers, name, returnType) {
    return handlers.map(function (_ref, index) {
      var args = _ref.args,
          action = _ref.action;

      return 'public ' + returnType + ' ' + name + (index + 1) + ('(' + args + ') {\n' + action + '\n}');
    });
  }
};

module.exports = CSharpParserGeneratorTrait;