/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.netbeans.modules.java.source.save;

import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.CaseTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.ForLoopTree;
import com.sun.source.tree.IfTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.ModuleTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.SwitchTree;
import com.sun.source.tree.SynchronizedTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.TryTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.tree.WhileLoopTree;
import com.sun.source.util.SourcePositions;
import org.netbeans.api.java.source.support.ErrorAwareTreeScanner;
import com.sun.tools.javac.api.JavacTaskImpl;
import com.sun.tools.javac.api.JavacTool;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.main.JavaCompiler;

import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;

import javax.swing.text.BadLocationException;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticListener;
import javax.tools.JavaFileObject;

import com.sun.tools.javac.parser.ParserFactory;
import com.sun.tools.javac.util.Log;
import org.netbeans.api.java.lexer.JavaTokenId;
import org.netbeans.api.java.source.CodeStyle;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.modules.editor.indent.spi.Context;
import org.netbeans.modules.editor.indent.spi.Context.Region;
import org.netbeans.modules.editor.indent.spi.ExtraLock;
import org.netbeans.modules.editor.indent.spi.IndentTask;
import org.netbeans.modules.java.source.NoJavacHelper;
import org.netbeans.modules.java.source.TreeUtilitiesAccessor;
import org.netbeans.modules.java.source.parsing.FileObjects;
import org.netbeans.modules.java.source.parsing.JavacParser;
import org.netbeans.modules.java.source.parsing.ParsingUtils;
import org.netbeans.modules.java.source.parsing.PrefetchableJavaFileObject;
import org.netbeans.modules.parsing.impl.Utilities;
import org.openide.filesystems.FileObject;

/**
 *
 * @author Dusan Balek
 */
public class Reindenter implements IndentTask {

    private final Context context;
    private CodeStyle cs;
    private TokenSequence<JavaTokenId> ts;
    private String text;
    private CompilationUnitTree cut;
    private Tree parsedTree;
    private SourcePositions sp;
    private Map<Integer, Integer> newIndents;
    private int currentEmbeddingStartOffset;
    private int currentEmbeddingLength;

    private Reindenter(Context context) {
        this.context = context;
    }

    @Override
    public void reindent() throws BadLocationException {
        ts = null;
        currentEmbeddingLength = -1;
        newIndents = new HashMap<Integer, Integer>();
        cs = CodeStyle.getDefault(context.document());
        for (Region region : context.indentRegions()) {
            if (initRegionData(region)) {
                HashSet<Integer> linesToAddStar = new HashSet<Integer>();
                LinkedList<Integer> startOffsets = getStartOffsets(region);
                for (ListIterator<Integer> it = startOffsets.listIterator(); it.hasNext();) {
                    int startOffset = it.next();
                    int endOffset;
                    if (it.hasNext()) {
                        endOffset = it.next() - 1;
                        it.previous();
                    } else {
                        endOffset = region.getEndOffset();
                    }
                    String blockCommentLine;
                    int delta = ts.move(startOffset);
                    if (((startOffset == 0 || delta > 0) && ts.moveNext() || ts.movePrevious())
                            && (ts.token().id() != JavaTokenId.BLOCK_COMMENT || ts.embedded() == null)) {
                        if (cs.addLeadingStarInComment()
                                && (ts.token().id() == JavaTokenId.BLOCK_COMMENT && cs.enableBlockCommentFormatting()
                                || ts.token().id() == JavaTokenId.JAVADOC_COMMENT && cs.enableJavadocFormatting())) {
                            blockCommentLine = ts.token().text().toString();
                            if (delta > 0) {
                                int idx = blockCommentLine.indexOf('\n', delta); //NOI18N
                                blockCommentLine = (idx < 0 ? blockCommentLine.substring(delta) : blockCommentLine.substring(delta, idx)).trim();
                                int off = ts.offset() + delta - 1;
                                int prevLineStartOffset = context.lineStartOffset(off < 0 ? startOffset : off);
                                Integer prevLineIndent = newIndents.get(prevLineStartOffset);
                                newIndents.put(startOffset, (prevLineIndent != null ? prevLineIndent : context.lineIndent(prevLineStartOffset)) + (prevLineStartOffset > ts.offset() ? 0 : 1)); //NOI18N
                            } else {
                                int idx = blockCommentLine.lastIndexOf('\n'); //NOI18N
                                if (idx > 0) {
                                    blockCommentLine = blockCommentLine.substring(idx).trim();
                                }
                                newIndents.put(startOffset, getNewIndent(startOffset, endOffset) + 1);
                            }
                            if (!blockCommentLine.startsWith("*")) { //NOI18N
                                linesToAddStar.add(startOffset);
                            }
                        } else {
                            if (delta == 0 && ts.moveNext() && ts.token().id() == JavaTokenId.LINE_COMMENT) {
                                newIndents.put(startOffset, 0);
                            } else {
                                newIndents.put(startOffset, getNewIndent(startOffset, endOffset));
                            }
                        }
                    }
                }
                while (!startOffsets.isEmpty()) {
                    int startOffset = startOffsets.removeLast();
                    Integer newIndent = newIndents.get(startOffset);
                    if (linesToAddStar.contains(startOffset)) {
                        context.modifyIndent(startOffset, 0);
                        context.document().insertString(startOffset, "* ", null); //NOI18N
                    }
                    if (newIndent != null) {
                        context.modifyIndent(startOffset, newIndent);
                    }
                    if (!startOffsets.isEmpty()) {
                        char c;
                        int len = 0;
                        while ((c = text.charAt(startOffset - 2 - len)) != '\n' && Character.isWhitespace(c)) { //NOI18N
                            len++;
                        }
                        if (len > 0) {
                            context.document().remove(startOffset - 1 - len, len);
                        }
                    }
                }
            }
        }
    }

    @Override
    public ExtraLock indentLock() {
        return null;
    }
    
