//// [parserindenter.ts]
//﻿
// Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// 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.
//

///<reference path='formatting.ts' />


module Formatting {
    export class Indenter implements ILineIndenationResolver  {

        private indentationBag: IndentationBag;
        private scriptBlockBeginLineNumber: number;
        private offsetIndentationDeltas: Dictionary_int_int;

        constructor(
            public logger: TypeScript.ILogger,
            public tree: ParseTree,
            public snapshot: ITextSnapshot,
            public languageHostIndentation: string,
            public editorOptions: Services.EditorOptions,
            public firstToken: TokenSpan,
            public smartIndent: boolean) {

            this.indentationBag = new IndentationBag(this.snapshot);
            this.scriptBlockBeginLineNumber = -1;
            this.offsetIndentationDeltas = new Dictionary_int_int();     // text offset -> indentation delta

            // by default the root (program) has zero indendation
            this.tree.Root.SetIndentationOverride("");

            this.ApplyScriptBlockIndentation(this.languageHostIndentation, this.tree);
            this.FillInheritedIndentation(this.tree);

        }

        public GetIndentationEdits(token: TokenSpan, nextToken: TokenSpan, node: ParseNode, sameLineIndent: boolean): List_TextEditInfo {
            if (this.logger.information()) {
                this.logger.log("GetIndentationEdits(" +
                    "t1=[" + token.Span.startPosition() + "," + token.Span.endPosition()+ "], " +
                    "t2=[" + (nextToken == null ? "null" : (nextToken.Span.startPosition() + "," + nextToken.Span.endPosition())) + "]" +
                    ")");
            }

            var result = this.GetIndentationEditsWorker(token, nextToken, node, sameLineIndent);

            if (this.logger.information()) {
                for (var i = 0; i < result.count() ; i++) {
                    var edit = result.get(i);
                    this.logger.log("edit: minChar=" + edit.position + ", limChar=" + (edit.position + edit.length) + ", text=\"" + TypeScript.stringToLiteral(edit.replaceWith, 30) + "\"");
                }
            }

            return result;
        }

        public GetIndentationEditsWorker(token: TokenSpan, nextToken: TokenSpan, node: ParseNode, sameLineIndent: boolean): List_TextEditInfo {
            var result = new List_TextEditInfo();
            var indentationInfo: IndentationInfo = null;

            // This handles the case:
            //      return (
            //              function() {
            //              })
            // The given function's node indicates that the function starts directly after "return (".
            // In this case, we adjust the span to point to the function keyword.
            // The same applies to objects and arrays.
            // The reason this is done inside the Indenter is because it only affects indentation behavior.
            // It's also done in ParseTree when we traverse up the tree because we don't have the 
            // tokens for nodes outside the span we are formatting.
            this.AdjustStartOffsetIfNeeded(token, node);

            // Don't adjust indentation on the same line of a script block
            if (this.scriptBlockBeginLineNumber == token.lineNumber()) {
                return result;
            }

            // Don't indent multi-line strings
            if (!sameLineIndent && this.IsMultiLineString(token)) {
                return result;
            }

            // Special cases for the tokens that don't show up in the tree, such as curly braces and comments
            indentationInfo = this.GetSpecialCaseIndentation(token, node);
            if (indentationInfo == null) {
                //// For anything else

                // Get the indentation level only from the node that starts on the same offset as the token
                // otherwise the token is not meant to be indented
                while (!node.CanIndent() && node.Parent != null && token.Span.span.start() == node.Parent.AuthorNode.Details.StartOffset)
                    node = node.Parent;

                if (node.CanIndent() && token.Span.span.start() == node.AuthorNode.Details.StartOffset) {
                    indentationInfo = node.GetEffectiveIndentation(this);
                }
                else {
                    //// Special cases for anything else that is not in the tree and should be indented

                    // check for label (identifier followed by a colon)
                    if (token.Token == AuthorTokenKind.atkIdentifier && nextToken != null && nextToken.Token == AuthorTokenKind.atkColon) {
                        // This will make the label on the same level as the surrounding function/block
                        // ex: 
                        // {
                        //      statement;
                        //      label:
                        //          statement;
                        // }
                        indentationInfo = node.GetEffectiveChildrenIndentation(this);
                    }
                    else {
                        //// Move the token the same indentation-delta that moved its indentable parent
                        //// For example:
                        ////    var a,
                        ////        b;
                        //// The declaration 'b' would remain under 'a' even if 'var' got indented.
                        indentationInfo = this.ApplyIndentationDeltaFromParent(token, node);
                    }
                }
            }

            // Get the indent edit from the indentation info
            if (indentationInfo != null) {
                var edit = this.GetIndentEdit(indentationInfo, token.Span.startPosition(), sameLineIndent);
                if (edit != null) {
                    this.RegisterIndentation(edit, sameLineIndent);

                    result.add(edit);

                    // multi-line comments, apply delta indentation to all the other lines
                    if (token.Token == AuthorTokenKind.atkComment) {
                        var commentEdits = this.GetCommentIndentationEdits(token);
                        commentEdits.foreach((item) => {
                            result.add(item);
                        });
                    }
                }
            }

            return result;
        }

        private GetCommentIndentationEdits(token: TokenSpan): List_TextEditInfo {
            var result = new List_TextEditInfo();

            if (token.Token != AuthorTokenKind.atkComment)
                return result;

            var commentLastLineNumber = this.snapshot.GetLineNumberFromPosition(token.Span.endPosition());
            if (token.lineNumber() == commentLastLineNumber)
                return result;

            var commentFirstLineIndentationDelta = this.GetIndentationDelta(token.Span.startPosition(), null);
            if (commentFirstLineIndentationDelta != undefined) {
                for (var line = token.lineNumber() + 1; line <= commentLastLineNumber; line++) {
                    var lineStartPosition = this.snapshot.GetLineFromLineNumber(line).startPosition();
                    var lineIndent = this.GetLineIndentationForOffset(lineStartPosition);

                    var commentIndentationInfo = this.ApplyIndentationDelta2(lineIndent, commentFirstLineIndentationDelta);
                    if (commentIndentationInfo != null) {
                        var tokenStartPosition = lineStartPosition + lineIndent.length;
                        var commentIndentationEdit = this.GetIndentEdit(commentIndentationInfo, tokenStartPosition, false);
                        if (commentIndentationEdit != null) {
                            result.add(commentIndentationEdit);
                        }
                    }
                }
            }

            return result;
        }

        static GetIndentSizeFromIndentText(indentText: string, editorOptions: Services.EditorOptions): number {
            return GetIndentSizeFromText(indentText, editorOptions, /*includeNonIndentChars:*/ false);
        }

        static GetIndentSizeFromText(text: string, editorOptions: Services.EditorOptions, includeNonIndentChars: boolean): number {
            var indentSize = 0;

            for (var i = 0; i < text.length; i++) {
                var c = text.charAt(i);

                if (c == '\t')
                    indentSize = (indentSize + editorOptions.TabSize) - (indentSize % editorOptions.TabSize);
                else if (c == ' ')
                    indentSize += 1;
                else {
                    if (includeNonIndentChars)
                        indentSize += 1;
                    else
                        break;
                }
            }

            return indentSize;
        }

