"use strict";

// Simple AST node visitor builder.
const visitor = {
  build(functions) {
    function visit(node, ...args) {
      return functions[node.type](node, ...args);
    }

    function visitNop() {
      // Do nothing.
    }

    function visitExpression(node, ...args) {
      return visit(node.expression, ...args);
    }

    function visitChildren(property) {
      return function(node, ...args) {
        // We do not use .map() here, because if you need the result
        // of applying visitor to children you probable also need to
        // process it in some way, therefore you anyway have to override
        // this method. If you do not needed that, we do not waste time
        // and memory for creating the output array
        node[property].forEach(child => visit(child, ...args));
      };
    }

    const DEFAULT_FUNCTIONS = {
      grammar(node, ...args) {
        for (const imp of node.imports) {
          visit(imp, ...args);
        }

        if (node.topLevelInitializer) {
          if (Array.isArray(node.topLevelInitializer)) {
            for (const tli of node.topLevelInitializer) {
              visit(tli, ...args);
            }
          } else {
            visit(node.topLevelInitializer, ...args);
          }
        }

        if (node.initializer) {
          if (Array.isArray(node.initializer)) {
            for (const init of node.initializer) {
              visit(init, ...args);
            }
          } else {
            visit(node.initializer, ...args);
          }
        }

        node.rules.forEach(rule => visit(rule, ...args));
      },

      grammar_import: visitNop,
      top_level_initializer: visitNop,
      initializer: visitNop,
      rule: visitExpression,
      named: visitExpression,
      choice: visitChildren("alternatives"),
      action: visitExpression,
      sequence: visitChildren("elements"),
      labeled: visitExpression,
      text: visitExpression,
      simple_and: visitExpression,
      simple_not: visitExpression,
      optional: visitExpression,
      zero_or_more: visitExpression,
      one_or_more: visitExpression,
      repeated(node, ...args) {
        if (node.delimiter) {
          visit(node.delimiter, ...args);
        }

        return visit(node.expression, ...args);
      },
      group: visitExpression,
      semantic_and: visitNop,
      semantic_not: visitNop,
      rule_ref: visitNop,
      library_ref: visitNop,
      literal: visitNop,
      class: visitNop,
      any: visitNop,
    };

    Object.keys(DEFAULT_FUNCTIONS).forEach(type => {
      if (!Object.prototype.hasOwnProperty.call(functions, type)) {
        functions[type] = DEFAULT_FUNCTIONS[type];
      }
    });

    return visit;
  },
};

module.exports = visitor;
