/* ***** 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 dom = require("../lib/dom");
    var oop = require("../lib/oop");
    var lang = require("../lib/lang");
    var EventEmitter = require("../lib/event_emitter").EventEmitter;

    var Gutter = function (parentEl) {
        this.element = dom.createElement("div");
        this.element.className = "ace_layer ace_gutter-layer";
        parentEl.appendChild(this.element);
        this.setShowFoldWidgets(this.$showFoldWidgets);

        this.gutterWidth = 0;

        this.$annotations = [];
        this.$updateAnnotations = this.$updateAnnotations.bind(this);

        this.$cells = [];
    };

    (function () {

        oop.implement(this, EventEmitter);

        this.setSession = function (session) {
            if (this.session)
                this.session.removeEventListener("change", this.$updateAnnotations);
            this.session = session;
            if (session)
                session.on("change", this.$updateAnnotations);
        };

        this.addGutterDecoration = function (row, className) {
            if (window.console)
                console.warn && console.warn("deprecated use session.addGutterDecoration");
            this.session.addGutterDecoration(row, className);
        };

        this.removeGutterDecoration = function (row, className) {
            if (window.console)
                console.warn && console.warn("deprecated use session.removeGutterDecoration");
            this.session.removeGutterDecoration(row, className);
        };

        this.setAnnotations = function (annotations) {
            // iterate over sparse array
            this.$annotations = [];
            for (var i = 0; i < annotations.length; i++) {
                var annotation = annotations[i];
                var row = annotation.row;
                var rowInfo = this.$annotations[row];
                if (!rowInfo)
                    rowInfo = this.$annotations[row] = {text: []};

                var annoText = annotation.text;
                annoText = annoText ? lang.escapeHTML(annoText) : annotation.html || "";

                if (rowInfo.text.indexOf(annoText) === -1)
                    rowInfo.text.push(annoText);

                var type = annotation.type;
                if (type == "error")
                    rowInfo.className = " ace_error";
                else if (type == "warning" && rowInfo.className != " ace_error")
                    rowInfo.className = " ace_warning";
                else if (type == "info" && (!rowInfo.className))
                    rowInfo.className = " ace_info";
            }
        };

        this.$updateAnnotations = function (delta) {
            if (!this.$annotations.length)
                return;
            var firstRow = delta.start.row;
            var len = delta.end.row - firstRow;
            if (len === 0) {
                // do nothing
            } else if (delta.action == 'remove') {
                this.$annotations.splice(firstRow, len + 1, null);
            } else {
                var args = new Array(len + 1);
                args.unshift(firstRow, 1);
                this.$annotations.splice.apply(this.$annotations, args);
            }
        };

        this.update = function (config) {
            var session = this.session;
            var firstRow = config.firstRow;
            var lastRow = Math.min(config.lastRow + config.gutterOffset,  // needed to compensate for hor scollbar
                session.getLength() - 1);
            var fold = session.getNextFoldLine(firstRow);
            var foldStart = fold ? fold.start.row : Infinity;
            var foldWidgets = this.$showFoldWidgets && session.foldWidgets;
            var breakpoints = session.$breakpoints;
            var decorations = session.$decorations;
            var firstLineNumber = session.$firstLineNumber;
            var lastLineNumber = 0;

            var gutterRenderer = session.gutterRenderer || this.$renderer;

            var cell = null;
            var index = -1;
            var row = firstRow;
            while (true) {
                if (row > foldStart) {
                    row = fold.end.row + 1;
                    fold = session.getNextFoldLine(row, fold);
                    foldStart = fold ? fold.start.row : Infinity;
                }
                if (row > lastRow) {
                    while (this.$cells.length > index + 1) {
                        cell = this.$cells.pop();
                        this.element.removeChild(cell.element);
                    }
                    break;
                }

                cell = this.$cells[++index];
                if (!cell) {
                    cell = {element: null, textNode: null, foldWidget: null};
                    cell.element = dom.createElement("div");
                    cell.textNode = document.createTextNode('');
                    cell.element.appendChild(cell.textNode);
                    this.element.appendChild(cell.element);
                    this.$cells[index] = cell;
                }

                var className = "ace_gutter-cell ";
                if (breakpoints[row])
                    className += breakpoints[row];
                if (decorations[row])
                    className += decorations[row];
                if (this.$annotations[row])
                    className += this.$annotations[row].className;
                if (cell.element.className != className)
                    cell.element.className = className;

                var height = session.getRowLength(row) * config.lineHeight + "px";
                if (height != cell.element.style.height)
                    cell.element.style.height = height;

                if (foldWidgets) {
                    var c = foldWidgets[row];
                    // check if cached value is invalidated and we need to recompute
                    if (c == null)
                        c = foldWidgets[row] = session.getFoldWidget(row);
                }

                if (c) {
                    if (!cell.foldWidget) {
                        cell.foldWidget = dom.createElement("span");
                        cell.element.appendChild(cell.foldWidget);
                    }
                    var className = "ace_fold-widget ace_" + c;
                    if (c == "start" && row == foldStart && row < fold.end.row)
                        className += " ace_closed";
                    else
                        className += " ace_open";
                    if (cell.foldWidget.className != className)
                        cell.foldWidget.className = className;

                    var height = config.lineHeight + "px";
                    if (cell.foldWidget.style.height != height)
                        cell.foldWidget.style.height = height;
                } else {
                    if (cell.foldWidget) {
                        cell.element.removeChild(cell.foldWidget);
                        cell.foldWidget = null;
                    }
                }

                var text = lastLineNumber = gutterRenderer
                    ? gutterRenderer.getText(session, row)
                    : row + firstLineNumber;
                if (text != cell.textNode.data)
                    cell.textNode.data = text;

                row++;
            }

            this.element.style.height = config.minHeight + "px";

            if (this.$fixedWidth || session.$useWrapMode)
                lastLineNumber = session.getLength() + firstLineNumber;

            var gutterWidth = gutterRenderer
                ? gutterRenderer.getWidth(session, lastLineNumber, config)
                : lastLineNumber.toString().length * config.characterWidth;

            var padding = this.$padding || this.$computePadding();
            gutterWidth += padding.left + padding.right;
            if (gutterWidth !== this.gutterWidth && !isNaN(gutterWidth)) {
                this.gutterWidth = gutterWidth;
                this.element.style.width = Math.ceil(this.gutterWidth) + "px";
                this._emit("changeGutterWidth", gutterWidth);
            }
        };

        this.$fixedWidth = false;

        this.$showLineNumbers = true;
        this.$renderer = "";
        this.setShowLineNumbers = function (show) {
            this.$renderer = !show && {
                    getWidth: function () {
                        return ""
                    },
                    getText: function () {
                        return ""
                    }
                };
        };

        this.getShowLineNumbers = function () {
            return this.$showLineNumbers;
        };

        this.$showFoldWidgets = true;
        this.setShowFoldWidgets = function (show) {
            if (show)
                dom.addCssClass(this.element, "ace_folding-enabled");
            else
                dom.removeCssClass(this.element, "ace_folding-enabled");

            this.$showFoldWidgets = show;
            this.$padding = null;
        };

        this.getShowFoldWidgets = function () {
            return this.$showFoldWidgets;
        };

        this.$computePadding = function () {
            if (!this.element.firstChild)
                return {left: 0, right: 0};
            var style = dom.computedStyle(this.element.firstChild);
            this.$padding = {};
            this.$padding.left = parseInt(style.paddingLeft) + 1 || 0;
            this.$padding.right = parseInt(style.paddingRight) || 0;
            return this.$padding;
        };

        this.getRegion = function (point) {
            var padding = this.$padding || this.$computePadding();
            var rect = this.element.getBoundingClientRect();
            if (point.x < padding.left + rect.left)
                return "markers";
            if (this.$showFoldWidgets && point.x > rect.right - padding.right)
                return "foldWidgets";
        };

    }).call(Gutter.prototype);

    exports.Gutter = Gutter;

});