        private GetSpecialCaseIndentation(token: TokenSpan, node: ParseNode): IndentationInfo {
            var indentationInfo: IndentationInfo = null;

            switch (token.Token) {
                case AuthorTokenKind.atkLCurly: // { is not part of the tree
                    indentationInfo = this.GetSpecialCaseIndentationForLCurly(node);
                    return indentationInfo;

                case AuthorTokenKind.atkElse:   // else is not part of the tree
                case AuthorTokenKind.atkRBrack: // ] is not part of the tree
                    indentationInfo = node.GetNodeStartLineIndentation(this);
                    return indentationInfo;

                case AuthorTokenKind.atkRCurly: // } is not part of the tree
                    // if '}' is for a body-block, get indentation based on its parent.
                    if (node.AuthorNode.Details.Kind == AuthorParseNodeKind.apnkBlock && node.AuthorNode.EdgeLabel == AuthorParseNodeEdge.apneBody)
                        node = node.Parent;
                    indentationInfo = node.GetNodeStartLineIndentation(this);
                    return indentationInfo;

                case AuthorTokenKind.atkWhile: // while (in do-while) is not part of the tree
                    if (node.AuthorNode.Details.Kind == AuthorParseNodeKind.apnkDoWhile) {
                        indentationInfo = node.GetNodeStartLineIndentation(this);
                        return indentationInfo;
                    }

                    return null;

                case AuthorTokenKind.atkSColon:
                    return this.GetSpecialCaseIndentationForSemicolon(token, node);

                case AuthorTokenKind.atkComment:
                    return this.GetSpecialCaseIndentationForComment(token, node);

                default:
                    return indentationInfo;
            }
        }

        private GetSpecialCaseIndentationForLCurly(node: ParseNode): IndentationInfo {
            var indentationInfo: IndentationInfo = null;

            if (node.AuthorNode.Details.Kind == AuthorParseNodeKind.apnkFncDecl ||
                node.AuthorNode.EdgeLabel == AuthorParseNodeEdge.apneThen || node.AuthorNode.EdgeLabel == AuthorParseNodeEdge.apneElse) {
                    // flushed with the node (function & if)
                indentationInfo = node.GetNodeStartLineIndentation(this);
                return indentationInfo;
            }
            else if (node.AuthorNode.Details.Kind == AuthorParseNodeKind.apnkObject && !node.CanIndent()) {
                // if the open curly belongs to a non-indented object, do nothing here.
                return null;
            }

            // effective identation of the block
            indentationInfo = node.GetEffectiveIndentation(this);
            return indentationInfo;
        }

        private GetSpecialCaseIndentationForSemicolon(token: TokenSpan, node: ParseNode): IndentationInfo {
            var indentationInfo: IndentationInfo = null;

            if (this.smartIndent) {
                indentationInfo = node.GetEffectiveChildrenIndentation(this);
                return indentationInfo;
            }
            else {
                // Indent all semicolons except the ones that belong to the for statement parts (initalizer, condition, itnrement)
                if (node.AuthorNode.Details.Kind != AuthorParseNodeKind.apnkFor) {
                    // The passed node is actually either the program or the list because semicolon doesn't belong
                    // to any statement in the tree, though the statement extends up to the semicolon position.
                    // To find the correct statement, we look for the adjacent node on the left of the semicolon.
                    var semiColonStartSpan = new Span(token.Span.startPosition(), 0);
                    node = ParseTree.FindCommonParentNode(semiColonStartSpan, semiColonStartSpan, node);
                    indentationInfo = node.GetEffectiveChildrenIndentation(this);
                    return indentationInfo;
                }
            }

            return null;
        }

        private GetSpecialCaseIndentationForComment(token: TokenSpan, node: ParseNode): IndentationInfo {
            var indentationInfo: IndentationInfo = null;

            // Only indent line comment and the first line of block comment
            var twoCharSpan = token.Span.Intersection(new Span(token.Span.startPosition(), 2));
            if (twoCharSpan != null && (twoCharSpan.GetText() == "//" || twoCharSpan.GetText() == "/*")) {
                while (node.ChildrenIndentationDelta == null && node.Parent != null)
                    node = node.Parent;

                if (this.CanIndentComment(token, node)) {
                    indentationInfo = node.GetEffectiveChildrenIndentationForComment(this);
                }
                else {
                    indentationInfo = this.ApplyIndentationDeltaFromParent(token, node);
                }
            }

            return indentationInfo;
        }

        private CanIndentComment(token: TokenSpan, node: ParseNode): boolean {
            switch (node.AuthorNode.Details.Kind) {
                case AuthorParseNodeKind.apnkProg:
                case AuthorParseNodeKind.apnkBlock:
                case AuthorParseNodeKind.apnkSwitch:
                case AuthorParseNodeKind.apnkCase:
                case AuthorParseNodeKind.apnkDefaultCase:
                case AuthorParseNodeKind.apnkIf:
                case AuthorParseNodeKind.apnkFor:
                case AuthorParseNodeKind.apnkForIn:
                case AuthorParseNodeKind.apnkWhile:
                case AuthorParseNodeKind.apnkWith:
                case AuthorParseNodeKind.apnkDoWhile:
                case AuthorParseNodeKind.apnkObject:
                    return true;

                case AuthorParseNodeKind.apnkFncDecl:
                    // Comments before arguments are not indented.
                    // This code doesn't cover the cases of comment after the last argument or 
                    // when there are no arguments. Though this is okay since the only case we care about is:
                    // function foo(/* test */ a,
                    //              /* test */ b)
                    var result = true;
                    var children = ParseNodeExtensions.FindChildrenWithEdge(node, AuthorParseNodeEdge.apneArgument);
                    children.foreach((argumentNode) => {
                        if (token.Span.startPosition() < argumentNode.AuthorNode.Details.StartOffset)
                            result = false;
                    });

                    return result;
            }

            return false;
        }

        private ApplyScriptBlockIndentation(languageHostIndentation: string, tree: ParseTree): void
        {
            if (languageHostIndentation == null || tree.StartNodeSelf == null)
                return;

            var scriptBlockIndentation = this.ApplyIndentationLevel(languageHostIndentation, 1);

            //TypeScript: Projection snapshots not supported

            // Disconnect the sibling node if it belongs to a different script block
            //IProjectionSnapshot projectionSnapshot = this.snapshot as IProjectionSnapshot;
            //if (projectionSnapshot != null)
            //{
            //    // Get script block spans.
            //    foreach (SnapshotSpan sourceSpan in projectionSnapshot.GetSourceSpans())
            //    {
            //        // Map the spans to the JavaScript buffer.
            //        ReadOnlyCollection<Span> spans = projectionSnapshot.MapFromSourceSnapshot(sourceSpan);

            //        Debug.Assert(spans.Count == 1, string.Format(CultureInfo.InvariantCulture, "Unexpected span count of {0}.", spans.Count));

            //        if (spans.Count > 0)
            //        {
            //            Span span = spans.First();

            //            // If the "self" node is the first root-level node in a script block, then remove the start node.
            //            if (span.Contains(tree.StartNodethis.AuthorNode.Details.StartOffset))
            //            {
            //                this.scriptBlockBeginLineNumber = projectionSnapshot.GetLineNumberFromPosition(span.Start);

            //                if (tree.StartNodePreviousSibling.HasValue)
            //                {
            //                    int siblingStartOffset = tree.StartNodePreviousSibling.Value.Details.StartOffset;

            //                    // Don't consider sibling in these cases:
            //                    // 1. The sibling belongs to another script block
            //                    // 2. The sibling is on the same line of the script block
            //                    if (!span.Contains(siblingStartOffset) || projectionSnapshot.GetLineNumberFromPosition(siblingStartOffset) == this.scriptBlockBeginLineNumber)
            //                    {
            //                        tree.StartNodePreviousSibling = null;
            //                    }
            //                }

            //                break;
            //            }
            //        }
            //    }
            //}

            // The root is the program.
            tree.Root.SetIndentationOverride(scriptBlockIndentation);
        }

