/* ***** BEGIN LICENSE BLOCK *****
 * Distributed under the BSD license:
 *
 * Copyright (c) 2010, Ajax.org B.V.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Ajax.org B.V. nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ***** END LICENSE BLOCK ***** */

define(function (require, exports, module) {
    "use strict";

    var Range = require("../range").Range;

    /*
     * If an array is passed in, the folds are expected to be sorted already.
     */
    function FoldLine(foldData, folds) {
        this.foldData = foldData;
        if (Array.isArray(folds)) {
            this.folds = folds;
        } else {
            folds = this.folds = [folds];
        }

        var last = folds[folds.length - 1];
        this.range = new Range(folds[0].start.row, folds[0].start.column,
            last.end.row, last.end.column);
        this.start = this.range.start;
        this.end = this.range.end;

        this.folds.forEach(function (fold) {
            fold.setFoldLine(this);
        }, this);
    }

    (function () {
        /*
         * Note: This doesn't update wrapData!
         */
        this.shiftRow = function (shift) {
            this.start.row += shift;
            this.end.row += shift;
            this.folds.forEach(function (fold) {
                fold.start.row += shift;
                fold.end.row += shift;
            });
        };

        this.addFold = function (fold) {
            if (fold.sameRow) {
                if (fold.start.row < this.startRow || fold.endRow > this.endRow) {
                    throw new Error("Can't add a fold to this FoldLine as it has no connection");
                }
                this.folds.push(fold);
                this.folds.sort(function (a, b) {
                    return -a.range.compareEnd(b.start.row, b.start.column);
                });
                if (this.range.compareEnd(fold.start.row, fold.start.column) > 0) {
                    this.end.row = fold.end.row;
                    this.end.column = fold.end.column;
                } else if (this.range.compareStart(fold.end.row, fold.end.column) < 0) {
                    this.start.row = fold.start.row;
                    this.start.column = fold.start.column;
                }
            } else if (fold.start.row == this.end.row) {
                this.folds.push(fold);
                this.end.row = fold.end.row;
                this.end.column = fold.end.column;
            } else if (fold.end.row == this.start.row) {
                this.folds.unshift(fold);
                this.start.row = fold.start.row;
                this.start.column = fold.start.column;
            } else {
                throw new Error("Trying to add fold to FoldRow that doesn't have a matching row");
            }
            fold.foldLine = this;
        };

        this.containsRow = function (row) {
            return row >= this.start.row && row <= this.end.row;
        };

        this.walk = function (callback, endRow, endColumn) {
            var lastEnd = 0,
                folds = this.folds,
                fold,
                cmp, stop, isNewRow = true;

            if (endRow == null) {
                endRow = this.end.row;
                endColumn = this.end.column;
            }

            for (var i = 0; i < folds.length; i++) {
                fold = folds[i];

                cmp = fold.range.compareStart(endRow, endColumn);
                // This fold is after the endRow/Column.
                if (cmp == -1) {
                    callback(null, endRow, endColumn, lastEnd, isNewRow);
                    return;
                }

                stop = callback(null, fold.start.row, fold.start.column, lastEnd, isNewRow);
                stop = !stop && callback(fold.placeholder, fold.start.row, fold.start.column, lastEnd);

                // If the user requested to stop the walk or endRow/endColumn is
                // inside of this fold (cmp == 0), then end here.
                if (stop || cmp === 0) {
                    return;
                }

                // Note the new lastEnd might not be on the same line. However,
                // it's the callback's job to recognize this.
                isNewRow = !fold.sameRow;
                lastEnd = fold.end.column;
            }
            callback(null, endRow, endColumn, lastEnd, isNewRow);
        };

        this.getNextFoldTo = function (row, column) {
            var fold, cmp;
            for (var i = 0; i < this.folds.length; i++) {
                fold = this.folds[i];
                cmp = fold.range.compareEnd(row, column);
                if (cmp == -1) {
                    return {
                        fold: fold,
                        kind: "after"
                    };
                } else if (cmp === 0) {
                    return {
                        fold: fold,
                        kind: "inside"
                    };
                }
            }
            return null;
        };

        this.addRemoveChars = function (row, column, len) {
            var ret = this.getNextFoldTo(row, column),
                fold, folds;
            if (ret) {
                fold = ret.fold;
                if (ret.kind == "inside"
                    && fold.start.column != column
                    && fold.start.row != row) {
                    //throwing here breaks whole editor
                    //TODO: properly handle this
                    window.console && window.console.log(row, column, fold);
                } else if (fold.start.row == row) {
                    folds = this.folds;
                    var i = folds.indexOf(fold);
                    if (i === 0) {
                        this.start.column += len;
                    }
                    for (i; i < folds.length; i++) {
                        fold = folds[i];
                        fold.start.column += len;
                        if (!fold.sameRow) {
                            return;
                        }
                        fold.end.column += len;
                    }
                    this.end.column += len;
                }
            }
        };

        this.split = function (row, column) {
            var pos = this.getNextFoldTo(row, column);

            if (!pos || pos.kind == "inside")
                return null;

            var fold = pos.fold;
            var folds = this.folds;
            var foldData = this.foldData;

            var i = folds.indexOf(fold);
            var foldBefore = folds[i - 1];
            this.end.row = foldBefore.end.row;
            this.end.column = foldBefore.end.column;

            // Remove the folds after row/column and create a new FoldLine
            // containing these removed folds.
            folds = folds.splice(i, folds.length - i);

            var newFoldLine = new FoldLine(foldData, folds);
            foldData.splice(foldData.indexOf(this) + 1, 0, newFoldLine);
            return newFoldLine;
        };

        this.merge = function (foldLineNext) {
            var folds = foldLineNext.folds;
            for (var i = 0; i < folds.length; i++) {
                this.addFold(folds[i]);
            }
            // Remove the foldLineNext - no longer needed, as
            // it's merged now with foldLineNext.
            var foldData = this.foldData;
            foldData.splice(foldData.indexOf(foldLineNext), 1);
        };

        this.toString = function () {
            var ret = [this.range.toString() + ": ["];

            this.folds.forEach(function (fold) {
                ret.push("  " + fold.toString());
            });
            ret.push("]");
            return ret.join("\n");
        };

        this.idxToPosition = function (idx) {
            var lastFoldEndColumn = 0;

            for (var i = 0; i < this.folds.length; i++) {
                var fold = this.folds[i];

                idx -= fold.start.column - lastFoldEndColumn;
                if (idx < 0) {
                    return {
                        row: fold.start.row,
                        column: fold.start.column + idx
                    };
                }

                idx -= fold.placeholder.length;
                if (idx < 0) {
                    return fold.start;
                }

                lastFoldEndColumn = fold.end.column;
            }

            return {
                row: this.end.row,
                column: this.end.column + idx
            };
        };
    }).call(FoldLine.prototype);

    exports.FoldLine = FoldLine;
});
