text: import { isPresent, StringWrapper } from 'angular2/src/facade/lang';
  import { ListWrapper, StringMapWrapper, SetWrapper } from 'angular2/src/facade/collection';
  import * as o from '../output/output_ast';
  import { Identifiers, identifierToken } from '../identifiers';
  import { ViewConstructorVars, InjectMethodVars, DetectChangesVars, ViewTypeEnum, ViewEncapsulationEnum, ChangeDetectionStrategyEnum, ViewProperties } from './constants';
  import { ChangeDetectionStrategy, isDefaultChangeDetectionStrategy } from 'angular2/src/core/change_detection/change_detection';
  import { CompileView } from './compile_view';
  import { CompileElement, CompileNode } from './compile_element';
  import { templateVisitAll } from '../template_ast';
  import { getViewFactoryName, createFlatArray, createDiTokenExpression } from './util';
  import { ViewType } from 'angular2/src/core/linker/view_type';
  import { ViewEncapsulation } from 'angular2/src/core/metadata/view';
  import { CompileIdentifierMetadata } from '../compile_metadata';
  import { bindView } from './view_binder';
  const IMPLICIT_TEMPLATE_VAR = '\$implicit';
  const CLASS_ATTR = 'class';
  const STYLE_ATTR = 'style';
  var parentRenderNodeVar = o.variable('parentRenderNode');
  var rootSelectorVar = o.variable('rootSelector');
  export class ViewCompileDependency {
      constructor(comp, factoryPlaceholder) {
          this.comp = comp;
          this.factoryPlaceholder = factoryPlaceholder;
      }
  }
  export function buildView(view, template, targetDependencies, targetStatements) {
      var builderVisitor = new ViewBuilderVisitor(view, targetDependencies, targetStatements);
      templateVisitAll(builderVisitor, template, view.declarationElement.isNull() ?
          view.declarationElement :
          view.declarationElement.parent);
      // Need to separate binding from creation to be able to refer to
      // variables that have been declared after usage.
      bindView(view, template);
      view.afterNodes();
      createViewTopLevelStmts(view, targetStatements);
      return builderVisitor.nestedViewCount;
  }
  class ViewBuilderVisitor {
      constructor(view, targetDependencies, targetStatements) {
          this.view = view;
          this.targetDependencies = targetDependencies;
          this.targetStatements = targetStatements;
          this.nestedViewCount = 0;
      }
      _isRootNode(parent) { return parent.view !== this.view; }
      _addRootNodeAndProject(node, ngContentIndex, parent) {
          var vcAppEl = (node instanceof CompileElement && node.hasViewContainer) ? node.appElement : null;
          if (this._isRootNode(parent)) {
              // store appElement as root node only for ViewContainers
              if (this.view.viewType !== ViewType.COMPONENT) {
                  this.view.rootNodesOrAppElements.push(isPresent(vcAppEl) ? vcAppEl : node.renderNode);
              }
          }
          else if (isPresent(parent.component) && isPresent(ngContentIndex)) {
              parent.addContentNode(ngContentIndex, isPresent(vcAppEl) ? vcAppEl : node.renderNode);
          }
      }
      _getParentRenderNode(parent) {
          if (this._isRootNode(parent)) {
              if (this.view.viewType === ViewType.COMPONENT) {
                  return parentRenderNodeVar;
              }
              else {
                  // root node of an embedded/host view
                  return o.NULL_EXPR;
              }
          }
          else {
              return isPresent(parent.component) &&
                  parent.component.template.encapsulation !== ViewEncapsulation.Native ?
                  o.NULL_EXPR :
                  parent.renderNode;
          }
      }
      visitBoundText(ast, parent) {
          return this._visitText(ast, '', ast.ngContentIndex, parent);
      }
      visitText(ast, parent) {
          return this._visitText(ast, ast.value, ast.ngContentIndex, parent);
      }
      _visitText(ast, value, ngContentIndex, parent) {
          var fieldName = `_text_${this.view.nodes.length}`;
          this.view.fields.push(new o.ClassField(fieldName, o.importType(this.view.genConfig.renderTypes.renderText), [o.StmtModifier.Private]));
          var renderNode = o.THIS_EXPR.prop(fieldName);
          var compileNode = new CompileNode(parent, this.view, this.view.nodes.length, renderNode, ast);
          var createRenderNode = o.THIS_EXPR.prop(fieldName)
              .set(ViewProperties.renderer.callMethod('createText', [
              this._getParentRenderNode(parent),
              o.literal(value),
              this.view.createMethod.resetDebugInfoExpr(this.view.nodes.length, ast)
          ]))
              .toStmt();
          this.view.nodes.push(compileNode);
          this.view.createMethod.addStmt(createRenderNode);
          this._addRootNodeAndProject(compileNode, ngContentIndex, parent);
          return renderNode;
      }
      visitNgContent(ast, parent) {
          // the projected nodes originate from a different view, so we don't
          // have debug information for them...
          this.view.createMethod.resetDebugInfo(null, ast);
          var parentRenderNode = this._getParentRenderNode(parent);
          var nodesExpression = ViewProperties.projectableNodes.key(o.literal(ast.index), new o.ArrayType(o.importType(this.view.genConfig.renderTypes.renderNode)));
          if (parentRenderNode !== o.NULL_EXPR) {
              this.view.createMethod.addStmt(ViewProperties.renderer.callMethod('projectNodes', [
                  parentRenderNode,
                  o.importExpr(Identifiers.flattenNestedViewRenderNodes)
                      .callFn([nodesExpression])
              ])
                  .toStmt());
          }
          else if (this._isRootNode(parent)) {
              if (this.view.viewType !== ViewType.COMPONENT) {
                  // store root nodes only for embedded/host views
                  this.view.rootNodesOrAppElements.push(nodesExpression);
              }
          }
          else {
              if (isPresent(parent.component) && isPresent(ast.ngContentIndex)) {
                  parent.addContentNode(ast.ngContentIndex, nodesExpression);
              }
          }
          return null;
      }
      visitElement(ast, parent) {
          var nodeIndex = this.view.nodes.length;
          var createRenderNodeExpr;
          var debugContextExpr = this.view.createMethod.resetDebugInfoExpr(nodeIndex, ast);
          if (nodeIndex === 0 && this.view.viewType === ViewType.HOST) {
              createRenderNodeExpr = o.THIS_EXPR.callMethod('selectOrCreateHostElement', [o.literal(ast.name), rootSelectorVar, debugContextExpr]);
          }
          else {
              createRenderNodeExpr = ViewProperties.renderer.callMethod('createElement', [this._getParentRenderNode(parent), o.literal(ast.name), debugContextExpr]);
          }
          var fieldName = `_el_${nodeIndex}`;
          this.view.fields.push(new o.ClassField(fieldName, o.importType(this.view.genConfig.renderTypes.renderElement), [o.StmtModifier.Private]));
          this.view.createMethod.addStmt(o.THIS_EXPR.prop(fieldName).set(createRenderNodeExpr).toStmt());
          var renderNode = o.THIS_EXPR.prop(fieldName);
          var component = ast.getComponent();
          var directives = ast.directives.map(directiveAst => directiveAst.directive);
          var variables = _readHtmlAndDirectiveVariables(ast.exportAsVars, ast.directives, this.view.viewType);
          var htmlAttrs = _readHtmlAttrs(ast.attrs);
          var attrNameAndValues = _mergeHtmlAndDirectiveAttrs(htmlAttrs, directives);
          for (var i = 0; i < attrNameAndValues.length; i++) {
              var attrName = attrNameAndValues[i][0];
              var attrValue = attrNameAndValues[i][1];
              this.view.createMethod.addStmt(ViewProperties.renderer.callMethod('setElementAttribute', [renderNode, o.literal(attrName), o.literal(attrValue)])
                  .toStmt());
          }
          var compileElement = new CompileElement(parent, this.view, nodeIndex, renderNode, ast, component, directives, ast.providers, ast.hasViewContainer, false, variables);
          this.view.nodes.push(compileElement);
          var compViewExpr = null;
          if (isPresent(component)) {
              var nestedComponentIdentifier = new CompileIdentifierMetadata({ name: getViewFactoryName(component, 0) });
              this.targetDependencies.push(new ViewCompileDependency(component, nestedComponentIdentifier));
              compViewExpr = o.variable(`compView_${nodeIndex}`);
              compileElement.setComponentView(compViewExpr);
              this.view.createMethod.addStmt(compViewExpr.set(o.importExpr(nestedComponentIdentifier)
                  .callFn([
                  ViewProperties.viewUtils,
                  compileElement.injector,
                  compileElement.appElement
              ]))
                  .toDeclStmt());
          }
          compileElement.beforeChildren();
          this._addRootNodeAndProject(compileElement, ast.ngContentIndex, parent);
          templateVisitAll(this, ast.children, compileElement);
          compileElement.afterChildren(this.view.nodes.length - nodeIndex - 1);
          if (isPresent(compViewExpr)) {
              var codeGenContentNodes;
              if (this.view.component.type.isHost) {
                  codeGenContentNodes = ViewProperties.projectableNodes;
              }
              else {
                  codeGenContentNodes = o.literalArr(compileElement.contentNodesByNgContentIndex.map(nodes => createFlatArray(nodes)));
              }
              this.view.createMethod.addStmt(compViewExpr.callMethod('create', [codeGenContentNodes, o.NULL_EXPR]).toStmt());
          }
          return null;
      }
      visitEmbeddedTemplate(ast, parent) {
          var nodeIndex = this.view.nodes.length;
          var fieldName = `_anchor_${nodeIndex}`;
          this.view.fields.push(new o.ClassField(fieldName, o.importType(this.view.genConfig.renderTypes.renderComment), [o.StmtModifier.Private]));
          this.view.createMethod.addStmt(o.THIS_EXPR.prop(fieldName)
              .set(ViewProperties.renderer.callMethod('createTemplateAnchor', [
              this._getParentRenderNode(parent),
              this.view.createMethod.resetDebugInfoExpr(nodeIndex, ast)
          ]))
              .toStmt());
          var renderNode = o.THIS_EXPR.prop(fieldName);
          var templateVariableBindings = ast.vars.map(varAst => [varAst.value.length > 0 ? varAst.value : IMPLICIT_TEMPLATE_VAR, varAst.name]);
          var directives = ast.directives.map(directiveAst => directiveAst.directive);
          var compileElement = new CompileElement(parent, this.view, nodeIndex, renderNode, ast, null, directives, ast.providers, ast.hasViewContainer, true, {});
          this.view.nodes.push(compileElement);
          this.nestedViewCount++;
          var embeddedView = new CompileView(this.view.component, this.view.genConfig, this.view.pipeMetas, o.NULL_EXPR, this.view.viewIndex + this.nestedViewCount, compileElement, templateVariableBindings);
          this.nestedViewCount +=
              buildView(embeddedView, ast.children, this.targetDependencies, this.targetStatements);
          compileElement.beforeChildren();
          this._addRootNodeAndProject(compileElement, ast.ngContentIndex, parent);
          compileElement.afterChildren(0);
          return null;
      }
      visitAttr(ast, ctx) { return null; }
      visitDirective(ast, ctx) { return null; }
      visitEvent(ast, eventTargetAndNames) {
          return null;
      }
      visitVariable(ast, ctx) { return null; }
      visitDirectiveProperty(ast, context) { return null; }
      visitElementProperty(ast, context) { return null; }
  }
  function _mergeHtmlAndDirectiveAttrs(declaredHtmlAttrs, directives) {
      var result = {};
      StringMapWrapper.forEach(declaredHtmlAttrs, (value, key) => { result[key] = value; });
      directives.forEach(directiveMeta => {
          StringMapWrapper.forEach(directiveMeta.hostAttributes, (value, name) => {
              var prevValue = result[name];
              result[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value;
          });
      });
      return mapToKeyValueArray(result);
  }
  function _readHtmlAttrs(attrs) {
      var htmlAttrs = {};
      attrs.forEach((ast) => { htmlAttrs[ast.name] = ast.value; });
      return htmlAttrs;
  }
  function _readHtmlAndDirectiveVariables(elementExportAsVars, directives, viewType) {
      var variables = {};
      var component = null;
      directives.forEach((directive) => {
          if (directive.directive.isComponent) {
              component = directive.directive;
          }
          directive.exportAsVars.forEach(varAst => { variables[varAst.name] = identifierToken(directive.directive.type); });
      });
      elementExportAsVars.forEach((varAst) => {
          variables[varAst.name] = isPresent(component) ? identifierToken(component.type) : null;
      });
      return variables;
  }
  function mergeAttributeValue(attrName, attrValue1, attrValue2) {
      if (attrName == CLASS_ATTR || attrName == STYLE_ATTR) {
          return `${attrValue1} ${attrValue2}`;
      }
      else {
          return attrValue2;
      }
  }
  function mapToKeyValueArray(data) {
      var entryArray = [];
      StringMapWrapper.forEach(data, (value, name) => { entryArray.push([name, value]); });
      // We need to sort to get a defined output order
      // for tests and for caching generated artifacts...
      ListWrapper.sort(entryArray, (entry1, entry2) => StringWrapper.compare(entry1[0], entry2[0]));
      var keyValueArray = [];
      entryArray.forEach((entry) => { keyValueArray.push([entry[0], entry[1]]); });
      return keyValueArray;
  }
  function createViewTopLevelStmts(view, targetStatements) {
      var nodeDebugInfosVar = o.NULL_EXPR;
      if (view.genConfig.genDebugInfo) {
          nodeDebugInfosVar = o.variable(`nodeDebugInfos_${view.component.type.name}${view.viewIndex}`);
          targetStatements.push(nodeDebugInfosVar
              .set(o.literalArr(view.nodes.map(createStaticNodeDebugInfo), new o.ArrayType(new o.ExternalType(Identifiers.StaticNodeDebugInfo), [o.TypeModifier.Const])))
              .toDeclStmt(null, [o.StmtModifier.Final]));
      }
      var renderCompTypeVar = o.variable(`renderType_${view.component.type.name}`);
      if (view.viewIndex === 0) {
          targetStatements.push(renderCompTypeVar.set(o.NULL_EXPR)
              .toDeclStmt(o.importType(Identifiers.RenderComponentType)));
      }
      var viewClass = createViewClass(view, renderCompTypeVar, nodeDebugInfosVar);
      targetStatements.push(viewClass);
      targetStatements.push(createViewFactory(view, viewClass, renderCompTypeVar));
  }
  function createStaticNodeDebugInfo(node) {
      var compileElement = node instanceof CompileElement ? node : null;
      var providerTokens = [];
      var componentToken = o.NULL_EXPR;
      var varTokenEntries = [];
      if (isPresent(compileElement)) {
          providerTokens = compileElement.getProviderTokens();
          if (isPresent(compileElement.component)) {
              componentToken = createDiTokenExpression(identifierToken(compileElement.component.type));
          }
          StringMapWrapper.forEach(compileElement.variableTokens, (token, varName) => {
              varTokenEntries.push([varName, isPresent(token) ? createDiTokenExpression(token) : o.NULL_EXPR]);
          });
      }
      return o.importExpr(Identifiers.StaticNodeDebugInfo)
          .instantiate([
          o.literalArr(providerTokens, new o.ArrayType(o.DYNAMIC_TYPE, [o.TypeModifier.Const])),
          componentToken,
          o.literalMap(varTokenEntries, new o.MapType(o.DYNAMIC_TYPE, [o.TypeModifier.Const]))
      ], o.importType(Identifiers.StaticNodeDebugInfo, null, [o.TypeModifier.Const]));
  }
  function createViewClass(view, renderCompTypeVar, nodeDebugInfosVar) {
      var emptyTemplateVariableBindings = view.templateVariableBindings.map((entry) => [entry[0], o.NULL_EXPR]);
      var viewConstructorArgs = [
          new o.FnParam(ViewConstructorVars.viewUtils.name, o.importType(Identifiers.ViewUtils)),
          new o.FnParam(ViewConstructorVars.parentInjector.name, o.importType(Identifiers.Injector)),
          new o.FnParam(ViewConstructorVars.declarationEl.name, o.importType(Identifiers.AppElement))
      ];
      var viewConstructor = new o.ClassMethod(null, viewConstructorArgs, [
          o.SUPER_EXPR.callFn([
              o.variable(view.className),
              renderCompTypeVar,
              ViewTypeEnum.fromValue(view.viewType),
              o.literalMap(emptyTemplateVariableBindings),
              ViewConstructorVars.viewUtils,
              ViewConstructorVars.parentInjector,
              ViewConstructorVars.declarationEl,
              ChangeDetectionStrategyEnum.fromValue(getChangeDetectionMode(view)),
              nodeDebugInfosVar
          ])
              .toStmt()
      ]);
      var viewMethods = [
          new o.ClassMethod('createInternal', [new o.FnParam(rootSelectorVar.name, o.STRING_TYPE)], generateCreateMethod(view), o.importType(Identifiers.AppElement)),
          new o.ClassMethod('injectorGetInternal', [
              new o.FnParam(InjectMethodVars.token.name, o.DYNAMIC_TYPE),
              // Note: Can't use o.INT_TYPE here as the method in AppView uses number
              new o.FnParam(InjectMethodVars.requestNodeIndex.name, o.NUMBER_TYPE),
              new o.FnParam(InjectMethodVars.notFoundResult.name, o.DYNAMIC_TYPE)
          ], addReturnValuefNotEmpty(view.injectorGetMethod.finish(), InjectMethodVars.notFoundResult), o.DYNAMIC_TYPE),
          new o.ClassMethod('detectChangesInternal', [new o.FnParam(DetectChangesVars.throwOnChange.name, o.BOOL_TYPE)], generateDetectChangesMethod(view)),
          new o.ClassMethod('dirtyParentQueriesInternal', [], view.dirtyParentQueriesMethod.finish()),
          new o.ClassMethod('destroyInternal', [], view.destroyMethod.finish())
      ].concat(view.eventHandlerMethods);
      var viewClass = new o.ClassStmt(view.className, o.importExpr(Identifiers.AppView, [getContextType(view)]), view.fields, view.getters, viewConstructor, viewMethods.filter((method) => method.body.length > 0));
      return viewClass;
  }
  function createViewFactory(view, viewClass, renderCompTypeVar) {
      var viewFactoryArgs = [
          new o.FnParam(ViewConstructorVars.viewUtils.name, o.importType(Identifiers.ViewUtils)),
          new o.FnParam(ViewConstructorVars.parentInjector.name, o.importType(Identifiers.Injector)),
          new o.FnParam(ViewConstructorVars.declarationEl.name, o.importType(Identifiers.AppElement))
      ];
      var initRenderCompTypeStmts = [];
      var templateUrlInfo;
      if (view.component.template.templateUrl == view.component.type.moduleUrl) {
          templateUrlInfo =
              `${view.component.type.moduleUrl} class ${view.component.type.name} - inline template`;
      }
      else {
          templateUrlInfo = view.component.template.templateUrl;
      }
      if (view.viewIndex === 0) {
          initRenderCompTypeStmts = [
              new o.IfStmt(renderCompTypeVar.identical(o.NULL_EXPR), [
                  renderCompTypeVar.set(ViewConstructorVars
                      .viewUtils.callMethod('createRenderComponentType', [
                      o.literal(templateUrlInfo),
                      o.literal(view.component
                          .template.ngContentSelectors.length),
                      ViewEncapsulationEnum
                          .fromValue(view.component.template.encapsulation),
                      view.styles
                  ]))
                      .toStmt()
              ])
          ];
      }
      return o.fn(viewFactoryArgs, initRenderCompTypeStmts.concat([
          new o.ReturnStatement(o.variable(viewClass.name)
              .instantiate(viewClass.constructorMethod.params.map((param) => o.variable(param.name))))
      ]), o.importType(Identifiers.AppView, [getContextType(view)]))
          .toDeclStmt(view.viewFactory.name, [o.StmtModifier.Final]);
  }
  function generateCreateMethod(view) {
      var parentRenderNodeExpr = o.NULL_EXPR;
      var parentRenderNodeStmts = [];
      if (view.viewType === ViewType.COMPONENT) {
          parentRenderNodeExpr = ViewProperties.renderer.callMethod('createViewRoot', [o.THIS_EXPR.prop('declarationAppElement').prop('nativeElement')]);
          parentRenderNodeStmts = [
              parentRenderNodeVar.set(parentRenderNodeExpr)
                  .toDeclStmt(o.importType(view.genConfig.renderTypes.renderNode), [o.StmtModifier.Final])
          ];
      }
      var resultExpr;
      if (view.viewType === ViewType.HOST) {
          resultExpr = view.nodes[0].appElement;
      }
      else {
          resultExpr = o.NULL_EXPR;
      }
      return parentRenderNodeStmts.concat(view.createMethod.finish())
          .concat([
          o.THIS_EXPR.callMethod('init', [
              createFlatArray(view.rootNodesOrAppElements),
              o.literalArr(view.nodes.map(node => node.renderNode)),
              o.literalArr(view.disposables),
              o.literalArr(view.subscriptions)
          ])
              .toStmt(),
          new o.ReturnStatement(resultExpr)
      ]);
  }
  function generateDetectChangesMethod(view) {
      var stmts = [];
      if (view.detectChangesInInputsMethod.isEmpty() && view.updateContentQueriesMethod.isEmpty() &&
          view.afterContentLifecycleCallbacksMethod.isEmpty() &&
          view.detectChangesRenderPropertiesMethod.isEmpty() &&
          view.updateViewQueriesMethod.isEmpty() && view.afterViewLifecycleCallbacksMethod.isEmpty()) {
          return stmts;
      }
      ListWrapper.addAll(stmts, view.detectChangesInInputsMethod.finish());
      stmts.push(o.THIS_EXPR.callMethod('detectContentChildrenChanges', [DetectChangesVars.throwOnChange])
          .toStmt());
      var afterContentStmts = view.updateContentQueriesMethod.finish().concat(view.afterContentLifecycleCallbacksMethod.finish());
      if (afterContentStmts.length > 0) {
          stmts.push(new o.IfStmt(o.not(DetectChangesVars.throwOnChange), afterContentStmts));
      }
      ListWrapper.addAll(stmts, view.detectChangesRenderPropertiesMethod.finish());
      stmts.push(o.THIS_EXPR.callMethod('detectViewChildrenChanges', [DetectChangesVars.throwOnChange])
          .toStmt());
      var afterViewStmts = view.updateViewQueriesMethod.finish().concat(view.afterViewLifecycleCallbacksMethod.finish());
      if (afterViewStmts.length > 0) {
          stmts.push(new o.IfStmt(o.not(DetectChangesVars.throwOnChange), afterViewStmts));
      }
      var varStmts = [];
      var readVars = o.findReadVarNames(stmts);
      if (SetWrapper.has(readVars, DetectChangesVars.changed.name)) {
          varStmts.push(DetectChangesVars.changed.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE));
      }
      if (SetWrapper.has(readVars, DetectChangesVars.changes.name)) {
          varStmts.push(DetectChangesVars.changes.set(o.NULL_EXPR)
              .toDeclStmt(new o.MapType(o.importType(Identifiers.SimpleChange))));
      }
      if (SetWrapper.has(readVars, DetectChangesVars.valUnwrapper.name)) {
          varStmts.push(DetectChangesVars.valUnwrapper.set(o.importExpr(Identifiers.ValueUnwrapper).instantiate([]))
              .toDeclStmt(null, [o.StmtModifier.Final]));
      }
      return varStmts.concat(stmts);
  }
  function addReturnValuefNotEmpty(statements, value) {
      if (statements.length > 0) {
          return statements.concat([new o.ReturnStatement(value)]);
      }
      else {
          return statements;
      }
  }
  function getContextType(view) {
      var typeMeta = view.component.type;
      return typeMeta.isHost ? o.DYNAMIC_TYPE : o.importType(typeMeta);
  }
  function getChangeDetectionMode(view) {
      var mode;
      if (view.viewType === ViewType.COMPONENT) {
          mode = isDefaultChangeDetectionStrategy(view.component.changeDetection) ?
              ChangeDetectionStrategy.CheckAlways :
              ChangeDetectionStrategy.CheckOnce;
      }
      else {
          mode = ChangeDetectionStrategy.CheckAlways;
      }
      return mode;
  }
  