        private GetIndentEdit(indentInfo: IndentationInfo, tokenStartPosition: number, sameLineIndent: boolean): TextEditInfo {
            var indentText = this.ApplyIndentationLevel(indentInfo.Prefix, indentInfo.Level);

            if (sameLineIndent) {
                return new TextEditInfo(tokenStartPosition, 0, indentText);
            }
            else {
                var snapshotLine = this.snapshot.GetLineFromPosition(tokenStartPosition);
                var currentIndentSpan = new Span(snapshotLine.startPosition(), tokenStartPosition - snapshotLine.startPosition());
                var currentIndentText = this.snapshot.GetText(currentIndentSpan);

                if (currentIndentText !== indentText) {
                    if (this.logger.debug()) {
                        // Verify that currentIndentText is all whitespaces
                        for (var i = 0, len = currentIndentText.length; i < len; i++) {
                            var c = currentIndentText.charCodeAt(i);
                            if (!StringUtils.IsWhiteSpace(c)) {
                                Debug.Fail("Formatting error: Will remove user code when indenting the line: " + snapshotLine.getText());
                                break;
                            }
                        }
                    }
                    return new TextEditInfo(currentIndentSpan.start(), currentIndentSpan.length(), indentText);
                }
            }

            return null;
        }

        private ApplyIndentationLevel(existingIndentation: string, level: number): string {
            var indentSize = this.editorOptions.IndentSize;
            var tabSize = this.editorOptions.TabSize;
            var convertTabsToSpaces = this.editorOptions.ConvertTabsToSpaces;

            if (level < 0) {
                if (StringUtils.IsNullOrEmpty(existingIndentation))
                    return "";

                var totalIndent = 0;
                StringUtils.foreach(existingIndentation, (c) => {
                    if (c == '\t')
                        totalIndent += tabSize;
                    else
                        totalIndent++;
                });

                totalIndent += level * indentSize;
                if (totalIndent < 0)
                    return "";
                else
                    return this.GetIndentString(null, totalIndent, tabSize, convertTabsToSpaces);
            }

            var totalIndentSize = level * indentSize;
            return this.GetIndentString(existingIndentation, totalIndentSize, tabSize, convertTabsToSpaces);
        }

        private GetIndentString(prefix: string, totalIndentSize: number, tabSize: number, convertTabsToSpaces: boolean): string {
            var tabString = convertTabsToSpaces ? StringUtils.create(' ', tabSize) : "\t";

            var text = "";
            if (!StringUtils.IsNullOrEmpty(prefix))
                text += prefix;

            var pos = 0;

            // fill first with tabs
            while (pos <= totalIndentSize - tabSize) {
                text += tabString;
                pos += tabSize;
            }

            // fill the reminder with spaces
            while (pos < totalIndentSize) {
                text += ' ';
                pos++;
            }

            return text;
        }

        private  ApplyIndentationDeltaFromParent(token: TokenSpan, node: ParseNode): IndentationInfo {
            var indentationInfo: IndentationInfo = null;

            var indentableParent = node;
            while (indentableParent != null && !indentableParent.CanIndent())
                indentableParent = indentableParent.Parent;

            if (indentableParent != null && indentableParent.AuthorNode.Details.Kind != AuthorParseNodeKind.apnkProg) {
                var parentIndentationDeltaSize = this.GetIndentationDelta(indentableParent.AuthorNode.Details.StartOffset, token.Span.startPosition());
                if (parentIndentationDeltaSize !== undefined) {
                    indentationInfo = this.ApplyIndentationDelta1(token.Span.startPosition(), parentIndentationDeltaSize);
                }
            }

            return indentationInfo;
        }

        private ApplyIndentationDelta1(tokenStartPosition: number, delta: number): IndentationInfo {
            // Get current indentation
            var snapshotLine = this.snapshot.GetLineFromPosition(tokenStartPosition);
            var currentIndentSpan = new Span(snapshotLine.startPosition(), tokenStartPosition - snapshotLine.startPosition());
            var currentIndent = this.snapshot.GetText(currentIndentSpan);

            // Calculate new indentation from current-indentation and delta
            return this.ApplyIndentationDelta2(currentIndent, delta);
        }

        private ApplyIndentationDelta2(currentIndent: string, delta: number): IndentationInfo {
            if (delta == 0)
                return null;

            var currentIndentSize = Indenter.GetIndentSizeFromIndentText(currentIndent, this.editorOptions);

            var newIndentSize = currentIndentSize + delta;
            if (newIndentSize < 0) {
                newIndentSize = 0;
            }

            var newIndent = this.GetIndentString(null, newIndentSize, this.editorOptions.TabSize, this.editorOptions.ConvertTabsToSpaces);
            if (newIndent != null) {
                return new IndentationInfo(newIndent, 0);
            }

            return null;
        }

        private GetIndentationDelta(tokenStartPosition: number, childTokenStartPosition: number/*?*/): number/*?*/ {
            Debug.Assert(childTokenStartPosition !== undefined, "Error: caller must pass 'null' for undefined position");

            var indentationDeltaSize = this.offsetIndentationDeltas.GetValue(tokenStartPosition);
            if (indentationDeltaSize === null) {
                var indentEditInfo = this.indentationBag.FindIndent(tokenStartPosition);

                // No recorded indentation, return null
                if (indentEditInfo == null)
                    return null;

                var origIndentText = this.snapshot.GetText(new Span(indentEditInfo.OrigIndentPosition, indentEditInfo.OrigIndentLength()));
                var newIndentText = indentEditInfo.Indentation();

                var origIndentSize = Indenter.GetIndentSizeFromText(origIndentText, this.editorOptions, /*includeNonIndentChars*/true);
                var newIndentSize = Indenter.GetIndentSizeFromIndentText(newIndentText, this.editorOptions);

                // Check the child's position whether it's before the parent position
                // if so indent the child based on the first token on the line as opposed to the parent position
                //
                // Example of relative to parent (not line), relative indentation should be "4 (newIndentSize) - 9 (indentSize up to for) = -5"
                //
                // if (1) { for (i = 0; i < 10;       =>          if (1) {
                //                      i++) {                       for (i = 0; i < 10;
                //                                                               i++) {
                //
                // Example of relative to line, relative indentation should be "4 (newIndentSize) - 0 (indentSize up to if) = 4"
                //
                // if (1) { for (i = 0; i < 10;      =>          if (1) {
                //     i++) {                                        for (i = 0; i < 10;
                //                                                       i++) {
                if (childTokenStartPosition !== null) {
                    var childTokenLineStartPosition = this.snapshot.GetLineFromPosition(childTokenStartPosition).startPosition();
                    var childIndentText = this.snapshot.GetText(new Span(childTokenLineStartPosition, childTokenStartPosition - childTokenLineStartPosition));

                    var childIndentSize = Indenter.GetIndentSizeFromIndentText(childIndentText, this.editorOptions);

                    if (childIndentSize < origIndentSize)
                        origIndentSize = Indenter.GetIndentSizeFromIndentText(origIndentText, this.editorOptions);
                }

                indentationDeltaSize = newIndentSize - origIndentSize;
                this.offsetIndentationDeltas.Add(tokenStartPosition, indentationDeltaSize);
            }

            return indentationDeltaSize;
        }

