/**********************************************************************
 * Copyright (c) 2014 HubSpot Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **********************************************************************/
package com.hubspot.jinjava.tree;

import static com.hubspot.jinjava.tree.parse.TokenScannerSymbols.TOKEN_EXPR_START;
import static com.hubspot.jinjava.tree.parse.TokenScannerSymbols.TOKEN_FIXED;
import static com.hubspot.jinjava.tree.parse.TokenScannerSymbols.TOKEN_NOTE;
import static com.hubspot.jinjava.tree.parse.TokenScannerSymbols.TOKEN_TAG;

import java.util.LinkedList;

import org.apache.commons.lang3.StringUtils;

import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
import com.hubspot.jinjava.interpret.MissingEndTagException;
import com.hubspot.jinjava.interpret.TemplateError;
import com.hubspot.jinjava.interpret.TemplateSyntaxException;
import com.hubspot.jinjava.interpret.UnexpectedTokenException;
import com.hubspot.jinjava.interpret.UnknownTagException;
import com.hubspot.jinjava.lib.tag.EndTag;
import com.hubspot.jinjava.lib.tag.Tag;
import com.hubspot.jinjava.tree.parse.ExpressionToken;
import com.hubspot.jinjava.tree.parse.TagToken;
import com.hubspot.jinjava.tree.parse.TextToken;
import com.hubspot.jinjava.tree.parse.Token;
import com.hubspot.jinjava.tree.parse.TokenScanner;

public class TreeParser {

  private final PeekingIterator<Token> scanner;
  private final JinjavaInterpreter interpreter;

  private Node parent;

  public TreeParser(JinjavaInterpreter interpreter, String input) {
    this.scanner = Iterators.peekingIterator(new TokenScanner(input, interpreter.getConfig()));
    this.interpreter = interpreter;
  }

  public Node buildTree() {
    Node root = new RootNode();

    parent = root;

    while (scanner.hasNext()) {
      Node node = nextNode();

      if (node != null) {
        parent.getChildren().add(node);
      }
    }

    if (parent != root) {
      interpreter.addError(TemplateError.fromException(
          new MissingEndTagException(((TagNode) parent).getEndName(),
                                     parent.getMaster().getImage(),
                                     parent.getLineNumber())));
    }

    return root;
  }

  /**
   * @return null if EOF or error
   */

  private Node nextNode() {
    Token token = scanner.next();

    switch (token.getType()) {
      case TOKEN_FIXED:
        return text((TextToken) token);

      case TOKEN_EXPR_START:
        return expression((ExpressionToken) token);

      case TOKEN_TAG:
        return tag((TagToken) token);

      case TOKEN_NOTE:
        break;

      default:
        interpreter.addError(TemplateError.fromException(new UnexpectedTokenException(token.getImage(),
                                                                                      token.getLineNumber())));
    }

    return null;
  }

  private Node getLastSibling() {

    if (parent == null || parent.getChildren().isEmpty()) {
      return null;
    }

    return parent.getChildren().getLast();
  }

  private Node text(TextToken textToken) {
    if (interpreter.getConfig().isLstripBlocks()) {
      if (scanner.hasNext() && scanner.peek().getType() == TOKEN_TAG) {
        textToken = new TextToken(StringUtils.stripEnd(textToken.getImage(), "\t "), textToken.getLineNumber());
      }
    }

    final Node lastSibling = getLastSibling();

    // if last sibling was a tag and has rightTrimAfterEnd, strip whitespace
    if (lastSibling != null
        && lastSibling instanceof TagNode
        && lastSibling.getMaster().isRightTrimAfterEnd()) {
      textToken.setLeftTrim(true);
    }

    // for first TextNode child of TagNode where rightTrim is enabled, mark it for left trim
    if (parent instanceof TagNode
        && lastSibling == null
        && parent.getMaster().isRightTrim()) {
      textToken.setLeftTrim(true);
    }

    TextNode n = new TextNode(textToken);
    n.setParent(parent);
    return n;
  }

  private Node expression(ExpressionToken expressionToken) {
    ExpressionNode n = new ExpressionNode(expressionToken);
    n.setParent(parent);
    return n;
  }

  private Node tag(TagToken tagToken) {
    Tag tag = interpreter.getContext().getTag(tagToken.getTagName());
    if (tag == null) {
      interpreter.addError(TemplateError.fromException(new UnknownTagException(tagToken)));
      return null;
    }

    if (tag instanceof EndTag) {
      endTag(tag, tagToken);
      return null;
    } else {

      // if a tag has left trim, mark the last sibling to trim right whitespace
      if (tagToken.isLeftTrim()) {
        final Node lastSibling = getLastSibling();
        if (lastSibling != null && lastSibling instanceof TextNode) {
          lastSibling.getMaster().setRightTrim(true);
        }
      }
    }

    TagNode node = new TagNode(tag, tagToken);
    node.setParent(parent);

    if (node.getEndName() != null) {
      parent.getChildren().add(node);
      parent = node;
      return null;
    }

    return node;
  }

  private void endTag(Tag tag, TagToken tagToken) {

    final LinkedList<Node> children = parent.getChildren();
    final Node lastChild = children.isEmpty() ? null : children.get(children.size() - 1);

    if (parent instanceof TagNode
        && tagToken.isLeftTrim()
        && lastChild != null
        && lastChild instanceof TextNode) {
      lastChild.getMaster().setRightTrim(true);
    }

    parent.getMaster().setRightTrimAfterEnd(tagToken.isRightTrim());

    while (!(parent instanceof RootNode)) {
      TagNode parentTag = (TagNode) parent;
      parent = parent.getParent();

      if (parentTag.getEndName().equals(tag.getEndTagName())) {
        break;
      } else {
        interpreter.addError(TemplateError.fromException(
            new TemplateSyntaxException(tagToken.getImage(),
                                        "Mismatched end tag, expected: " + parentTag.getEndName(),
                                        tagToken.getLineNumber())));
      }
    }
  }

}

@@ -1081,38 +1081,8 @@
 G;%0A%0A
-import java.util.LinkedList;%0A%0A
 impo
@@ -5649,145 +5649,42 @@
 nal 
-LinkedList%3CNode%3E children = parent.getChildren();%0A    final Node lastChild = children.isEmpty() ? null : children.get(children.size() - 1
+Node lastSibling = getLastSibling(
 );%0A%0A
@@ -5765,21 +5765,23 @@
  && last
-Child
+Sibling
  != null
@@ -5796,21 +5796,23 @@
  && last
-Child
+Sibling
  instanc
@@ -5837,21 +5837,23 @@
     last
-Child
+Sibling
 .getMast

use getLastSibling