package com.linkedin.intellij.dust;

import com.intellij.formatting.*;
import com.intellij.formatting.templateLanguages.BlockWithParent;
import com.intellij.formatting.templateLanguages.DataLanguageBlockWrapper;
import com.intellij.formatting.templateLanguages.TemplateLanguageBlock;
import com.intellij.formatting.templateLanguages.TemplateLanguageBlockFactory;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.formatter.xml.SyntheticBlock;
import com.intellij.psi.tree.IElementType;
import com.linkedin.intellij.dust.psi.DustElseTag;
import com.linkedin.intellij.dust.psi.DustPsiUtil;
import com.linkedin.intellij.dust.psi.DustTypes;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * User: yzhang
 * Date: 3/11/13
 * Time: 10:19 AM
 *
 * Based on the intellij mustache plugin
 */
public class DustFormatterBlock extends TemplateLanguageBlock {
  DustFormatterBlock(@NotNull TemplateLanguageBlockFactory blockFactory, @NotNull CodeStyleSettings settings,
                  @NotNull ASTNode node, @Nullable List<DataLanguageBlockWrapper> foreignChildren) {
    super(blockFactory, settings, node, foreignChildren);
  }

  @Override
  public Indent getIndent() {
    // ignore whitespace
    if (myNode.getText().trim().length() == 0) {
      return Indent.getNoneIndent();
    }

    if (DustPsiUtil.isNonRootStatementsElement(myNode.getPsi())) {
      // we're computing the indent for a non-root STATEMENTS:
      //      if it's not contained in a foreign block, indent!
      if (hasOnlyDustLanguageParents()) {
        return Indent.getNormalIndent();
      }
    }

    if (myNode.getTreeParent() != null
        && DustPsiUtil.isNonRootStatementsElement(myNode.getTreeParent().getPsi())) {
      // we're computing the indent for a direct descendant of a non-root STATEMENTS:
      //      if its Block parent (i.e. not DUST AST Tree parent) is a DustForamtterBlock
      //      which has NOT been indented, then have the element provide the indent itself
      if (getParent() instanceof DustFormatterBlock
          && ((DustFormatterBlock) getParent()).getIndent() == Indent.getNoneIndent()) {
        return Indent.getNormalIndent();
      }
    }

    // any element that is the direct descendant of a foreign block gets an indent
    if (getRealBlockParent() instanceof DataLanguageBlockWrapper) {
      return Indent.getNormalIndent();
    }

    return Indent.getNoneIndent();
  }

  private boolean hasOnlyDustLanguageParents() {
    BlockWithParent parent = getParent();
    boolean hasOnlyDustLanguageParents = true;

    while (parent != null) {
      if (parent instanceof DataLanguageBlockWrapper) {
        hasOnlyDustLanguageParents = false;
        break;
      }
      parent = parent.getParent();
    }

    return hasOnlyDustLanguageParents;
  }

  private BlockWithParent getRealBlockParent() {
    // if we can follow the chain of synthetic parent blocks, and if we end up
    // at a real DataLanguage block (i.e. the synthetic blocks didn't lead to an DustFormatterBlock),
    // we're a child of a templated language node and need an indent
    BlockWithParent parent = getParent();
    while (parent instanceof DataLanguageBlockWrapper
        && ((DataLanguageBlockWrapper) parent).getOriginal() instanceof SyntheticBlock) {
      parent = parent.getParent();
    }


    return parent;
  }

  /**
   * TODO implement alignment for "stacked" content.  i.e.:
   *      {#foo bar="baz"
   *           bat="bam"} <- note the alignment here
   */
  @Override
  public Alignment getAlignment() {
    return null;
  }

  @Override
  protected IElementType getTemplateTextElementType() {
    // we ignore HTML tokens since they get formatted by the templated language
    return DustTypes.HTML;
  }

  @Override
  public boolean isRequiredRange(TextRange range) {
    // seems our approach doesn't require us to insert any custom DataLanguageBlockFragmentWrapper blocks
    return false;
  }

  /**
   * TODO if/when we implement alignment, update this method to do alignment properly
   *
   * This method handles indent and alignment on Enter.
   */
  @NotNull
  @Override
  public ChildAttributes getChildAttributes(int newChildIndex) {
    /**
     * We indent if we're in a TAG_BLOCK (note that this works nicely since Enter can only be invoked
     * INSIDE a block (i.e. after the open block).
     *
     * Also indent if we are wrapped in a block created by the templated language
     */
    if (myNode.getElementType() == DustTypes.TAG_BLOCK
        || (getParent() instanceof DataLanguageBlockWrapper
        && (myNode.getElementType() != DustTypes.STATEMENTS || myNode.getTreeNext() instanceof PsiErrorElement))) {
      return new ChildAttributes(Indent.getNormalIndent(), null);
    } else {
      return new ChildAttributes(Indent.getNoneIndent(), null);
    }
  }

}