        private FillInheritedIndentation(tree: ParseTree): void
        {
            var offset = -1;
            var indentNode: ParseNode = null;

            if (tree.StartNodeSelf != null) {
                if (!this.smartIndent && tree.StartNodePreviousSibling !== null && tree.StartNodeSelf.AuthorNode.Label == 0 && tree.StartNodePreviousSibling.Label == 0) {
                    indentNode = tree.StartNodeSelf;
                    offset = tree.StartNodePreviousSibling.Details.StartOffset;

                    // In case the sibling node is on the same line of a parent node, ex:
                    //      case 1: a++;
                    //          break;
                    // In this example, the sibling of break is a++ but a++ is on the same line of its parent.
                    var lineNum = this.snapshot.GetLineNumberFromPosition(offset);
                    var node = indentNode;
                    while (node.Parent != null && this.snapshot.GetLineNumberFromPosition(node.Parent.AuthorNode.Details.StartOffset) == lineNum) {
                        node = node.Parent;
                        if (node.CanIndent()) {
                            indentNode = node;
                            indentNode.IndentationDelta = 0;
                        }
                    }
                }
                else {
                    var parent: ParseNode;

                    // Otherwise base on parent indentation.
                    if (this.smartIndent) {
                        // in smartIndent the self node is the parent node since it's the closest node to the new line
                        // ... unless in case if the startNodeSelf represents the firstToken then we need to choose its parent
                        parent = tree.StartNodeSelf;
                        while (parent != null && parent.AuthorNode.Details.StartOffset == this.firstToken.Span.startPosition())
                            parent = parent.Parent;
                    }
                    else {
                        // Get the parent that is really on a different line from the self node
                        var startNodeLineNumber = this.snapshot.GetLineNumberFromPosition(tree.StartNodeSelf.AuthorNode.Details.StartOffset);
                        parent = tree.StartNodeSelf.Parent;
                        while (parent != null &&
                                startNodeLineNumber == this.snapshot.GetLineNumberFromPosition(parent.AuthorNode.Details.StartOffset)) {
                            parent = parent.Parent;
                        }
                    }

                    // The parent node to take its indentation is the first parent that has indentation.
                    while (parent != null && !parent.CanIndent()) {
                        parent = parent.Parent;
                    }

                    // Skip Program since it has no indentation
                    if (parent != null && parent.AuthorNode.Details.Kind != AuthorParseNodeKind.apnkProg) {
                        offset = parent.AuthorNode.Details.StartOffset;
                        indentNode = parent;
                    }
                }
            }

            if (indentNode != null) {
                var indentOverride = this.GetLineIndentationForOffset(offset);

                // Set the indentation on all the siblings to be the same as indentNode
                if (!this.smartIndent && tree.StartNodePreviousSibling !== null && indentNode.Parent != null) {
                    ParseNodeExtensions.GetChildren(indentNode.Parent).foreach((sibling) => {
                        if (sibling !== indentNode) {
                            if (sibling.CanIndent())
                                sibling.SetIndentationOverride(indentOverride);
                        }
                    });
                }

                // Set the indent override string on the indent node and on every parent (on different line) after adjusting the indent by the negative delta
                var lastDelta = 0;
                var lastLine = this.snapshot.GetLineNumberFromPosition(indentNode.AuthorNode.Details.StartOffset);
                do {
                    var currentLine = this.snapshot.GetLineNumberFromPosition(indentNode.AuthorNode.Details.StartOffset);
                    if (lastLine != currentLine) {
                        lastLine = currentLine;
                        indentOverride = this.ApplyIndentationLevel(indentOverride, -lastDelta);
                        lastDelta = 0;
                    }

                    if (indentNode.CanIndent()) {
                        indentNode.SetIndentationOverride(indentOverride);
                        lastDelta = indentNode.IndentationDelta;
                    }

                    indentNode = indentNode.Parent;
                }
                while (indentNode != null);
            }
        }

        public GetLineIndentationForOffset(offset: number): string {
            var indentationEdit: IndentationEditInfo;

            // First check if we already have indentation info in our indentation bag
            indentationEdit = this.indentationBag.FindIndent(offset);
            if (indentationEdit != null) {
                return indentationEdit.Indentation();
            }
            else {
                // Otherwise, use the indentation from the textBuffer
                var line = this.snapshot.GetLineFromPosition(offset);
                var lineText = line.getText();
                var index = 0;

                while (index < lineText.length && (lineText.charAt(index) == ' ' || lineText.charAt(index) == '\t')) {
                    ++index;
                }

                return lineText.substr(0, index);
            }
        }

        private RegisterIndentation(indent: TextEditInfo, sameLineIndent: boolean): void
        {
            var indentationInfo: IndentationEditInfo = null;

            if (sameLineIndent) {
                // Consider the original indentation from the beginning of the line up to the indent position (or really the token position)
                var lineStartPosition = this.snapshot.GetLineFromPosition(indent.Position).startPosition();
                var lineIndentLength = indent.Position - lineStartPosition;

                indentationInfo = IndentationEditInfo.create2(indent.Position, indent.ReplaceWith, lineStartPosition, lineIndentLength);
            }
            else {
                indentationInfo = new IndentationEditInfo(indent);
            }

            this.indentationBag.AddIndent(indentationInfo);
        }

        public RegisterIndentation2(position: number, indent: string): void
        {
            this.RegisterIndentation(new TextEditInfo(position, 0, indent), false);
        }

        private AdjustStartOffsetIfNeeded(token: TokenSpan, node: ParseNode): void
        {
            if (token == null)
                return;

            var updateStartOffset = false;

            switch (token.Token) {
                case AuthorTokenKind.atkFunction:
                    updateStartOffset = node.AuthorNode.Details.Kind == AuthorParseNodeKind.apnkFncDecl;
                    break;

                case AuthorTokenKind.atkLCurly:
                    updateStartOffset = node.AuthorNode.Details.Kind == AuthorParseNodeKind.apnkObject;
                    break;

                case AuthorTokenKind.atkLBrack:
                    updateStartOffset = node.AuthorNode.Details.Kind == AuthorParseNodeKind.apnkArray;
                    break;
            }

            if (updateStartOffset) {
                ParseNodeExtensions.SetNodeSpan(node, token.Span.startPosition(), node.AuthorNode.Details.EndOffset);
            }
        }

