text: 'use strict';
  
  const isArray = Array.isArray;
  const Node = require('./ast/Node');
  const Literal = require('./ast/Literal');
  const Identifier = require('./ast/Identifier');
  const HtmlElement = require('./ast/HtmlElement');
  const Html = require('./ast/Html');
  const ok = require('assert').ok;
  const Container = require('./ast/Container');
  const createError = require('raptor-util/createError');
  
  class GeneratorEvent {
      constructor(node, codegen) {
          this.node = node;
          this.codegen = codegen;
  
          this.isBefore = true;
          this.builder = codegen.builder;
          this.context = codegen.context;
  
          this.insertedNodes = null;
      }
  
      insertCode(newCode) {
          if (!this.insertedNodes) {
              this.insertedNodes = [];
          }
          this.insertedNodes = this.insertedNodes.concat(newCode);
      }
  }
  
  class FinalNodes {
      constructor() {
          this.nodes = [];
          this.nodes._finalNode = true; // Mark the array as a collection of final nodes
          this.lastNode = null;
      }
  
      push(node) {
          if (!node) {
              return;
          }
  
          if (node instanceof Html) {
              if (this.lastNode instanceof Html) {
                  this.lastNode.append(node);
                  return;
              }
          }
  
          if (node.setFinalNode) {
              node.setFinalNode(true);
          }
  
          this.lastNode = node;
          this.nodes.push(node);
      }
  }
  
  class CodeGenerator {
      constructor(context, options) {
          options = options || {};
          this.root = null;
  
          this._code = '';
          this.currentIndent = '';
          this.inFunction = false;
  
          this._doneListeners = [];
  
  
          this.builder = context.builder;
  
          this.context = context;
  
          ok(this.builder, '"this.builder" is required');
  
          this._codegenCodeMethodName = 'generate' +
              context.outputType.toUpperCase() +
              'Code';
      }
  
      addVar(name, value) {
          return this.context.addVar(name, value);
      }
  
      addStaticVar(name, value) {
          return this.context.addStaticVar(name, value);
      }
  
      addStaticCode(code) {
          this.context.addStaticCode(code);
      }
  
      addDependency(path, type, options) {
          this.context.addDependency(path, type, options);
      }
  
      pushMeta(key, value, unique) {
          this.context.pushMeta(key, value, unique);
      }
  
      setMeta(key, value) {
          this.context.setMeta(key, value);
      }
  
      getEscapeXmlAttrVar() {
          return this.context.getEscapeXmlAttrVar();
      }
  
      importModule(varName, path) {
          return this.context.importModule(varName, path);
      }
  
      _invokeCodeGenerator(func, node, isMethod) {
          try {
              if (isMethod) {
                  return func.call(node, this);
              } else {
                  return func.call(node, node, this);
              }
          } catch(err) {
              var errorMessage = 'Generating code for ';
  
              if (node instanceof HtmlElement) {
                  errorMessage += '<'+node.tagName+'> tag';
              } else {
                  errorMessage += node.type + ' node';
              }
  
              if (node.pos) {
                  errorMessage += ' ('+this.context.getPosInfo(node.pos)+')';
              }
  
              errorMessage += ' failed. Error: ' + err;
  
              throw createError(errorMessage, err /* cause */);
          }
      }
  
      _generateCode(node, finalNodes) {
          if (isArray(node)) {
              node.forEach((child) => {
                  this._generateCode(child, finalNodes);
              });
              return;
          } else if (node instanceof Container) {
              node.forEach((child) => {
                  if (child.container === node) {
                      this._generateCode(child, finalNodes);
                  }
              });
              return;
          }
  
          if (node == null) {
              return;
          }
  
          if (typeof node === 'string' || node._finalNode || !(node instanceof Node)) {
              finalNodes.push(node);
              return;
          }
  
          if (node._normalizeChildTextNodes) {
              node._normalizeChildTextNodes(this.context);
          }
  
          let oldCurrentNode = this._currentNode;
          this._currentNode = node;
  
          var beforeAfterEvent = new GeneratorEvent(node, this);
  
          var isWhitespacePreserved = node.isPreserveWhitespace();
  
          if (isWhitespacePreserved) {
              this.context.beginPreserveWhitespace();
          }
  
          beforeAfterEvent.isBefore = true;
          beforeAfterEvent.node.emit('beforeGenerateCode', beforeAfterEvent);
          this.context.emit('beforeGenerateCode:' + beforeAfterEvent.node.type, beforeAfterEvent);
          this.context.emit('beforeGenerateCode', beforeAfterEvent);
  
          if (beforeAfterEvent.insertedNodes) {
              this._generateCode(beforeAfterEvent.insertedNodes, finalNodes);
              beforeAfterEvent.insertedNodes = null;
          }
  
          let codeGeneratorFunc;
          let generatedCode;
  
          if (node.getCodeGenerator) {
              codeGeneratorFunc = node.getCodeGenerator(this.outputType);
  
              if (codeGeneratorFunc) {
                  node.setCodeGenerator(null);
  
                  generatedCode = this._invokeCodeGenerator(codeGeneratorFunc, node, false);
  
                  if (generatedCode === null) {
                      node = null;
                  } else if (generatedCode !== undefined && generatedCode !== node) {
                      node = null;
                      this._generateCode(generatedCode, finalNodes);
                  }
              }
          }
  
          if (node != null) {
              codeGeneratorFunc = node.generateCode;
  
              if (!codeGeneratorFunc) {
                  codeGeneratorFunc = node[this._codegenCodeMethodName];
              }
  
              if (codeGeneratorFunc) {
                  generatedCode = this._invokeCodeGenerator(codeGeneratorFunc, node, true);
  
                  if (generatedCode === undefined || generatedCode === node) {
                      finalNodes.push(node);
                  } else if (generatedCode === null) {
                      // If nothing was returned then don't generate any code
                  } else {
                      this._generateCode(generatedCode, finalNodes);
                  }
              } else {
                  finalNodes.push(node);
              }
          }
  
          beforeAfterEvent.isBefore = false;
          beforeAfterEvent.node.emit('afterGenerateCode', beforeAfterEvent);
          this.context.emit('afterGenerateCode:' + beforeAfterEvent.node.type, beforeAfterEvent);
          this.context.emit('afterGenerateCode', beforeAfterEvent);
  
          if (beforeAfterEvent.insertedNodes) {
              this._generateCode(beforeAfterEvent.insertedNodes, finalNodes);
              beforeAfterEvent.insertedNodes = null;
          }
  
          if (isWhitespacePreserved) {
              this.context.endPreserveWhitespace();
          }
  
          this._currentNode = oldCurrentNode;
      }
  
      generateCode(node) {
          if (!node) {
              return null;
          }
  
          if (node._finalNode) {
              return node;
          }
  
          let finalNodes = new FinalNodes();
  
          var isList = typeof node.forEach === 'function';
  
          this._generateCode(node, finalNodes);
  
          finalNodes = finalNodes.nodes;
  
          if (!isList) {
              if (finalNodes.length === 0) {
                  return null;
              } else if (finalNodes.length === 1) {
                  return finalNodes[0];
              }
          }
  
          return finalNodes;
      }
  
      isLiteralNode(node) {
          return node instanceof Literal;
      }
  
      isIdentifierNode(node) {
          return node instanceof Identifier;
      }
  
      isPreserveWhitespaceEnabled() {
          return false;
      }
  
      addError(message, code) {
          ok('"message" is required');
  
          let node = this._currentNode;
  
          if (typeof message === 'object') {
              let errorInfo = message;
              errorInfo.node = node;
              this.context.addError(errorInfo);
          } else {
              this.context.addError({node, message, code});
          }
      }
  
      onDone(listenerFunc) {
          this._doneListeners.push(listenerFunc);
      }
  
      getRequirePath(targetFilename) {
          return this.context.getRequirePath(targetFilename);
      }
  
      resolvePath(pathExpression) {
          return this.context.resolvePath(pathExpression);
      }
  }
  
  module.exports = CodeGenerator;