/*
 * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) 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.
 */

define(["require", "exports", "module", "ace/lib/oop", "ace/range", "ace/mode/folding/fold_mode"],
    function (require, exports, module, oop, range) {

        "use strict";   // JS strict mode

        var oop = require("../../lib/oop");
        var BaseFoldMode = require("./fold_mode").FoldMode;
        var Range = range.Range;

        var AsyncAPIFoldMode = function () {
        };
        oop.inherits(AsyncAPIFoldMode, BaseFoldMode);

        (function () {
            var startToEndBracketRegexMap = {
                "(\\{)(?:.(?!}))*": "(})"
            };

            /*
             * The fold range start token to end token map
             * Can update this map to define the fold ranges
             */
            var startToEndTokenRegexMap = {
                "(\\/\\*)(?:.(?!\\*\\/))*": "(\\*\\/)",
                "(begin)(?:.(?!}|end\\s*;))*": "(end\\s*;)"
            };

            // Regular expressions that identify starting and stopping points
            this.foldingStartMarker = new RegExp("(?:" +
                (Object.keys(startToEndBracketRegexMap).length == 0 ? "" : Object.keys(startToEndBracketRegexMap).join("|") + "|") +
                Object.keys(startToEndTokenRegexMap).join("|") +
                ")", "mi");
            this.foldingStopMarker = new RegExp("(?:" +
                (Object.values(startToEndBracketRegexMap).length == 0 ? "" : Object.values(startToEndBracketRegexMap).join("|") + "|") +
                Object.values(startToEndTokenRegexMap).join("|") +
                ")", "mi");

            // The order of tokens should be in the order in which the token groups are matched in foldingStopMarker
            var startTokenRegexps = Object.keys(startToEndTokenRegexMap).map(function (regexString) {
                return new RegExp("^" + regexString, "i");
            });

            // The order of tokens should be in the order in which the token groups are matched in foldingStartMarker
            var endTokenRegexps = Object.values(startToEndTokenRegexMap).map(function (regexString) {
                return new RegExp("^" + regexString, "i");
            });

            this.getFoldWidgetRange = function (session, foldStyle, row) {
                var line = session.getLine(row);

                var match = line.match(this.foldingStartMarker);
                if (match) {
                    var i = match.index;

                    if (match[1]) {     // Brackets are matched using a special function which supports nested brackets
                        return this.openingBracketBlock(session, match[1], row, i);
                    }

                    var matchFound;
                    for (var j = 1; j < match.length; j++) {
                        if (match[j]) {
                            matchFound = true;
                            break;
                        }
                    }

                    if (matchFound) {
                        return getRangeFromStartPositionToEndToken(session, row, i + match[j].length, endTokenRegexps[j - 2]);
                    }
                }

                if (foldStyle != "markbeginend") {
                    return;
                }

                match = line.match(this.foldingStopMarker);
                if (match) {
                    i = match.index;

                    if (match[1]) {     // Brackets are matched using a special function which supports nested brackets
                        return this.closingBracketBlock(session, match[1], row, i);
                    }

                    for (j = 1; j < match.length; j++) {
                        if (match[j]) {
                            matchFound = true;
                            break;
                        }
                    }

                    if (matchFound) {
                        return getRangeFromEndPositionToStartToken(session, row, i, startTokenRegexps[j - 2]);
                    }
                }
            };
        }).call(AsyncAPIFoldMode.prototype);

        /**
         * Return the range from the start position to the position at which the first string matching the endTokenRegex is found
         * Search is started at start position and traveled forward
         *
         * @param session Ace editor session
         * @param startRow Start row of the range
         * @param startColumn Start column of the range
         * @param endTokenRegex Regex of the end token
         */
        function getRangeFromStartPositionToEndToken(session, startRow, startColumn, endTokenRegex) {
            var editorEndRow = session.getLength() - 1;
            var editorEndColumn = session.getLine(editorEndRow).length;

            var editorText = session.doc.getTextRange(Range.fromPoints(
                {row: startRow, column: startColumn},
                {row: editorEndRow, column: editorEndColumn}
            ));

            var endTokenRegexMatch;
            var endTokenRow = startRow;
            var endTokenColumn = startColumn;
            for (var i = 0; i < editorText.length; i++) {
                if (editorText.charAt(i) == "\n") {
                    endTokenRow++;
                    endTokenColumn = 0;
                } else {
                    endTokenColumn++;
                }
                if (endTokenRegexMatch = endTokenRegex.exec(editorText.substring(i))) {
                    var range = Range.fromPoints(
                        {row: startRow, column: startColumn},
                        {row: endTokenRow, column: endTokenColumn - 1}
                    );
                    if (endTokenRegexMatch[1]) {
                        range.end.column += endTokenRegexMatch[0].length - endTokenRegexMatch[1].length;
                    }
                    return range;
                }
            }
        }

        /**
         * Return the range from the end position to the position at which the last string matching the startTokenRegex is found
         * Search is started at end position and traveled backward
         *
         * @param session Ace editor session
         * @param endRow End row of the range
         * @param endColumn End column of the range
         * @param startTokenRegex Regex of the end token
         */
        function getRangeFromEndPositionToStartToken(session, endRow, endColumn, startTokenRegex) {
            var editorText = session.doc.getTextRange(Range.fromPoints(
                {row: 0, column: 0},
                {row: endRow, column: endColumn}
            ));

            var startTokenRegexMatch;
            var startTokenRow = endRow;
            var startTokenColumn = endColumn;
            for (var i = editorText.length; i > 0; i--) {
                if (editorText.charAt(i - 1) == "\n") {
                    startTokenRow--;
                    startTokenColumn = session.getLine(startTokenRow).length;
                } else {
                    startTokenColumn--;
                }
                if (startTokenRegexMatch = startTokenRegex.exec(editorText.substring(i - 1))) {
                    var range = Range.fromPoints(
                        {row: startTokenRow, column: startTokenColumn},
                        {row: endRow, column: endColumn}
                    );
                    if (startTokenRegexMatch[1]) {
                        range.start.column += startTokenRegexMatch[1].length;
                    }
                    return range;
                }
            }
        }

        exports.AsyncAPIFoldMode = AsyncAPIFoldMode;
    });