        private IsMultiLineString(token: TokenSpan): boolean {
            return token.tokenID === TypeScript.TokenID.StringLiteral &&
                this.snapshot.GetLineNumberFromPosition(token.Span.endPosition()) > this.snapshot.GetLineNumberFromPosition(token.Span.startPosition());
        }
    }
}


//// [parserindenter.js]
//﻿
// Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// 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.
//
///<reference path='formatting.ts' />
var Formatting;
(function (Formatting) {
    var Indenter = (function () {
        function Indenter(logger, tree, snapshot, languageHostIndentation, editorOptions, firstToken, smartIndent) {
            this.logger = logger;
            this.tree = tree;
            this.snapshot = snapshot;
            this.languageHostIndentation = languageHostIndentation;
            this.editorOptions = editorOptions;
            this.firstToken = firstToken;
            this.smartIndent = smartIndent;
            this.indentationBag = new IndentationBag(this.snapshot);
            this.scriptBlockBeginLineNumber = -1;
            this.offsetIndentationDeltas = new Dictionary_int_int(); // text offset -> indentation delta
            // by default the root (program) has zero indendation
            this.tree.Root.SetIndentationOverride("");
            this.ApplyScriptBlockIndentation(this.languageHostIndentation, this.tree);
            this.FillInheritedIndentation(this.tree);
        }
        Indenter.prototype.GetIndentationEdits = function (token, nextToken, node, sameLineIndent) {
            if (this.logger.information()) {
                this.logger.log("GetIndentationEdits(" +
                    "t1=[" + token.Span.startPosition() + "," + token.Span.endPosition() + "], " +
                    "t2=[" + (nextToken == null ? "null" : (nextToken.Span.startPosition() + "," + nextToken.Span.endPosition())) + "]" +
                    ")");
            }
            var result = this.GetIndentationEditsWorker(token, nextToken, node, sameLineIndent);
            if (this.logger.information()) {
                for (var i = 0; i < result.count(); i++) {
                    var edit = result.get(i);
                    this.logger.log("edit: minChar=" + edit.position + ", limChar=" + (edit.position + edit.length) + ", text=\"" + TypeScript.stringToLiteral(edit.replaceWith, 30) + "\"");
                }
            }
            return result;
        };
        Indenter.prototype.GetIndentationEditsWorker = function (token, nextToken, node, sameLineIndent) {
            var result = new List_TextEditInfo();
            var indentationInfo = null;
            // This handles the case:
            //      return (
            //              function() {
            //              })
            // The given function's node indicates that the function starts directly after "return (".
            // In this case, we adjust the span to point to the function keyword.
            // The same applies to objects and arrays.
            // The reason this is done inside the Indenter is because it only affects indentation behavior.
            // It's also done in ParseTree when we traverse up the tree because we don't have the 
            // tokens for nodes outside the span we are formatting.
            this.AdjustStartOffsetIfNeeded(token, node);
            // Don't adjust indentation on the same line of a script block
            if (this.scriptBlockBeginLineNumber == token.lineNumber()) {
                return result;
            }
            // Don't indent multi-line strings
            if (!sameLineIndent && this.IsMultiLineString(token)) {
                return result;
            }
            // Special cases for the tokens that don't show up in the tree, such as curly braces and comments
            indentationInfo = this.GetSpecialCaseIndentation(token, node);
            if (indentationInfo == null) {
                //// For anything else
                // Get the indentation level only from the node that starts on the same offset as the token
                // otherwise the token is not meant to be indented
                while (!node.CanIndent() && node.Parent != null && token.Span.span.start() == node.Parent.AuthorNode.Details.StartOffset)
                    node = node.Parent;
                if (node.CanIndent() && token.Span.span.start() == node.AuthorNode.Details.StartOffset) {
                    indentationInfo = node.GetEffectiveIndentation(this);
                }
                else {
                    //// Special cases for anything else that is not in the tree and should be indented
                    // check for label (identifier followed by a colon)
                    if (token.Token == AuthorTokenKind.atkIdentifier && nextToken != null && nextToken.Token == AuthorTokenKind.atkColon) {
                        // This will make the label on the same level as the surrounding function/block
                        // ex: 
                        // {
                        //      statement;
                        //      label:
                        //          statement;
                        // }
                        indentationInfo = node.GetEffectiveChildrenIndentation(this);
                    }
                    else {
                        //// Move the token the same indentation-delta that moved its indentable parent
                        //// For example:
                        ////    var a,
                        ////        b;
                        //// The declaration 'b' would remain under 'a' even if 'var' got indented.
                        indentationInfo = this.ApplyIndentationDeltaFromParent(token, node);
                    }
                }
            }
            // Get the indent edit from the indentation info
            if (indentationInfo != null) {
                var edit = this.GetIndentEdit(indentationInfo, token.Span.startPosition(), sameLineIndent);
                if (edit != null) {
                    this.RegisterIndentation(edit, sameLineIndent);
                    result.add(edit);
                    // multi-line comments, apply delta indentation to all the other lines
                    if (token.Token == AuthorTokenKind.atkComment) {
                        var commentEdits = this.GetCommentIndentationEdits(token);
                        commentEdits.foreach(function (item) {
                            result.add(item);
                        });
                    }
                }
            }
            return result;
        };
        Indenter.prototype.GetCommentIndentationEdits = function (token) {
            var result = new List_TextEditInfo();
            if (token.Token != AuthorTokenKind.atkComment)
                return result;
            var commentLastLineNumber = this.snapshot.GetLineNumberFromPosition(token.Span.endPosition());
            if (token.lineNumber() == commentLastLineNumber)
                return result;
            var commentFirstLineIndentationDelta = this.GetIndentationDelta(token.Span.startPosition(), null);
            if (commentFirstLineIndentationDelta != undefined) {
                for (var line = token.lineNumber() + 1; line <= commentLastLineNumber; line++) {
                    var lineStartPosition = this.snapshot.GetLineFromLineNumber(line).startPosition();
                    var lineIndent = this.GetLineIndentationForOffset(lineStartPosition);
                    var commentIndentationInfo = this.ApplyIndentationDelta2(lineIndent, commentFirstLineIndentationDelta);
                    if (commentIndentationInfo != null) {
                        var tokenStartPosition = lineStartPosition + lineIndent.length;
                        var commentIndentationEdit = this.GetIndentEdit(commentIndentationInfo, tokenStartPosition, false);
                        if (commentIndentationEdit != null) {
                            result.add(commentIndentationEdit);
                        }
                    }
                }
            }
            return result;
        };
        Indenter.GetIndentSizeFromIndentText = function (indentText, editorOptions) {
            return GetIndentSizeFromText(indentText, editorOptions, /*includeNonIndentChars:*/ false);
        };
        Indenter.GetIndentSizeFromText = function (text, editorOptions, includeNonIndentChars) {
            var indentSize = 0;
            for (var i = 0; i < text.length; i++) {
                var c = text.charAt(i);
                if (c == '\t')
                    indentSize = (indentSize + editorOptions.TabSize) - (indentSize % editorOptions.TabSize);
                else if (c == ' ')
                    indentSize += 1;
                else {
                    if (includeNonIndentChars)
                        indentSize += 1;
                    else
                        break;
                }
            }
            return indentSize;
        };
        Indenter.prototype.GetSpecialCaseIndentation = function (token, node) {
            var indentationInfo = null;
            switch (token.Token) {
                case AuthorTokenKind.atkLCurly:
                    indentationInfo = this.GetSpecialCaseIndentationForLCurly(node);
                    return indentationInfo;
                case AuthorTokenKind.atkElse: // else is not part of the tree
                case AuthorTokenKind.atkRBrack:
                    indentationInfo = node.GetNodeStartLineIndentation(this);
                    return indentationInfo;
                case AuthorTokenKind.atkRCurly:
                    // if '}' is for a body-block, get indentation based on its parent.
                    if (node.AuthorNode.Details.Kind == AuthorParseNodeKind.apnkBlock && node.AuthorNode.EdgeLabel == AuthorParseNodeEdge.apneBody)
                        node = node.Parent;
                    indentationInfo = node.GetNodeStartLineIndentation(this);
                    return indentationInfo;
                case AuthorTokenKind.atkWhile:
                    if (node.AuthorNode.Details.Kind == AuthorParseNodeKind.apnkDoWhile) {
                        indentationInfo = node.GetNodeStartLineIndentation(this);
                        return indentationInfo;
                    }
                    return null;
                case AuthorTokenKind.atkSColon:
                    return this.GetSpecialCaseIndentationForSemicolon(token, node);
                case AuthorTokenKind.atkComment:
                    return this.GetSpecialCaseIndentationForComment(token, node);
                default:
                    return indentationInfo;
            }
        };
        Indenter.prototype.GetSpecialCaseIndentationForLCurly = function (node) {
            var indentationInfo = null;
            if (node.AuthorNode.Details.Kind == AuthorParseNodeKind.apnkFncDecl ||
                node.AuthorNode.EdgeLabel == AuthorParseNodeEdge.apneThen || node.AuthorNode.EdgeLabel == AuthorParseNodeEdge.apneElse) {
                // flushed with the node (function & if)
                indentationInfo = node.GetNodeStartLineIndentation(this);
                return indentationInfo;
            }
            else if (node.AuthorNode.Details.Kind == AuthorParseNodeKind.apnkObject && !node.CanIndent()) {
                // if the open curly belongs to a non-indented object, do nothing here.
                return null;
            }
            // effective identation of the block
            indentationInfo = node.GetEffectiveIndentation(this);
            return indentationInfo;
        };
        Indenter.prototype.GetSpecialCaseIndentationForSemicolon = function (token, node) {
            var indentationInfo = null;
            if (this.smartIndent) {
                indentationInfo = node.GetEffectiveChildrenIndentation(this);
                return indentationInfo;
            }
            else {
                // Indent all semicolons except the ones that belong to the for statement parts (initalizer, condition, itnrement)
                if (node.AuthorNode.Details.Kind != AuthorParseNodeKind.apnkFor) {
                    // The passed node is actually either the program or the list because semicolon doesn't belong
                    // to any statement in the tree, though the statement extends up to the semicolon position.
                    // To find the correct statement, we look for the adjacent node on the left of the semicolon.
                    var semiColonStartSpan = new Span(token.Span.startPosition(), 0);
                    node = ParseTree.FindCommonParentNode(semiColonStartSpan, semiColonStartSpan, node);
                    indentationInfo = node.GetEffectiveChildrenIndentation(this);
                    return indentationInfo;
                }
            }
            return null;
        };
        Indenter.prototype.GetSpecialCaseIndentationForComment = function (token, node) {
            var indentationInfo = null;
            // Only indent line comment and the first line of block comment
            var twoCharSpan = token.Span.Intersection(new Span(token.Span.startPosition(), 2));
            if (twoCharSpan != null && (twoCharSpan.GetText() == "//" || twoCharSpan.GetText() == "/*")) {
                while (node.ChildrenIndentationDelta == null && node.Parent != null)
                    node = node.Parent;
                if (this.CanIndentComment(token, node)) {
                    indentationInfo = node.GetEffectiveChildrenIndentationForComment(this);
                }
                else {
                    indentationInfo = this.ApplyIndentationDeltaFromParent(token, node);
                }
            }
            return indentationInfo;
        };
        Indenter.prototype.CanIndentComment = function (token, node) {
            switch (node.AuthorNode.Details.Kind) {
                case AuthorParseNodeKind.apnkProg:
                case AuthorParseNodeKind.apnkBlock:
                case AuthorParseNodeKind.apnkSwitch:
                case AuthorParseNodeKind.apnkCase:
                case AuthorParseNodeKind.apnkDefaultCase:
                case AuthorParseNodeKind.apnkIf:
                case AuthorParseNodeKind.apnkFor:
                case AuthorParseNodeKind.apnkForIn:
                case AuthorParseNodeKind.apnkWhile:
                case AuthorParseNodeKind.apnkWith:
                case AuthorParseNodeKind.apnkDoWhile:
                case AuthorParseNodeKind.apnkObject:
                    return true;
                case AuthorParseNodeKind.apnkFncDecl:
                    // Comments before arguments are not indented.
                    // This code doesn't cover the cases of comment after the last argument or 
                    // when there are no arguments. Though this is okay since the only case we care about is:
                    // function foo(/* test */ a,
                    //              /* test */ b)
                    var result = true;
                    var children = ParseNodeExtensions.FindChildrenWithEdge(node, AuthorParseNodeEdge.apneArgument);
                    children.foreach(function (argumentNode) {
                        if (token.Span.startPosition() < argumentNode.AuthorNode.Details.StartOffset)
                            result = false;
                    });
                    return result;
            }
            return false;
        };
        Indenter.prototype.ApplyScriptBlockIndentation = function (languageHostIndentation, tree) {
            if (languageHostIndentation == null || tree.StartNodeSelf == null)
                return;
            var scriptBlockIndentation = this.ApplyIndentationLevel(languageHostIndentation, 1);
            //TypeScript: Projection snapshots not supported
            // Disconnect the sibling node if it belongs to a different script block
            //IProjectionSnapshot projectionSnapshot = this.snapshot as IProjectionSnapshot;
            //if (projectionSnapshot != null)
            //{
            //    // Get script block spans.
            //    foreach (SnapshotSpan sourceSpan in projectionSnapshot.GetSourceSpans())
            //    {
            //        // Map the spans to the JavaScript buffer.
            //        ReadOnlyCollection<Span> spans = projectionSnapshot.MapFromSourceSnapshot(sourceSpan);
            //        Debug.Assert(spans.Count == 1, string.Format(CultureInfo.InvariantCulture, "Unexpected span count of {0}.", spans.Count));
            //        if (spans.Count > 0)
            //        {
            //            Span span = spans.First();
            //            // If the "self" node is the first root-level node in a script block, then remove the start node.
            //            if (span.Contains(tree.StartNodethis.AuthorNode.Details.StartOffset))
            //            {
            //                this.scriptBlockBeginLineNumber = projectionSnapshot.GetLineNumberFromPosition(span.Start);
            //                if (tree.StartNodePreviousSibling.HasValue)
            //                {
            //                    int siblingStartOffset = tree.StartNodePreviousSibling.Value.Details.StartOffset;
            //                    // Don't consider sibling in these cases:
            //                    // 1. The sibling belongs to another script block
            //                    // 2. The sibling is on the same line of the script block
            //                    if (!span.Contains(siblingStartOffset) || projectionSnapshot.GetLineNumberFromPosition(siblingStartOffset) == this.scriptBlockBeginLineNumber)
            //                    {
            //                        tree.StartNodePreviousSibling = null;
            //                    }
            //                }
            //                break;
            //            }
            //        }
            //    }
            //}
            // The root is the program.
            tree.Root.SetIndentationOverride(scriptBlockIndentation);
        };
        Indenter.prototype.GetIndentEdit = function (indentInfo, tokenStartPosition, sameLineIndent) {
            var indentText = this.ApplyIndentationLevel(indentInfo.Prefix, indentInfo.Level);
            if (sameLineIndent) {
                return new TextEditInfo(tokenStartPosition, 0, indentText);
            }
            else {
                var snapshotLine = this.snapshot.GetLineFromPosition(tokenStartPosition);
                var currentIndentSpan = new Span(snapshotLine.startPosition(), tokenStartPosition - snapshotLine.startPosition());
                var currentIndentText = this.snapshot.GetText(currentIndentSpan);
                if (currentIndentText !== indentText) {
                    if (this.logger.debug()) {
                        // Verify that currentIndentText is all whitespaces
                        for (var i = 0, len = currentIndentText.length; i < len; i++) {
                            var c = currentIndentText.charCodeAt(i);
                            if (!StringUtils.IsWhiteSpace(c)) {
                                Debug.Fail("Formatting error: Will remove user code when indenting the line: " + snapshotLine.getText());
                                break;
                            }
                        }
                    }
                    return new TextEditInfo(currentIndentSpan.start(), currentIndentSpan.length(), indentText);
                }
            }
            return null;
        };
        Indenter.prototype.ApplyIndentationLevel = function (existingIndentation, level) {
            var indentSize = this.editorOptions.IndentSize;
            var tabSize = this.editorOptions.TabSize;
            var convertTabsToSpaces = this.editorOptions.ConvertTabsToSpaces;
            if (level < 0) {
                if (StringUtils.IsNullOrEmpty(existingIndentation))
                    return "";
                var totalIndent = 0;
                StringUtils.foreach(existingIndentation, function (c) {
                    if (c == '\t')
                        totalIndent += tabSize;
                    else
                        totalIndent++;
                });
                totalIndent += level * indentSize;
                if (totalIndent < 0)
                    return "";
                else
                    return this.GetIndentString(null, totalIndent, tabSize, convertTabsToSpaces);
            }
            var totalIndentSize = level * indentSize;
            return this.GetIndentString(existingIndentation, totalIndentSize, tabSize, convertTabsToSpaces);
        };
        Indenter.prototype.GetIndentString = function (prefix, totalIndentSize, tabSize, convertTabsToSpaces) {
            var tabString = convertTabsToSpaces ? StringUtils.create(' ', tabSize) : "\t";
            var text = "";
            if (!StringUtils.IsNullOrEmpty(prefix))
                text += prefix;
            var pos = 0;
            // fill first with tabs
            while (pos <= totalIndentSize - tabSize) {
                text += tabString;
                pos += tabSize;
            }
            // fill the reminder with spaces
            while (pos < totalIndentSize) {
                text += ' ';
                pos++;
            }
            return text;
        };
        Indenter.prototype.ApplyIndentationDeltaFromParent = function (token, node) {
            var indentationInfo = null;
            var indentableParent = node;
            while (indentableParent != null && !indentableParent.CanIndent())
                indentableParent = indentableParent.Parent;
            if (indentableParent != null && indentableParent.AuthorNode.Details.Kind != AuthorParseNodeKind.apnkProg) {
                var parentIndentationDeltaSize = this.GetIndentationDelta(indentableParent.AuthorNode.Details.StartOffset, token.Span.startPosition());
                if (parentIndentationDeltaSize !== undefined) {
                    indentationInfo = this.ApplyIndentationDelta1(token.Span.startPosition(), parentIndentationDeltaSize);
                }
            }
            return indentationInfo;
        };
        Indenter.prototype.ApplyIndentationDelta1 = function (tokenStartPosition, delta) {
            // Get current indentation
            var snapshotLine = this.snapshot.GetLineFromPosition(tokenStartPosition);
            var currentIndentSpan = new Span(snapshotLine.startPosition(), tokenStartPosition - snapshotLine.startPosition());
            var currentIndent = this.snapshot.GetText(currentIndentSpan);
            // Calculate new indentation from current-indentation and delta
            return this.ApplyIndentationDelta2(currentIndent, delta);
        };
        Indenter.prototype.ApplyIndentationDelta2 = function (currentIndent, delta) {
            if (delta == 0)
                return null;
            var currentIndentSize = Indenter.GetIndentSizeFromIndentText(currentIndent, this.editorOptions);
            var newIndentSize = currentIndentSize + delta;
            if (newIndentSize < 0) {
                newIndentSize = 0;
            }
            var newIndent = this.GetIndentString(null, newIndentSize, this.editorOptions.TabSize, this.editorOptions.ConvertTabsToSpaces);
            if (newIndent != null) {
                return new IndentationInfo(newIndent, 0);
            }
            return null;
        };
        Indenter.prototype.GetIndentationDelta = function (tokenStartPosition, childTokenStartPosition /*?*/) {
            Debug.Assert(childTokenStartPosition !== undefined, "Error: caller must pass 'null' for undefined position");
            var indentationDeltaSize = this.offsetIndentationDeltas.GetValue(tokenStartPosition);
            if (indentationDeltaSize === null) {
                var indentEditInfo = this.indentationBag.FindIndent(tokenStartPosition);
                // No recorded indentation, return null
                if (indentEditInfo == null)
                    return null;
                var origIndentText = this.snapshot.GetText(new Span(indentEditInfo.OrigIndentPosition, indentEditInfo.OrigIndentLength()));
                var newIndentText = indentEditInfo.Indentation();
                var origIndentSize = Indenter.GetIndentSizeFromText(origIndentText, this.editorOptions, /*includeNonIndentChars*/ true);
                var newIndentSize = Indenter.GetIndentSizeFromIndentText(newIndentText, this.editorOptions);
                // Check the child's position whether it's before the parent position
                // if so indent the child based on the first token on the line as opposed to the parent position
                //
                // Example of relative to parent (not line), relative indentation should be "4 (newIndentSize) - 9 (indentSize up to for) = -5"
                //
                // if (1) { for (i = 0; i < 10;       =>          if (1) {
                //                      i++) {                       for (i = 0; i < 10;
                //                                                               i++) {
                //
                // Example of relative to line, relative indentation should be "4 (newIndentSize) - 0 (indentSize up to if) = 4"
                //
                // if (1) { for (i = 0; i < 10;      =>          if (1) {
                //     i++) {                                        for (i = 0; i < 10;
                //                                                       i++) {
                if (childTokenStartPosition !== null) {
                    var childTokenLineStartPosition = this.snapshot.GetLineFromPosition(childTokenStartPosition).startPosition();
                    var childIndentText = this.snapshot.GetText(new Span(childTokenLineStartPosition, childTokenStartPosition - childTokenLineStartPosition));
                    var childIndentSize = Indenter.GetIndentSizeFromIndentText(childIndentText, this.editorOptions);
                    if (childIndentSize < origIndentSize)
                        origIndentSize = Indenter.GetIndentSizeFromIndentText(origIndentText, this.editorOptions);
                }
                indentationDeltaSize = newIndentSize - origIndentSize;
                this.offsetIndentationDeltas.Add(tokenStartPosition, indentationDeltaSize);
            }
            return indentationDeltaSize;
        };
        Indenter.prototype.FillInheritedIndentation = function (tree) {
            var offset = -1;
            var indentNode = null;
            if (tree.StartNodeSelf != null) {
                if (!this.smartIndent && tree.StartNodePreviousSibling !== null && tree.StartNodeSelf.AuthorNode.Label == 0 && tree.StartNodePreviousSibling.Label == 0) {
                    indentNode = tree.StartNodeSelf;
                    offset = tree.StartNodePreviousSibling.Details.StartOffset;
                    // In case the sibling node is on the same line of a parent node, ex:
                    //      case 1: a++;
                    //          break;
                    // In this example, the sibling of break is a++ but a++ is on the same line of its parent.
                    var lineNum = this.snapshot.GetLineNumberFromPosition(offset);
                    var node = indentNode;
                    while (node.Parent != null && this.snapshot.GetLineNumberFromPosition(node.Parent.AuthorNode.Details.StartOffset) == lineNum) {
                        node = node.Parent;
                        if (node.CanIndent()) {
                            indentNode = node;
                            indentNode.IndentationDelta = 0;
                        }
                    }
                }
                else {
                    var parent;
                    // Otherwise base on parent indentation.
                    if (this.smartIndent) {
                        // in smartIndent the self node is the parent node since it's the closest node to the new line
                        // ... unless in case if the startNodeSelf represents the firstToken then we need to choose its parent
                        parent = tree.StartNodeSelf;
                        while (parent != null && parent.AuthorNode.Details.StartOffset == this.firstToken.Span.startPosition())
                            parent = parent.Parent;
                    }
                    else {
                        // Get the parent that is really on a different line from the self node
                        var startNodeLineNumber = this.snapshot.GetLineNumberFromPosition(tree.StartNodeSelf.AuthorNode.Details.StartOffset);
                        parent = tree.StartNodeSelf.Parent;
                        while (parent != null &&
                            startNodeLineNumber == this.snapshot.GetLineNumberFromPosition(parent.AuthorNode.Details.StartOffset)) {
                            parent = parent.Parent;
                        }
                    }
                    // The parent node to take its indentation is the first parent that has indentation.
                    while (parent != null && !parent.CanIndent()) {
                        parent = parent.Parent;
                    }
                    // Skip Program since it has no indentation
                    if (parent != null && parent.AuthorNode.Details.Kind != AuthorParseNodeKind.apnkProg) {
                        offset = parent.AuthorNode.Details.StartOffset;
                        indentNode = parent;
                    }
                }
            }
            if (indentNode != null) {
                var indentOverride = this.GetLineIndentationForOffset(offset);
                // Set the indentation on all the siblings to be the same as indentNode
                if (!this.smartIndent && tree.StartNodePreviousSibling !== null && indentNode.Parent != null) {
                    ParseNodeExtensions.GetChildren(indentNode.Parent).foreach(function (sibling) {
                        if (sibling !== indentNode) {
                            if (sibling.CanIndent())
                                sibling.SetIndentationOverride(indentOverride);
                        }
                    });
                }
                // Set the indent override string on the indent node and on every parent (on different line) after adjusting the indent by the negative delta
                var lastDelta = 0;
                var lastLine = this.snapshot.GetLineNumberFromPosition(indentNode.AuthorNode.Details.StartOffset);
                do {
                    var currentLine = this.snapshot.GetLineNumberFromPosition(indentNode.AuthorNode.Details.StartOffset);
                    if (lastLine != currentLine) {
                        lastLine = currentLine;
                        indentOverride = this.ApplyIndentationLevel(indentOverride, -lastDelta);
                        lastDelta = 0;
                    }
                    if (indentNode.CanIndent()) {
                        indentNode.SetIndentationOverride(indentOverride);
                        lastDelta = indentNode.IndentationDelta;
                    }
                    indentNode = indentNode.Parent;
                } while (indentNode != null);
            }
        };
        Indenter.prototype.GetLineIndentationForOffset = function (offset) {
            var indentationEdit;
            // First check if we already have indentation info in our indentation bag
            indentationEdit = this.indentationBag.FindIndent(offset);
            if (indentationEdit != null) {
                return indentationEdit.Indentation();
            }
            else {
                // Otherwise, use the indentation from the textBuffer
                var line = this.snapshot.GetLineFromPosition(offset);
                var lineText = line.getText();
                var index = 0;
                while (index < lineText.length && (lineText.charAt(index) == ' ' || lineText.charAt(index) == '\t')) {
                    ++index;
                }
                return lineText.substr(0, index);
            }
        };
        Indenter.prototype.RegisterIndentation = function (indent, sameLineIndent) {
            var indentationInfo = null;
            if (sameLineIndent) {
                // Consider the original indentation from the beginning of the line up to the indent position (or really the token position)
                var lineStartPosition = this.snapshot.GetLineFromPosition(indent.Position).startPosition();
                var lineIndentLength = indent.Position - lineStartPosition;
                indentationInfo = IndentationEditInfo.create2(indent.Position, indent.ReplaceWith, lineStartPosition, lineIndentLength);
            }
            else {
                indentationInfo = new IndentationEditInfo(indent);
            }
            this.indentationBag.AddIndent(indentationInfo);
        };
        Indenter.prototype.RegisterIndentation2 = function (position, indent) {
            this.RegisterIndentation(new TextEditInfo(position, 0, indent), false);
        };
        Indenter.prototype.AdjustStartOffsetIfNeeded = function (token, node) {
            if (token == null)
                return;
            var updateStartOffset = false;
            switch (token.Token) {
                case AuthorTokenKind.atkFunction:
                    updateStartOffset = node.AuthorNode.Details.Kind == AuthorParseNodeKind.apnkFncDecl;
                    break;
                case AuthorTokenKind.atkLCurly:
                    updateStartOffset = node.AuthorNode.Details.Kind == AuthorParseNodeKind.apnkObject;
                    break;
                case AuthorTokenKind.atkLBrack:
                    updateStartOffset = node.AuthorNode.Details.Kind == AuthorParseNodeKind.apnkArray;
                    break;
            }
            if (updateStartOffset) {
                ParseNodeExtensions.SetNodeSpan(node, token.Span.startPosition(), node.AuthorNode.Details.EndOffset);
            }
        };
        Indenter.prototype.IsMultiLineString = function (token) {
            return token.tokenID === TypeScript.TokenID.StringLiteral &&
                this.snapshot.GetLineNumberFromPosition(token.Span.endPosition()) > this.snapshot.GetLineNumberFromPosition(token.Span.startPosition());
        };
        return Indenter;
    })();
    Formatting.Indenter = Indenter;
})(Formatting || (Formatting = {}));