    private boolean initRegionData(final Region region) {
        if (ts == null || (currentEmbeddingLength >= 0
                && !(currentEmbeddingStartOffset <= region.getStartOffset()
                && currentEmbeddingStartOffset + currentEmbeddingLength >= region.getEndOffset()))) {
            ts = null;
            currentEmbeddingStartOffset = 0;
            currentEmbeddingLength = context.document().getLength();
            TokenSequence<?> tseq = TokenHierarchy.get(context.document()).tokenSequence();
            while(tseq != null && (region.getStartOffset() == 0 || tseq.moveNext())) {
                tseq.move(region.getStartOffset());
                if (!tseq.moveNext() && !tseq.movePrevious()) {
                    return false;
                }
                if (tseq.language() == JavaTokenId.language()) {
                    ts = (TokenSequence<JavaTokenId>)tseq;
                    tseq.moveStart();
                    tseq.moveNext();
                    currentEmbeddingStartOffset = tseq.offset();
                    tseq.moveEnd();
                    tseq.movePrevious();
                    currentEmbeddingLength = tseq.offset() - currentEmbeddingStartOffset;
                    break;
                }
                tseq = tseq.embeddedJoined();
            }
            if (ts == null) {
                return false;                
            }
            ClassLoader origCL = Thread.currentThread().getContextClassLoader();
            try {
                Thread.currentThread().setContextClassLoader(Reindenter.class.getClassLoader());
                JavacTaskImpl javacTask = (JavacTaskImpl)JavacTool.create().getTask(null, null, new DiagnosticListener<JavaFileObject>() {
                    @Override
                    public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
                    }
                }, Collections.singletonList("-proc:none"), null, Collections.<JavaFileObject>emptySet()); //NOI18N
                com.sun.tools.javac.util.Context ctx = javacTask.getContext();
                JavaCompiler.instance(ctx).genEndPos = true;
                text = context.document().getText(currentEmbeddingStartOffset, currentEmbeddingLength);
                if (JavacParser.MIME_TYPE.equals(context.mimePath())) {
                    FileObject fo = Utilities.getFileObject(context.document());
                    cut = ParsingUtils.parseArbitrarySource(javacTask, FileObjects.memoryFileObject("", fo != null ? fo.getNameExt() : "", text));
                    parsedTree = cut;
                    sp = JavacTrees.instance(ctx).getSourcePositions();
                } else {
                    final SourcePositions[] psp = new SourcePositions[1];
                    cut = null;
                    parsedTree = TreeUtilitiesAccessor.getInstance().parseStatement(javacTask, "{" + text + "}", psp);
                    sp = new SourcePositions() {
                        @Override
                        public long getStartPosition(CompilationUnitTree file, Tree tree) {
                            return currentEmbeddingStartOffset + psp[0].getStartPosition(file, tree) - 1;
                        }

                        @Override
                        public long getEndPosition(CompilationUnitTree file, Tree tree) {
                            return currentEmbeddingStartOffset + psp[0].getEndPosition(file, tree) - 1;
                        }
                    };
                }
            } catch (Exception ex) {
                return false;
            } finally {
                Thread.currentThread().setContextClassLoader(origCL);
            }
        }
        return true;
    }

    private LinkedList<Integer> getStartOffsets(Region region) throws BadLocationException {
        LinkedList<Integer> offsets = new LinkedList<Integer>();
        int offset = region.getEndOffset();
        int lso;
        while (offset > 0 && (lso = context.lineStartOffset(offset)) >= region.getStartOffset()) {
            offsets.addFirst(lso);
            offset = lso - 1;
        }
        return offsets;
    }

    private int getNewIndent(int startOffset, int endOffset) throws BadLocationException {
        LinkedList<? extends Tree> path = getPath(startOffset);
        if (path.isEmpty()) {
            return 0;
        }
        Tree last = path.getFirst();
        int lastPos = getStartPosition(last);
        int currentIndent = getCurrentIndent(last, path);
        switch (last.getKind()) {
            case COMPILATION_UNIT:
                break;
            case MODULE:
                TokenSequence<JavaTokenId> token = findFirstNonWhitespaceToken(startOffset, endOffset);
                JavaTokenId nextTokenId = token != null ? token.token().id() : null;
                if (nextTokenId != null && nextTokenId == JavaTokenId.RBRACE) {
                    if (isLeftBraceOnNewLine(lastPos, startOffset)) {
                        switch (cs.getModuleDeclBracePlacement()) {
                            case NEW_LINE_INDENTED:
                                currentIndent += cs.getIndentSize();
                                break;
                            case NEW_LINE_HALF_INDENTED:
                                currentIndent += (cs.getIndentSize() / 2);
                                break;
                        }
                    }
                } else {
                    Tree t = null;
                    for (Tree member : ((ModuleTree)last).getDirectives()) {
                        if (sp.getEndPosition(cut, member) > startOffset) {
                            break;
                        }
                        t = member;
                    }
                    if (t != null) {
                        int i = getCurrentIndent(t, path);
                        currentIndent = i < 0 ? currentIndent + (cs.indentTopLevelClassMembers() ? cs.getIndentSize() : 0) : i;
                    } else {
                        token = findFirstNonWhitespaceToken(startOffset, lastPos);
                        JavaTokenId prevTokenId = token != null ? token.token().id() : null;
                        if (prevTokenId != null) {
                            switch (prevTokenId) {
                                case LBRACE:
                                    currentIndent += cs.indentTopLevelClassMembers() ? cs.getIndentSize() : 0;
                                    break;
                                case IDENTIFIER:
                                    if (nextTokenId != null && nextTokenId == JavaTokenId.LBRACE) {
                                        switch (cs.getModuleDeclBracePlacement()) {
                                            case NEW_LINE_INDENTED:
                                                currentIndent += cs.getIndentSize();
                                                break;
                                            case NEW_LINE_HALF_INDENTED:
                                                currentIndent += (cs.getIndentSize() / 2);
                                                break;
                                        }
                                    } else {
                                        currentIndent += cs.getContinuationIndentSize();
                                    }
                                    break;
                                default:
                                    currentIndent += cs.getContinuationIndentSize();
                            }
                        }
                    }
                }
                break;
            case CLASS:
            case INTERFACE:
            case ENUM:
            case ANNOTATION_TYPE:
                token = findFirstNonWhitespaceToken(startOffset, endOffset);
                nextTokenId = token != null ? token.token().id() : null;
                if (nextTokenId != null && nextTokenId == JavaTokenId.RBRACE) {
                    if (isLeftBraceOnNewLine(lastPos, startOffset)) {
                        switch (cs.getClassDeclBracePlacement()) {
                            case NEW_LINE_INDENTED:
                                currentIndent += cs.getIndentSize();
                                break;
                            case NEW_LINE_HALF_INDENTED:
                                currentIndent += (cs.getIndentSize() / 2);
                                break;
                        }
                    }
                } else {
                    Tree t = null;
                    for (Tree member : ((ClassTree)last).getMembers()) {
                        if (getEndPosition(member) > startOffset) {
                            break;
                        }
                        t = member;
                    }
                    if (t != null) {
                        int i = getCurrentIndent(t, path);
                        currentIndent = i < 0 ? currentIndent + (cs.indentTopLevelClassMembers() ? cs.getIndentSize() : 0) : i;
                    } else {
                        token = findFirstNonWhitespaceToken(startOffset, lastPos);
                        JavaTokenId prevTokenId = token != null ? token.token().id() : null;
                        if (prevTokenId != null) {
                            switch (prevTokenId) {
                                case LBRACE:
                                    if (path.size() > 1 && path.get(1).getKind() == Kind.NEW_CLASS && isLeftBraceOnNewLine(lastPos, startOffset)) {
                                        switch (cs.getClassDeclBracePlacement()) {
                                            case SAME_LINE:
                                            case NEW_LINE:
                                                currentIndent += cs.getIndentSize();
                                                break;
                                            case NEW_LINE_HALF_INDENTED:
                                                currentIndent += (cs.getIndentSize() - cs.getIndentSize() / 2);
                                                break;
                                        }
                                    } else {
                                        currentIndent += cs.indentTopLevelClassMembers() ? cs.getIndentSize() : 0;
                                    }
                                    break;
                                case COMMA:
                                    currentIndent = getMultilineIndent(((ClassTree)last).getImplementsClause(), path, token.offset(), currentIndent, cs.alignMultilineImplements(), true);
                                    break;
                                case IDENTIFIER:
                                case GT:
                                case GTGT:
                                case GTGTGT:
                                    if (nextTokenId != null && nextTokenId == JavaTokenId.LBRACE) {
                                        switch (cs.getClassDeclBracePlacement()) {
                                            case NEW_LINE_INDENTED:
                                                currentIndent += cs.getIndentSize();
                                                break;
                                            case NEW_LINE_HALF_INDENTED:
                                                currentIndent += (cs.getIndentSize() / 2);
                                                break;
                                        }
                                    } else {
                                        currentIndent += cs.getContinuationIndentSize();
                                    }
                                    break;
                                default:
                                    currentIndent += cs.getContinuationIndentSize();
                            }
                        }
                    }
                }
                break;
            case METHOD:
                token = findFirstNonWhitespaceToken(startOffset, lastPos);
                JavaTokenId prevTokenId = token != null ? token.token().id() : null;
                if (prevTokenId != null) {
                    switch (prevTokenId) {
                        case COMMA:
                            List<? extends ExpressionTree> thrws = ((MethodTree)last).getThrows();
                            if (!thrws.isEmpty() && getStartPosition(thrws.get(0)) < token.offset()) {
                                currentIndent = getMultilineIndent(thrws, path, token.offset(), currentIndent, cs.alignMultilineThrows(), true);
                            } else {
                                currentIndent = getMultilineIndent(((MethodTree)last).getParameters(), path, token.offset(), currentIndent, cs.alignMultilineMethodParams(), true);
                            }
                            break;
                        case RPAREN:
                        case IDENTIFIER:
                        case GT:
                        case GTGT:
                        case GTGTGT:
                            token = findFirstNonWhitespaceToken(startOffset, endOffset);
                            if (token != null && token.token().id() == JavaTokenId.LBRACE) {
                                switch (cs.getMethodDeclBracePlacement()) {
                                    case NEW_LINE_INDENTED:
                                        currentIndent += cs.getIndentSize();
                                        break;
                                    case NEW_LINE_HALF_INDENTED:
                                        currentIndent += (cs.getIndentSize() / 2);
                                        break;
                                }
                                break;
                            }
                        default:
                            token = findFirstNonWhitespaceToken(startOffset, endOffset);
                            if (token == null || token.token().id() != JavaTokenId.RPAREN) {
                                currentIndent += cs.getContinuationIndentSize();
                            }
                    }
                }
                break;
            case VARIABLE:
                Tree type = ((VariableTree)last).getType();
                if (type != null && type.getKind() != Kind.ERRONEOUS) {
                    ExpressionTree init = ((VariableTree)last).getInitializer();
                    if (init == null || init.getKind() != Kind.NEW_ARRAY
                            || (token = findFirstNonWhitespaceToken(startOffset, lastPos)) == null
                            || token.token().id() != JavaTokenId.EQ
                            || (token = findFirstNonWhitespaceToken(startOffset, endOffset)) == null
                            || token.token().id() != JavaTokenId.LBRACE) {
                        if (cs.alignMultilineAssignment()) {
                            int c = getColumn(last);
                            if (c >= 0) {
                                currentIndent = c;
                            }
                        } else {
                            currentIndent += cs.getContinuationIndentSize();
                        }
                    } else {
                        switch (cs.getOtherBracePlacement()) {
                            case NEW_LINE_INDENTED:
                                currentIndent += cs.getIndentSize();
                                break;
                            case NEW_LINE_HALF_INDENTED:
                                currentIndent += (cs.getIndentSize() / 2);
                                break;
                        }
                    }
                    break;
                } else {
                    last = ((VariableTree)last).getModifiers();
                    if (last == null)
                        break;
                }
            case MODIFIERS:
                Tree t = null;
                for (Tree ann : ((ModifiersTree)last).getAnnotations()) {
                    if (getEndPosition(ann) > startOffset) {
                        break;
                    }
                    t = ann;
                }
                if (t == null || findFirstNonWhitespaceToken(startOffset, getEndPosition(t)) != null) {
                    currentIndent += cs.getContinuationIndentSize();
                }
                break;
            case DO_WHILE_LOOP:
                token = findFirstNonWhitespaceToken(startOffset, lastPos);
                if (token != null && !EnumSet.of(JavaTokenId.RBRACE, JavaTokenId.SEMICOLON).contains(token.token().id())) {
                    currentIndent = getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.DO), lastPos, currentIndent);
                }
                break;
            case ENHANCED_FOR_LOOP:
                currentIndent = getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.RPAREN), getEndPosition(((EnhancedForLoopTree)last).getExpression()), currentIndent);
                break;
            case FOR_LOOP:
                LinkedList<Tree> forTrees = new LinkedList<Tree>();
                for (StatementTree st : ((ForLoopTree)last).getInitializer()) {
                    if (getEndPosition(st) > startOffset) {
                        break;
                    }
                    forTrees.add(st);
                }
                t = ((ForLoopTree)last).getCondition();
                if (t != null && getEndPosition(t) <= startOffset) {
                    forTrees.add(t);
                }
                for (ExpressionStatementTree est : ((ForLoopTree)last).getUpdate()) {
                    if (getEndPosition(est) > startOffset) {
                        break;
                    }
                    forTrees.add(est);
                }
                token = findFirstNonWhitespaceToken(startOffset, lastPos);
                if (token != null && token.token().id() == JavaTokenId.SEMICOLON) {
                    currentIndent = getMultilineIndent(forTrees, path, token.offset(), currentIndent, cs.alignMultilineFor(), true);
                } else {
                    currentIndent = getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.RPAREN), forTrees.isEmpty() ? lastPos : getEndPosition(forTrees.getLast()), currentIndent);
                }
                break;
            case IF:
                token = findFirstNonWhitespaceToken(startOffset, endOffset);
                if (token == null || token.token().id() != JavaTokenId.ELSE) {
                    token = findFirstNonWhitespaceToken(startOffset, lastPos);
                    if (token != null && !EnumSet.of(JavaTokenId.RBRACE, JavaTokenId.SEMICOLON).contains(token.token().id())) {
                        currentIndent = getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.RPAREN, JavaTokenId.ELSE), getEndPosition(((IfTree)last).getCondition()) - 1, currentIndent);
                    }
                }
                break;
            case SYNCHRONIZED:
                currentIndent = getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.RPAREN), getEndPosition(((SynchronizedTree)last).getExpression()) - 1, currentIndent);
                break;
            case TRY:
                token = findFirstNonWhitespaceToken(startOffset, endOffset);
                if (token == null || !EnumSet.of(JavaTokenId.CATCH, JavaTokenId.FINALLY).contains(token.token().id())) {
                    token = findFirstNonWhitespaceToken(startOffset, lastPos);
                    if (token != null && token.token().id() != JavaTokenId.RBRACE) {
                        t = null;
                        for (Tree res : ((TryTree)last).getResources()) {
                            if (getEndPosition(res) > startOffset) {
                                break;
                            }
                            t = res;
                        }
                        currentIndent = getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.TRY, JavaTokenId.RPAREN, JavaTokenId.FINALLY), t != null ? getEndPosition(t) : lastPos, currentIndent);
                    }
                }
                break;
            case CATCH:
                currentIndent = getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.RPAREN), lastPos, currentIndent);
                break;
            case WHILE_LOOP:
                currentIndent = getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.RPAREN), getEndPosition(((WhileLoopTree)last).getCondition()) - 1, currentIndent);
                break;
            case BLOCK:
                boolean isStatic = ((BlockTree)last).isStatic();
                if (isStatic) {
                    token = findFirstNonWhitespaceToken(startOffset, lastPos);
                    if (token != null && token.token().id() == JavaTokenId.STATIC && token.offset() == lastPos) {
                        switch (cs.getOtherBracePlacement()) {
                            case NEW_LINE_INDENTED:
                                currentIndent += cs.getIndentSize();
                                break;
                            case NEW_LINE_HALF_INDENTED:
                                currentIndent += (cs.getIndentSize() / 2);
                                break;
                        }
                        break;
                    }
                }
                token = findFirstNonWhitespaceToken(startOffset, endOffset);
                nextTokenId = token != null ? token.token().id() : null;
                if (nextTokenId == null || nextTokenId != JavaTokenId.RBRACE) {
                    token = findFirstOtherToken(startOffset, lastPos + 1, EnumSet.of(JavaTokenId.WHITESPACE));
                    int prevTokenLineStartOffset = token != null ? context.lineStartOffset(token.offset()) : -1;
                    t = null;
                    boolean isNextLabeledStatement = false;
                    Iterator<? extends StatementTree> it = ((BlockTree)last).getStatements().iterator();
                    while (it.hasNext()) {
                        StatementTree st = it.next();
                        if (getEndPosition(st) > startOffset) {
                            isNextLabeledStatement = st.getKind() == Kind.LABELED_STATEMENT;
                            break;
                        }
                        t = st;
                    }                    
                    if (isNextLabeledStatement && cs.absoluteLabelIndent()) {
                        currentIndent = 0;
                    } else if (t != null) {
                        int i = -1;
                        if (getEndPosition(t) < prevTokenLineStartOffset) {
                            Integer newIndent = newIndents.get(prevTokenLineStartOffset);
                            i = newIndent != null ? newIndent : context.lineIndent(prevTokenLineStartOffset);
                        } else {
                            i = getCurrentIndent(t, path);
                        }
                        currentIndent = i < 0 ? currentIndent + cs.getIndentSize() : i;
                    } else if (isStatic) {
                        currentIndent += cs.getIndentSize();
                    } else if (isLeftBraceOnNewLine(lastPos, startOffset)) {
                        switch (path.size() > 1 && path.get(1).getKind() == Kind.METHOD ? cs.getMethodDeclBracePlacement() : cs.getOtherBracePlacement()) {
                            case SAME_LINE:
                            case NEW_LINE:
                                currentIndent += cs.getIndentSize();
                                break;
                            case NEW_LINE_HALF_INDENTED:
                                currentIndent += (cs.getIndentSize() - cs.getIndentSize() / 2);
                                break;
                        }
                    } else if (prevTokenLineStartOffset >= 0 && prevTokenLineStartOffset > context.lineStartOffset(lastPos)) {
                        Integer newIndent = newIndents.get(prevTokenLineStartOffset);
                        currentIndent = newIndent != null ? newIndent : context.lineIndent(prevTokenLineStartOffset);
                    } else {
                        int i = path.size() > 1 ? getCurrentIndent(path.get(1), path) : -1;
                        currentIndent = (i < 0 ? currentIndent : i) + cs.getIndentSize();
                    }
                    if (nextTokenId != null && nextTokenId == JavaTokenId.LBRACE) {
                        switch (cs.getOtherBracePlacement()) {
                            case NEW_LINE_INDENTED:
                                currentIndent += cs.getIndentSize();
                                break;
                            case NEW_LINE_HALF_INDENTED:
                                currentIndent += (cs.getIndentSize() / 2);
                                break;
                        }
                    }
                } else if (isStatic) {
                    switch (cs.getOtherBracePlacement()) {
                        case NEW_LINE_INDENTED:
                            currentIndent += cs.getIndentSize();
                            break;
                        case NEW_LINE_HALF_INDENTED:
                            currentIndent += (cs.getIndentSize() / 2);
                            break;
                    }
                } else if (!isLeftBraceOnNewLine(lastPos, startOffset)) {
                    int i = path.size() > 1 ? getCurrentIndent(path.get(1), path) : -1;
                    currentIndent = i < 0 ? currentIndent + cs.getIndentSize() : i;
                }
                break;
            case SWITCH:
                token = findFirstNonWhitespaceToken(startOffset, endOffset);
                nextTokenId = token != null ? token.token().id() : null;
                if (nextTokenId != null && nextTokenId == JavaTokenId.RBRACE) {
                    if (isLeftBraceOnNewLine(lastPos, startOffset)) {
                        switch (cs.getOtherBracePlacement()) {
                            case NEW_LINE_INDENTED:
                                currentIndent += cs.getIndentSize();
                                break;
                            case NEW_LINE_HALF_INDENTED:
                                currentIndent += (cs.getIndentSize() / 2);
                                break;
                        }
                    }
                } else {
                    t = null;
                    for (CaseTree ct : ((SwitchTree)last).getCases()) {
                        if (getEndPosition(ct) > startOffset) {
                            break;
                        }
                        t = ct;
                    }
                    if (t != null) {
                        CaseTree ct = (CaseTree)t;
                        if (nextTokenId == null || !EnumSet.of(JavaTokenId.CASE, JavaTokenId.DEFAULT).contains(nextTokenId)) {
                            t = null;
                            for (StatementTree st : ct.getStatements()) {
                                if (getEndPosition(st) > startOffset) {
                                    break;
                                }
                                t = st;
                            }
                            if (t != null) {
                                int i = getCurrentIndent(t, path);
                                currentIndent = i < 0 ? getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.COLON), getEndPosition(ct.getExpression()), currentIndent) : i;
                            } else {
                                int i = getCurrentIndent(ct, path);
                                currentIndent = i < 0 ? getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.COLON), getEndPosition(ct.getExpression()), currentIndent) : i;
                                currentIndent += cs.getIndentSize();
                            }
                        } else {
                            int i = getCurrentIndent(t, path);
                            currentIndent = i < 0 ? currentIndent + (cs.indentCasesFromSwitch() ? cs.getIndentSize() : 0) : i;                            
                        }
                    } else {
                        token = findFirstNonWhitespaceToken(startOffset, lastPos);
                        if (token != null && token.token().id() == JavaTokenId.LBRACE) {
                            currentIndent += (cs.indentCasesFromSwitch() ? cs.getIndentSize() : 0);
                        } else {
                            currentIndent = getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.RPAREN), getEndPosition(((SwitchTree)last).getExpression()) - 1, currentIndent);
                        }
                    }
                }
                break;
            case CASE:
                t = null;
                for (StatementTree st : ((CaseTree)last).getStatements()) {
                    if (getEndPosition(st) > startOffset) {
                        break;
                    }
                    t = st;
                }
                if (t != null) {
                    int i = getCurrentIndent(t, path);
                    currentIndent = i < 0 ? getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.COLON), getEndPosition(((CaseTree)last).getExpression()), currentIndent) : i;
                } else {
                    currentIndent = getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.COLON), getEndPosition(((CaseTree)last).getExpression()), currentIndent);
                }
                break;
            case NEW_ARRAY:
                token = findFirstNonWhitespaceToken(startOffset, endOffset);
                nextTokenId = token != null ? token.token().id() : null;
                if (nextTokenId != JavaTokenId.RBRACE) {
                    token = findFirstNonWhitespaceToken(startOffset, lastPos);
                    prevTokenId = token != null ? token.token().id() : null;
                    if (prevTokenId != null) {
                        switch (prevTokenId) {
                            case LBRACE:
                                currentIndent += cs.getIndentSize();
                                break;
                            case COMMA:
                                currentIndent = getMultilineIndent(((NewArrayTree)last).getInitializers(), path, token.offset(), currentIndent, cs.alignMultilineArrayInit(), false);
                                break;
                            case RBRACKET:
                                if (nextTokenId == JavaTokenId.LBRACE) {
                                    switch (cs.getOtherBracePlacement()) {
                                        case NEW_LINE_INDENTED:
                                            currentIndent += cs.getIndentSize();
                                            break;
                                        case NEW_LINE_HALF_INDENTED:
                                            currentIndent += (cs.getIndentSize() / 2);
                                            break;
                                    }
                                    break;
                                }
                            default:
                                currentIndent += cs.getContinuationIndentSize();
                        }
                    }
                }
                break;
            case LAMBDA_EXPRESSION:
                token = findFirstNonWhitespaceToken(startOffset, endOffset);
                nextTokenId = token != null ? token.token().id() : null;
                token = findFirstNonWhitespaceToken(startOffset, lastPos);
                prevTokenId = token != null ? token.token().id() : null;
                if (prevTokenId == JavaTokenId.ARROW && nextTokenId == JavaTokenId.LBRACE) {
                    switch (cs.getOtherBracePlacement()) {
                        case NEW_LINE_INDENTED:
                            currentIndent += cs.getIndentSize();
                            break;
                        case NEW_LINE_HALF_INDENTED:
                            currentIndent += (cs.getIndentSize() / 2);
                            break;
                    }
                } else if (nextTokenId != JavaTokenId.RPAREN) {
                    currentIndent = getContinuationIndent(path, currentIndent);
                }
                break;
            case NEW_CLASS:
                token = findFirstNonWhitespaceToken(startOffset, endOffset);
                nextTokenId = token != null ? token.token().id() : null;
                token = findFirstNonWhitespaceToken(startOffset, lastPos);
                prevTokenId = token != null ? token.token().id() : null;
                if (prevTokenId == JavaTokenId.RPAREN && nextTokenId == JavaTokenId.LBRACE) {
                    switch (cs.getClassDeclBracePlacement()) {
                        case NEW_LINE_INDENTED:
                            currentIndent += cs.getIndentSize();
                            break;
                        case NEW_LINE_HALF_INDENTED:
                            currentIndent += (cs.getIndentSize() / 2);
                            break;
                    }
                } else if (nextTokenId != JavaTokenId.RPAREN) {
                    currentIndent = getContinuationIndent(path, currentIndent);
                }
                break;
            case METHOD_INVOCATION:
                token = findFirstNonWhitespaceToken(startOffset, lastPos);
                if (token != null && token.token().id() == JavaTokenId.COMMA) {
                    currentIndent = getMultilineIndent(((MethodInvocationTree)last).getArguments(), path, token.offset(), currentIndent, cs.alignMultilineCallArgs(), true);
                } else {
                    token = findFirstNonWhitespaceToken(startOffset, endOffset);
                    if (token == null || token.token().id() != JavaTokenId.RPAREN) {
                        currentIndent = getContinuationIndent(path, currentIndent);
                    }
                }
                break;
            case ANNOTATION:
                token = findFirstNonWhitespaceToken(startOffset, lastPos);
                if (token != null && token.token().id() == JavaTokenId.COMMA) {
                    currentIndent = getMultilineIndent(((AnnotationTree)last).getArguments(), path, token.offset(), currentIndent, cs.alignMultilineAnnotationArgs(), true);
                } else {
                    token = findFirstNonWhitespaceToken(startOffset, endOffset);
                    if (token == null || token.token().id() != JavaTokenId.RPAREN) {
                        currentIndent = getContinuationIndent(path, currentIndent);
                    }
                }
                break;
            case LABELED_STATEMENT:
                token = findFirstNonWhitespaceToken(startOffset, lastPos);
                if (token == null || token.token().id() != JavaTokenId.COLON) {
                    currentIndent = getContinuationIndent(path, currentIndent);
                } else {
                    currentIndent += cs.getLabelIndent();
                }
                break;
            default:
                currentIndent = getContinuationIndent(path, currentIndent);
                break;
        }
        return currentIndent;
    }

    private int getStartPosition(Tree last) {
        return (int) sp.getStartPosition(cut, last);
    }

    private int getEndPosition(Tree last) {
        return (int) sp.getEndPosition(cut, last);
    }

    private LinkedList<? extends Tree> getPath(final int startOffset) {
        final LinkedList<Tree> path = new LinkedList<Tree>();

        // When right at the token end move to previous token; otherwise move to the token that "contains" the offset
        if (ts.move(startOffset) == 0 && startOffset > 0 || !ts.moveNext()) {
            ts.movePrevious();
        }
        final int offset = (ts.token().id() == JavaTokenId.IDENTIFIER
                || ts.token().id().primaryCategory().startsWith("keyword") || //NOI18N
                ts.token().id().primaryCategory().startsWith("string") || //NOI18N
                ts.token().id().primaryCategory().equals("literal")) //NOI18N
                ? ts.offset() : startOffset;

        new ErrorAwareTreeScanner<Void, Void>() {

            @Override
            public Void scan(Tree node, Void p) {
                if (node != null) {
                    if (getStartPosition(node) < offset && getEndPosition(node) >= offset) {
                        super.scan(node, p);
                        if (node.getKind() != Tree.Kind.ERRONEOUS || !path.isEmpty()) {
                            path.add(node);
                        }
                    }
                }
                return null;
            }
        }.scan(parsedTree, null);

        if (path.isEmpty() || path.getFirst() == parsedTree || getEndPosition(path.getFirst()) > offset) {
            return path;
        }

        if (!path.isEmpty() && ts.move(offset) == 0) {
            if (ts.movePrevious()) {
                switch (ts.token().id()) {
                    case RPAREN:
                        if (!EnumSet.of(Kind.ENHANCED_FOR_LOOP, Kind.FOR_LOOP, Kind.IF, Kind.WHILE_LOOP, Kind.DO_WHILE_LOOP,
                                Kind.TYPE_CAST, Kind.SYNCHRONIZED).contains(path.getFirst().getKind())) {
                            path.removeFirst();
                        }
                        break;
                    case GTGTGT:
                    case GTGT:
                    case GT:
                        if (EnumSet.of(Kind.MEMBER_SELECT, Kind.CLASS, Kind.GREATER_THAN).contains(path.getFirst().getKind())) {
                            break;
                        }
                    case SEMICOLON:
                        if (path.getFirst().getKind() == Kind.FOR_LOOP
                                && ts.offset() <= getStartPosition(((ForLoopTree)path.getFirst()).getUpdate().get(0))) {
                            break;
                        }
                    case RBRACE:
                        path.removeFirst();
                        switch (path.getFirst().getKind()) {
                            case CATCH:
                                path.removeFirst();
                            case METHOD:
                            case FOR_LOOP:
                            case ENHANCED_FOR_LOOP:
                            case IF:
                            case SYNCHRONIZED:
                            case WHILE_LOOP:
                            case TRY:
                                path.removeFirst();
                        }
                        break;
                }
            }
        }

        return path;
    }

    private TokenSequence<JavaTokenId> findFirstNonWhitespaceToken(int startOffset, int endOffset) {
        return findFirstOtherToken(startOffset, endOffset, EnumSet.of(JavaTokenId.WHITESPACE, JavaTokenId.LINE_COMMENT, JavaTokenId.BLOCK_COMMENT, JavaTokenId.JAVADOC_COMMENT));
    }
    
    private TokenSequence<JavaTokenId> findFirstOtherToken(int startOffset, int endOffset, EnumSet<JavaTokenId> ids) {
        if (startOffset == endOffset) {
            return null;
        }
        ts.move(startOffset);
        boolean backward = startOffset > endOffset;
        while (backward ? ts.movePrevious() : ts.moveNext()) {
            if (backward && ts.offset() < endOffset || !backward && ts.offset() > endOffset) {
                return null;
            }
            if (!ids.contains(ts.token().id())) {
                return ts;
            }
        }
        return null;
    }

    private TokenSequence<JavaTokenId> findFirstTokenOccurrence(int startOffset, int endOffset, JavaTokenId token) {
        if (startOffset == endOffset) {
            return null;
        }
        ts.move(startOffset);
        boolean backward = startOffset > endOffset;
        while (backward ? ts.movePrevious() : ts.moveNext()) {
            if (backward && ts.offset() < endOffset || !backward && ts.offset() > endOffset) {
                return null;
            }
            if (ts.token().id() == token) {
                return ts;
            }
        }
        return null;
    }

    private boolean isLeftBraceOnNewLine(int startOffset, int endOffset) {
        ts.move(startOffset);
        while (ts.moveNext()) {
            if (ts.offset() >= endOffset) {
                return false;
            }
            if (ts.token().id() == JavaTokenId.LBRACE) {
                if (!ts.movePrevious()) {
                    return false;
                }
                return ts.token().id() == JavaTokenId.LINE_COMMENT || ts.token().id() == JavaTokenId.WHITESPACE && ts.token().text().toString().indexOf('\n') >= 0;
            }
        }
        return false;
    }
    
    private int getColumn(Tree tree) throws BadLocationException {
        int startOffset = getStartPosition(tree);
        if (startOffset < 0) {
            return -1;
        }
        int lineStartOffset = context.lineStartOffset(startOffset);
        return getCol(context.document().getText(lineStartOffset, startOffset - lineStartOffset));
    }
    
    private int getCurrentIndent(Tree tree, List<? extends Tree> path) throws BadLocationException {
        int startOffset = -1;
        switch (tree.getKind()) {
            case METHOD_INVOCATION:
                MethodInvocationTree mit = (MethodInvocationTree)tree;
                startOffset = getEndPosition(mit.getMethodSelect());
                TokenSequence<JavaTokenId> token = startOffset >= 0 ? findFirstTokenOccurrence(startOffset, getEndPosition(tree), JavaTokenId.LPAREN) : null;
                if (token != null) {
                    startOffset = token.offset();
                }
                break;
            case NEW_CLASS:
                NewClassTree nct = (NewClassTree)tree;
                startOffset = getEndPosition(nct.getIdentifier());
                token = startOffset >= 0 ? findFirstTokenOccurrence(startOffset, getEndPosition(tree), JavaTokenId.LPAREN) : null;
                if (token != null) {
                    startOffset = token.offset();
                }
                break;
            case ANNOTATION:
                AnnotationTree at = (AnnotationTree)tree;
                startOffset = getEndPosition(at.getAnnotationType());
                token = startOffset >= 0 ? findFirstTokenOccurrence(startOffset, getEndPosition(tree), JavaTokenId.LPAREN) : null;
                if (token != null) {
                    startOffset = token.offset();
                }
                break;
            case METHOD:
                MethodTree mt = (MethodTree)tree;
                startOffset = getEndPosition(mt.getReturnType());
                if (startOffset < 0) {
                    startOffset = getEndPosition(mt.getModifiers());
                }
                if (startOffset < 0) {
                    startOffset = getStartPosition(tree);
                }
                token = startOffset >= 0 ? findFirstTokenOccurrence(startOffset, mt.getBody() != null ? getStartPosition(mt.getBody()) : getEndPosition(tree), JavaTokenId.LPAREN) : null;
                if (token != null) {
                    startOffset = token.offset();
                }
                break;
        }
        if (startOffset < 0) {
            startOffset = getStartPosition(tree);
        }
        if (startOffset < 0) {
            startOffset = currentEmbeddingStartOffset;
        }
        int lineStartOffset = context.lineStartOffset(startOffset);
        Integer newIndent = newIndents.get(lineStartOffset);
        int currentIndent = newIndent != null ? newIndent : context.lineIndent(lineStartOffset);
        if (cs.absoluteLabelIndent()) {
            for (Iterator<? extends Tree> it = path.iterator(); it.hasNext();) {
                Tree t = it.next();
                if (t.getKind() == Tree.Kind.LABELED_STATEMENT && getStartPosition(t) == lineStartOffset) {
                    Tree parent = it.hasNext() ? it.next() : null;
                    if (parent != null && parent.getKind() == Kind.BLOCK) {
                        Tree stat = null;
                        for (StatementTree st : ((BlockTree)parent).getStatements()) {
                            if (getEndPosition(st) > startOffset) {
                                break;
                            }
                            stat = st;
                        }
                        if (stat != null) {
                            int i = getCurrentIndent(stat, path);
                            currentIndent = i < 0 ? currentIndent + cs.getIndentSize() : i;
                        } else {
                            int i = getCurrentIndent(parent, path);
                            currentIndent = (i < 0 ? currentIndent : i) + cs.getIndentSize();
                        }
                    }
                    break;
                }
            }
        }
        return currentIndent;
    }

    private int getContinuationIndent(LinkedList<? extends Tree> path, int currentIndent) throws BadLocationException {
        for (Tree tree : path) {
            switch (tree.getKind()) {
                case CLASS:
                case INTERFACE:
                case ENUM:
                case ANNOTATION_TYPE:
                case VARIABLE:
                case METHOD:
                case TRY:
                case RETURN:
                case BLOCK:
                case FOR_LOOP:
                case SWITCH:
                case THROW:
                case WHILE_LOOP:
                case IF:
                case EXPRESSION_STATEMENT:
                case SYNCHRONIZED:
                case ASSERT:
                case CONTINUE:
                case LABELED_STATEMENT:
                case ENHANCED_FOR_LOOP:
                case BREAK:
                case EMPTY_STATEMENT:
                case DO_WHILE_LOOP:
                    int i = getCurrentIndent(tree, path);
                    return (i < 0 ? currentIndent : i) + cs.getContinuationIndentSize();
                case METHOD_INVOCATION:
                case NEW_CLASS:
                case LAMBDA_EXPRESSION:
                case ANNOTATION:
                    return currentIndent + cs.getContinuationIndentSize();
                case ASSIGNMENT:
                case MULTIPLY_ASSIGNMENT:
                case DIVIDE_ASSIGNMENT:
                case REMAINDER_ASSIGNMENT:
                case PLUS_ASSIGNMENT:
                case MINUS_ASSIGNMENT:
                case LEFT_SHIFT_ASSIGNMENT:
                case RIGHT_SHIFT_ASSIGNMENT:
                case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
                case AND_ASSIGNMENT:
                case XOR_ASSIGNMENT:
                case OR_ASSIGNMENT:
                    if (cs.alignMultilineAssignment()) {
                        int c = getColumn(tree);
                        return c < 0 ? currentIndent : c;
                    }
                    break;
                case AND:
                case CONDITIONAL_AND:
                case CONDITIONAL_OR:
                case DIVIDE:
                case EQUAL_TO:
                case GREATER_THAN:
                case GREATER_THAN_EQUAL:
                case LEFT_SHIFT:
                case LESS_THAN:
                case LESS_THAN_EQUAL:
                case MINUS:
                case MULTIPLY:
                case NOT_EQUAL_TO:
                case OR:
                case PLUS:
                case REMAINDER:
                case RIGHT_SHIFT:
                case UNSIGNED_RIGHT_SHIFT:
                case XOR:
                    if (cs.alignMultilineBinaryOp()) {
                        int c = getColumn(tree);
                        return c < 0 ? currentIndent : c;
                    }
                    break;
                case CONDITIONAL_EXPRESSION:
                    if (cs.alignMultilineTernaryOp()) {
                        int c = getColumn(tree);
                        return c < 0 ? currentIndent : c;
                    }
                    break;
            }
        }
        return currentIndent + cs.getContinuationIndentSize();
    }

    private int getStmtIndent(int startOffset, int endOffset, Set<JavaTokenId> expectedTokenIds, int expectedTokenOffset, int currentIndent) {
        TokenSequence<JavaTokenId> token = findFirstNonWhitespaceToken(startOffset, expectedTokenOffset);
        if (token != null && expectedTokenIds.contains(token.token().id())) {
            token = findFirstNonWhitespaceToken(startOffset, endOffset);
            if (token != null && token.token().id() == JavaTokenId.LBRACE) {
                switch (cs.getOtherBracePlacement()) {
                    case NEW_LINE_INDENTED:
                        currentIndent += cs.getIndentSize();
                        break;
                    case NEW_LINE_HALF_INDENTED:
                        currentIndent += (cs.getIndentSize() / 2);
                        break;
                }
            } else {
                currentIndent += cs.getIndentSize();
            }
        } else {
            currentIndent += cs.getContinuationIndentSize();
        }
        return currentIndent;
    }

    private int getMultilineIndent(List<? extends Tree> trees, LinkedList<? extends Tree> path, int commaOffset, int currentIndent, boolean align, boolean addContinuationIndent) throws BadLocationException {
        Tree tree = null;
        Tree first = null;
        for (Tree t : trees) {
            if (first == null) {
                first = t;
            }
            if (getEndPosition(t) > commaOffset) {
                break;
            }
            tree = t;
        }
        if (tree != null && findFirstNonWhitespaceToken(commaOffset, getEndPosition(tree)) == null) {
            int firstStartOffset = getStartPosition(first);
            int startOffset = first == tree ? firstStartOffset : getStartPosition(tree);
            if (firstStartOffset < 0 || startOffset < 0) {
                currentIndent = addContinuationIndent ? getContinuationIndent(path, currentIndent) : currentIndent + cs.getIndentSize();
            } else {
                int firstLineStartOffset = context.lineStartOffset(firstStartOffset);
                int lineStartOffset = firstStartOffset == startOffset ? firstLineStartOffset : context.lineStartOffset(startOffset);
                if (firstLineStartOffset != lineStartOffset) {
                    Integer newIndent = newIndents.get(lineStartOffset);
                    currentIndent = newIndent != null ? newIndent : context.lineIndent(lineStartOffset);
                } else if (align) {
                    currentIndent = getCol(context.document().getText(lineStartOffset, startOffset - lineStartOffset));
                } else {
                    currentIndent = addContinuationIndent ? getContinuationIndent(path, currentIndent) : currentIndent + cs.getIndentSize();
                }
            }
        } else {
            currentIndent = addContinuationIndent ? getContinuationIndent(path, currentIndent) : currentIndent + cs.getIndentSize();
        }
        return currentIndent;
    }

    private int getCol(String text) {
        int col = 0;
        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            if (c == '\t') {
                col += cs.getTabSize();
                col -= (col % cs.getTabSize());
            } else {
                col++;
            }
        }
        return col;
    }
    
    public static class Factory implements IndentTask.Factory {

        @Override
        public IndentTask createTask(Context context) {
            if (!NoJavacHelper.hasWorkingJavac())
                return null;
            return new Reindenter(context);
        }
    }
}
