Source: controllers/reviewController.js

/**
 * The controller handles the logic related to adding and removing annotations. It also handles the way how
 * the selected area is defined in the view. Requires `velpSelection` directive.
 *
 * @module reviewController
 * @author Joonas Lattu
 * @author Petteri Palojärvi
 * @author Seppo Tarvainen
 * @licence MIT
 * @copyright 2016 Timber project members
 */

var angular, $;
var timApp = angular.module('timApp');

var UNDEFINED = "undefined";

/**
 * Angular controller for handling annotations.
 * @lends module:reviewController
 */
timApp.controller("ReviewController", ['$scope', '$http', '$window', '$compile', function ($scope, $http, $window, $compile) {
    "use strict";
    var console = $window.console;

    var illegalClasses = ["annotation-element", "highlighted", "editorArea", "previewcontent"];

    $scope.annotationsAdded = false;
    $scope.selectedArea = null;
    $scope.selectedElement = null;
    $scope.rights = $window.rights;

    $scope.annotationids = {0: 0};
    $scope.zIndex = 1;

    /**
     * Makes a post request to the given URL.
     * @method makePostRequest
     * @param url - Request URL
     * @param params - Query parameters
     * @param successMethod - Method to run if the request was successful.
     */
    $scope.makePostRequest = function (url, params, successMethod) {
        $http({
            method: 'POST',
            url: url,
            data: params
        }).then(function (data) {
            successMethod(data);
        });
    };


    /**
     * Loads the document annotations into the view.
     * @method loadDocumentAnnotations
     */
    $scope.loadDocumentAnnotations = function () {
        var annotationsToRemove = [];

        for (var i = 0; i < $scope.annotations.length; i++) {

            var placeInfo = $scope.annotations[i].coord;
            var parent = document.getElementById(placeInfo.start.par_id);

            if (parent === null) {
                // TODO: Decide what to do, when parent element has been deleted, for now remove annotation from list
                annotationsToRemove.push($scope.annotations[i]);
                continue;
            }

            if ($scope.annotations[i].answer_id !== null) {
                if (!parent.classList.contains("has-annotation")) {
                    parent.classList.add("has-annotation");
                }
                continue;
            }

            if (parent.getAttribute("t") === placeInfo.start.t && placeInfo.start.offset !== null) {

                try {
                    var elements = parent.querySelector(".parContent");

                    var start_elpath = placeInfo.start.el_path;

                    for (var j = 0; j < start_elpath.length; j++) {
                        var elementChildren = getElementChildren(elements);
                        if (elementChildren[start_elpath[j]] !== null)
                            elements = elementChildren[start_elpath[j]];
                    }

                    var startel = elements.childNodes[placeInfo.start.node];
                    var endel = elements.childNodes[placeInfo.end.node];

                    var range = document.createRange();
                    range.setStart(startel, placeInfo.start.offset);
                    range.setEnd(endel, placeInfo.end.offset);
                    $scope.addAnnotationToCoord(range, $scope.annotations[i], false);
                    addAnnotationToElement(parent, $scope.annotations[i], false, "Added also margin annotation");
                } catch (err) {
                    addAnnotationToElement(parent, $scope.annotations[i], false, "Could not show annotation in correct place");
                }
            } else {
                addAnnotationToElement(parent, $scope.annotations[i], false, "Paragraph has been modified");
            }
        }

        for (var k = 0; k < annotationsToRemove.length; k++) {
            console.log("Deleted annotation:");
            console.log(annotationsToRemove[k]);
            var index = $scope.annotations.indexOf(annotationsToRemove[k]);
            $scope.annotations.splice(index, 1);
        }

        $scope.annotationsAdded = true;
    };

    /**
     * Gets the children (not childNodes) of the element.
     * @method getElementChildren
     * @param element - Element whose children are requested
     * @returns {Array} Element children
     */
    var getElementChildren = function (element) {
        /*if (typeof element.children !== "undefined")
         return element.children;
         */
        var children = [];
        for (var i = 0; i < element.childNodes.length; i++) {
            if (typeof element.childNodes[i].tagName !== "undefined") {
                children.push(element.childNodes[i]);
            }
        }
        return children;
    };

    /**
     * Gets the parent element of the given element.
     * @method getElementParent
     * @param element - Element whose parent is queried for
     * @returns {Element} Element parent
     */
    var getElementParent = function (element) {
        /*
         if (typeof element.parentElement !== "undefined")
         return element.parentElement;
         */
        var parent = element.parentNode;
        if (typeof parent.tagName !== "undefined") {
            return parent;
        }

        getElementParent(parent);
    };


    /**
     * Gets element parent element until given attribute is present.
     * @method getElementParentUntilAttribute
     * @param element - Element whose parent is queried for
     * @param attribute - Attribute as a string
     * @returns {Element} First element that has the given attribute
     */
    var getElementParentUntilAttribute = function (element, attribute) {
        //console.log(element);
        element = getElementParent(element);
        while (!element.hasAttribute(attribute)) {
            element = getElementParent(element);
        }
        return element;
    };

    /**
     * Checks if the given element is an annotation or not.
     * @method checkIfAnnotation
     * @param element - Element to check
     * @returns {boolean} Whether the element is an annotation or not
     */
    var checkIfAnnotation = function (element) {
        if (element.nodeName === "ANNOTATION")
            return true;

        if (element.nodeName === "SPAN") {
            return element.hasAttribute("annotation");
        }

        return false;
    };

    /**
     * Loads the annotations to the given answer.
     * @method loadAnnotationsToAnswer
     * @param answer_id - Answer ID
     * @param par_id - Paragraph ID
     */
    $scope.loadAnnotationsToAnswer = function (answer_id, par_id, showInPlace) {
        var par = document.getElementById(par_id);
        var annotations = $scope.getAnnotationsByAnswerId(answer_id);

        var oldAnnotations = par.querySelectorAll(".notes [aid]");
        if (oldAnnotations.length > 0) {
            var annotationParent = getElementParent(oldAnnotations[0]);
            for (var a = 0; a < oldAnnotations.length; a++) {
                console.log(getElementParent(oldAnnotations[a]));
                annotationParent.removeChild(oldAnnotations[a]);
            }
        }

        console.log("Loading annotations");
        for (var i = 0; i < annotations.length; i++) {
            var placeInfo = annotations[i].coord;

            var element = par.getElementsByTagName("PRE")[0].firstChild;
            console.log(annotations[i]);

            if (!showInPlace || placeInfo.start.offset === null) {
                addAnnotationToElement(par, annotations[i], false, "Added as margin annotation");
            } else {
                var range = document.createRange();
                range.setStart(element, placeInfo.start.offset);
                range.setEnd(element, placeInfo.end.offset);
                $scope.addAnnotationToCoord(range, annotations[i], false);
                addAnnotationToElement(par, annotations[i], false, "Added also margin annotation");
            }

        }
    };

    /**
     * Gets all the annotations with a given answer ID.
     * @method getAnnotationsByAnswerId
     * @param id - Answer ID
     * @returns {Array} Annotations of the answer
     */
    $scope.getAnnotationsByAnswerId = function (id) {
        var annotations = [];
        $scope.annotations.forEach(function (a) {
            console.log(a);
            if (a.answer_id !== null && a.answer_id === id)
                annotations.push(a);
        });

        annotations.sort(function (a, b) {
            console.log(b);
            console.log(a);
            return b.coord.start.offset - a.coord.start.offset;
        });

        return annotations;
    };

    /**
     * Adds an annotation to the given element in a given coordinate.
     * @method addAnnotationToCoord
     * @param range - Annotation coordinate
     * @param annotation - Annotation info
     * @param show - Whether annotation is shown when created or not
     */
    $scope.addAnnotationToCoord = function (range, annotation, show) {
        var span = $scope.createPopOverElement(annotation, show);
        try {
            range.surroundContents(span);
        } catch (err) {
            addAnnotationToElement(span, annotation, true, "Annotation crosses taglines");
            $scope.selectedArea = null;

            /*
             console.log(err);
             var new_range = document.createRange();

             var el = range.startContainer;
             var start = range.startOffset;
             var end = range.startContainer.parentNode.innerHTML.length - 1;

             new_range.setStart(el, start);
             new_range.setEnd(el, end);
             $scope.addAnnotationToCoord(new_range, annotation, show);
             */
        }

        $compile(span)($scope); // Gives error [$compile:nonassign]
    };

    /**
     * Adds an annotation to the given element. The annotation will be placed in the margin.
     * @method addAnnotationToElement
     * @param el - Given element
     * @param annotation - Annotation info
     * @param show - Whether annotation is shown when created or not
     * @param reason - The reason why the annotation is put here (not implemented yet)
     */
    var addAnnotationToElement = function (el, annotation, show, reason) {
        annotation.reason = reason;
        var span = $scope.createPopOverElement(annotation, show);
        var text = document.createTextNode("\u00A0" + annotation.content + "\u00A0");
        span.appendChild(text);
        addElementToParagraphMargin(el, span);

        console.log(reason);
        $compile(span)($scope); // Gives error [$compile:nonassign]
    };

    /**
     * Adds an element to the paragraph margin.
     * @method addElementToParagraphMargin
     * @param par - Paragraph where the element will be added
     * @param el - Element to add
     */
    var addElementToParagraphMargin = function (par, el) {
        var container = par.getElementsByClassName("notes");
        if (container.length > 0) {
            container[0].appendChild(el);
        } else {
            container = document.createElement("div");
            container.classList.add("notes");
            container.appendChild(el);
            par.appendChild(container);
        }
    };

    /**
     * Creates the velp badge button (the button with letter 'V' on it).
     * @method createVelpBadge
     */
    var createVelpBadge = function () {
        var btn = document.createElement("input");
        btn.type = "button";
        btn.classList.add("velp-badge");
        btn.classList.add("timButton");
        btn.value = "V";
        btn.id = "velpBadge";
        btn.setAttribute("ng-click", "clearVelpBadge($event)");
        $compile(btn)($scope);
        return btn;
    };

    /**
     * Moves the velp badge to the correct element.
     * @method updateVelpBadge
     * @param oldElement - Element where the badge was
     * @param newElement - Element where the badge needs to be attached
     */
    $scope.updateVelpBadge = function (oldElement, newElement) {
        if (newElement === null) {
            return null;
        } else if (oldElement === null) {
            addElementToParagraphMargin(newElement, createVelpBadge(newElement.id));
        } else if (oldElement.id !== newElement.id) {
            $scope.clearVelpBadge(null);
            addElementToParagraphMargin(newElement, createVelpBadge(newElement.id));
        }
    };

    /**
     * Removes the velp badge and clears the element selection.
     * @param e - Current click event
     */
    $scope.clearVelpBadge = function (e) {
        var btn = document.getElementById("velpBadge");
        if (btn !== null) {
            var parent = getElementParent(btn);
            parent.removeChild(btn);
        }

        if (e !== null) {
            console.log(e);
            $scope.selectedElement = null;
            $scope.selectedArea = null;
ID
            $scope.updateVelpList();
            e.stopPropagation();
        }
    };


    /**
     * Returns the real ID of an annotation.
     * @method getRealAnnotationId
     * @param id - Annotation ID generated by the client
     * @returns {int} Annotation ID generated by the server
     */
    $scope.getRealAnnotationId = function (id) {
        if (id < 0) {
            return $scope.annotationids[id];
        }
        return id;
    };

    /**
     * Deletes the given annotation.
     * @method deleteAnnotation
     * @param id - Annotation ID
     * @param inmargin - Whether annotation is a margin annotation or not
     */
    $scope.deleteAnnotation = function (id, inmargin) {
        console.log(inmargin);
        var annotationParents = document.querySelectorAll('[aid="{0}"]'.replace('{0}', id));
        var annotationHighlights = annotationParents[0].getElementsByClassName("highlighted");

        if (annotationParents.length > 1) {
            var savedHTML = "";
            for (var i = 0; i < annotationHighlights.length; i++) {
                var addHTML = annotationHighlights[i].innerHTML.replace('<span class="ng-scope">', '');
                addHTML = addHTML.replace('</span>', '');
                savedHTML += addHTML;
            }
            annotationParents[0].outerHTML = savedHTML;
            annotationParents[1].parentNode.removeChild(annotationParents[1]);
        } else {
            annotationParents[0].parentNode.removeChild(annotationParents[0]);
        }

        for (var a = 0; a < $scope.annotations.length; a++) {
            if (id === $scope.annotations[a].id)
                $scope.annotations.splice(a, 1);
        }

        var current_id = $scope.getRealAnnotationId(id);

        $scope.makePostRequest("/invalidate_annotation", {annotation_id: current_id}, function (json) {
            console.log(json);
        });
    };

     /**
     * Updates annotation data.
     * @method updateAnnotation
     * @param id - Annotation ID
     * @param inmargin - Whether the annotation is to be placed in the margin or not
     */
    $scope.updateAnnotation = function (id, inmargin) {
        var annotationParents = document.querySelectorAll('[aid="{0}"]'.replace('{0}', id));
        var annotationElement = $('[aid="{0}"]'.replace('{0}', id));
        var par = annotationElement.parents('.par');
        var annotationHighlights = annotationElement[0].getElementsByClassName("highlighted");
        if(!inmargin) {
            for (var a = 0; a < $scope.annotations.length; a++) {
                if (id === $scope.annotations[a].id){
                    annotationElement[1].parentNode.removeChild(annotationElement[1]);
                    addAnnotationToElement(par[0], $scope.annotations[a], false,"Added also margin annotation");
                    //addAnnotationToElement($scope.annotations[a], false, "Added also margin annotation");
                }
            }

            console.log("updateAnnotationMargin");
        } else {
            if (annotationParents.length > 1) {
                var savedHTML = "";
                for (var i = 0; i < annotationHighlights.length; i++) {
                    var addHTML = annotationHighlights[i].innerHTML.replace('<span class="ng-scope">', '');
                    addHTML = addHTML.replace('</span>', '');
                    savedHTML += addHTML;
                }
                annotationParents[0].outerHTML = savedHTML;

                // TODO: add redraw annotation text
            }
        }

    };



    /**
     * Changes annotation points.
     * @method changeAnnotationPoints
     * @param id - Annotation ID
     * @param points - Annotation points
     */
    $scope.changeAnnotationPoints = function (id, points) {
        for (var i = 0; i < $scope.annotations.length; i++) {
            if ($scope.annotations[i].id === id) {
                $scope.annotations[i].points = points;
                break;
            }
        }
    };

    /**
     * Adds a comment to the given annotation.
     * @method addComment
     * @param id - Annotation ID
     * @param name - Commenter name
     * @param comment - Content of the given comment
     */
     $scope.addComment = function (id, name, comment) {
        for (var i = 0; i < $scope.annotations.length; i++) {
            if ($scope.annotations[i].id === id) {
                $scope.annotations[i].comments.push({
                    commenter_username: name,
                    content: comment,
                    comment_time: "now",
                    comment_relative_time: "just now"
                });
                break;
            }
        }
    };

    /**
     * Changes the visibility of an annotation. Visibility can be one of the following:
     *
     * - 1 = Myself
     * - 2 = Document owner
     * - 3 = Teacher
     * - 4 = Everyone.
     *
     * @method changeVisibility
     * @param id - Annoation ID
     * @param visibility - Annotation visibility (1, 2, 3 or 4)
     */
    $scope.changeVisibility = function (id, visiblity) {
        for (var i = 0; i < $scope.annotations.length; i++) {
            if ($scope.annotations[i].id === id) {
                 $scope.annotations[i].visible_to = visiblity;
                break;
            }
        }
    };

    /**
     * Selects text range or just the element.
     * @method selectText
     * @todo When annotations can break tags, check annotations from all elements in the selection.
     */
    $scope.selectText = function () {

        var oldElement = null;
        if ($scope.selectedElement !== null)
            oldElement = $scope.selectedElement;

        try {
            var range;
            if ($window.getSelection) {
                range = $window.getSelection();
            } else {
                range = document.getSelection();
            }
            if (range.toString().length > 0) {
                $scope.selectedArea = range.getRangeAt(0);
                $scope.selectedElement = getElementParentUntilAttribute($scope.selectedArea.startContainer, "t");
            } else {
                $scope.selectedArea = null;
            }
        } catch (err) {
            console.log("error in method selectText");
            console.log(err);
            //return;
        }

        if ($scope.selectedArea !== null) {
            // Check if selection breaks tags, has annotation as a parent or as a child.
            if (isSelectionTagParentsUnequal($scope.selectedArea) ||
                hasSelectionParentAnnotation($scope.selectedArea) ||
                hasSelectionChildrenAnnotation($scope.selectedArea)) {
                $scope.selectedArea = null;
            }
        } else if ($scope.selectedArea === null) {
            var elements = document.getElementsByClassName("lightselect");
            console.log(elements);
            if (elements.length > 0)
                $scope.selectedElement = elements[0];
        }

        var newElement = $scope.selectedElement;
        $scope.updateVelpBadge(oldElement, newElement);
        if (newElement !== null)
            $scope.updateVelpList();
    };

    /**
     * Checks if the selection breaks HTML tags. Returns true if the tags were broken.
     * @method isSelectionTagParentsUnequal
     * @param range - Range object containing the user's selection
     * @returns {boolean} Whether the HTML tags were broken or not.
     */
    var isSelectionTagParentsUnequal = function (range) {
        console.log("check tags");
        return getElementParent(range.startContainer) !== getElementParent(range.endContainer);
    };

    /**
     * Checks iteratively if the element has an annotation element as its parent.
     * @method hasSelectionParentAnnotation
     * @param range - Range object containing the user's selection
     * @returns {boolean} Whether the element has an annotation element as its parent or not
     */
    var hasSelectionParentAnnotation = function (range) {
        console.log("check parents");
        var startcont = getElementParent(range.startContainer);
        while (!startcont.hasAttribute("t")) {
            startcont = getElementParent(startcont);
            if (checkIfAnnotation(startcont) || hasAnyIllegalClass(startcont))
                return true;
        }

        var endcont = getElementParent(range.endContainer);
        while (!endcont.hasAttribute("t")) {
            endcont = getElementParent(endcont);
            if (checkIfAnnotation(endcont))
                return true;
        }

        return false;
    };

    /**
     * Checks if the element has any class in `illegalClasses` array.
     * @method hasAnyIllegalClass
     * @param element - Element to be checked
     * @returns {boolean} Whether illegal classes were found or not.
     */
    var hasAnyIllegalClass = function (element) {
        for (var i = 0; i < illegalClasses.length; i++) {
            if (element.classList.contains(illegalClasses[i]))
                return true;
        }
        return false;
    };

    /**
     * Checks recursively if the selection has any annotation elements as children.
     * @method hasSelectionChildrenAnnotation
     * @param range - Range object containing the user's selection
     * @returns {boolean} Whether the selection has any annotation elements as children or not
     */
    var hasSelectionChildrenAnnotation = function (range) {
        console.log("check children");
        var div = document.createElement("div");
        var clone = range.cloneContents();
        div.appendChild(clone);
        var children = div.childNodes;
        console.log(div);
        for (var i = 0; i < children.length; i++) {
            if (hasElementChildrenAnnotation(children[i])) {
                console.log("Child has annotation");
                return true;
            }
        }
        return false;
    };

    /**
     * Checks if the element children has an annotation element.
     * @method hasElementChildrenAnnotation
     * @param element - Element to check
     * @returns {boolean} Whether annotation was found or not
     */
    var hasElementChildrenAnnotation = function (element) {

        if (checkIfAnnotation(element)) {
            return true;
        }

        var children = element.childNodes;

        for (var i = 0; i < children.length; i++)
            if (hasElementChildrenAnnotation(children[i]))
                return true;

        return false;
    };

    /**
     * Gets the velp by its ID. If no velps are found, this method returns null.
     * @method getVelpById
     * @param id - Velp to be found
     * @returns {Object|null} Velp or null
     */
    $scope.getVelpById = function (id) {
        for (var i = 0; i < $scope.velps.length; i++)
            if ($scope.velps[i].id === id)
                return $scope.velps[i];

        return null;
    };

    /**
     * Get marking highlight style.
     * @method getAnnotationHighlight
     * @param points - Points given in marking
     * @returns {string} Highlight style
     */
    $scope.getAnnotationHighlight = function (points) {
        var highlightStyle = "positive";
        if (points === 0)
            highlightStyle = "neutral";
        else if (points < 0)
            highlightStyle = "negative";
        return highlightStyle;
    };

    /**
     * Detect user right to annotation to document.
     * @param points - Points given in velp or annotation
     * @returns {boolean} Right to make annotations
     */

    $scope.notAnnotationRights = function (points) {
        if ($scope.$parent.rights.teacher) {
            return false;
        } else {
            if (points === null) {
                return false;
            } else {
                return true;
            }
        }
    };

    /**
     * Return caption text if the user has no rights to the annotation.
     * @param state - Whether the annotation is disabled to the user or not
     * @returns {string} Caption text
     */

    $scope.showDisabledText = function(state) {
        if (state){
            return "You need to have teacher rights to make annotations with points.";
        }
    };

    /**
     * Adds an annotation with the selected velp's data to
     * the selected text area or element.
     * @method useVelp
     * @todo When the annotations can cross HTML tags, end coordinate needs to be changed according to the end element.
     * @todo Also get the paragraph element (parelement) according to endContainer.
     * @param velp - Velp selected in the `velpSelection` directive
     */
    $scope.useVelp = function (velp) {

        if ($scope.velpToEdit.id >= 0) return;

        var newAnnotation = {
            id: -($scope.annotations.length + 1),
            velp: velp.id,
            points: velp.points,
            doc_id: $scope.docId,
            visible_to: 4,
            content: velp.content,
            annotator_name: "me",
            edit_access: 1,
            email: "",
            timesince: "just now",
            creationtime: "now",
            coord: {},
            comments: [],
            newannotation: true,
            user_id: -1
        };


        if ($scope.selectedArea !== null) {

            var parelement = getElementParent($scope.selectedArea.startContainer);
            var startElement = getElementParent($scope.selectedArea.startContainer);

            var innerDiv = document.createElement('div');
            var cloned = $scope.selectedArea.cloneContents();
            innerDiv.appendChild(cloned);

            while (!parelement.hasAttribute("t")) {
                parelement = getElementParent(parelement);
            }

            var element_path = getElementPositionInTree(startElement, []);
            var answer_id = getAnswerInfo(startElement);

            if (answer_id === null){
                newAnnotation.user_id = null;
            } else {
                newAnnotation.user_id = $scope.$parent.selectedUser.id;
            }

            var startoffset = getRealStartOffset($scope.selectedArea.startContainer, $scope.selectedArea.startOffset);
            var endOffset = $scope.selectedArea.endOffset;
            if (innerDiv.childElementCount === 0)
                endOffset = startoffset + innerDiv.childNodes[innerDiv.childNodes.length - 1].length;

            newAnnotation.coord = {
                start: {
                    par_id: parelement.id,
                    t: parelement.getAttribute("t"),
                    el_path: element_path,
                    offset: startoffset,
                    depth: element_path.length
                },
                end: {
                    par_id: parelement.id,
                    t: parelement.getAttribute("t"),
                    el_path: element_path,
                    offset: endOffset,
                    depth: element_path.length
                }
            };

            if (answer_id !== null)
                newAnnotation.answer_id = answer_id.selectedAnswer.id;
            addAnnotationToElement($scope.selectedElement, newAnnotation, false, "Added also margin annotation");
            $scope.addAnnotationToCoord($scope.selectedArea, newAnnotation, true);
            $scope.annotations.push(newAnnotation);
            $scope.annotationids[newAnnotation.id] = newAnnotation.id;

            var nodeNums = getNodeNumbers($scope.selectedArea.startContainer, newAnnotation.id, innerDiv);
            newAnnotation.coord.start.node = nodeNums[0];
            newAnnotation.coord.end.node = nodeNums[1];

            console.log(newAnnotation);

            $scope.makePostRequest("/add_annotation", newAnnotation, function (json) {
                $scope.annotationids[newAnnotation.id] = json.data.id;
                console.log("Annotation to text");
                console.log(json);

            });

            $scope.selectedArea = undefined;
            velp.used += 1;
        } else if ($scope.selectedElement !== null) {

            newAnnotation.coord = {
                start: {
                    par_id: $scope.selectedElement.id,
                    t: $scope.selectedElement.getAttribute("t"),
                    offset: null
                },
                end: {
                    par_id: $scope.selectedElement.id,
                    t: $scope.selectedElement.getAttribute("t")
                }
            };

            var el_answer_id = getAnswerInfo($scope.selectedElement);

            if (el_answer_id !== null)
                newAnnotation.answer_id = el_answer_id.selectedAnswer.id;

            addAnnotationToElement($scope.selectedElement, newAnnotation, true, "No coordinate found");
            $scope.annotations.push(newAnnotation);
            $scope.annotationids[newAnnotation.id] = newAnnotation.id;

            $scope.makePostRequest("/add_annotation", newAnnotation, function (json) {
                $scope.annotationids[newAnnotation.id] = json.data.id;
                console.log("Annotation to element");
                console.log(json);
            });
            velp.used += 1;
        }

        $scope.annotationsAdded = true;
    };

    /**
     * Gets the answer info of the element. Returns null if no answer found.
     * @method getAnswerInfo
     * @param start - Paragraph where the answerbrowser element is searched for.
     * @returns {Element|null} answerbrowser element or null.
     */
    var getAnswerInfo = function (start) {

        if (start.hasAttribute("attrs") && start.hasAttribute("t")) {
            var answ = start.getElementsByTagName("answerbrowser");
            if (answ.length > 0) {
                var answScope = angular.element(answ[0]).isolateScope();
                return answScope;
            }
            return null;
        }

        var myparent = getElementParent(start);

        if (myparent.tagName === "ANSWERBROWSER") {
            return angular.element(myparent).isolateScope();
        }

        if (myparent.hasAttribute("t"))
            return null;

        return getAnswerInfo(myparent);
    };

    /**
     * Gets an array of element indexes from the TIM paragraph element to the given element.
     * TIM paragraph element is defined as an element containing a 't' attribute.
     * If the given element is inside the TIM paragraph element, this method returns the following array: [0].
     * If the given element is inside the second child element of the TIM paragraph element, the following
     * array is returned: [0, 1].
     *
     * @method getElementPositionInTree
     * @param start - Starting element
     * @param array - Array of indexes
     * @returns {Array} Array of element indexes
     */
    var getElementPositionInTree = function (start, array) {
        var myparent = getElementParent(start);

        if (myparent.hasAttribute("t")) {
            return array.reverse();
        }

        var count = 0;

        var children = getElementChildren(myparent);
        for (var i = 0; i < children.length; i++) {

            if (children[i] === start) {
                array.push(count);
                return getElementPositionInTree(myparent, array);
            }

            if (checkIfAnnotation(children[i])) {
                var innerElements = children[i].getElementsByClassName("highlighted")[0];
                var innerChildren = getElementChildren(innerElements);
                if (innerChildren.length > 2) {
                    count += innerChildren.length - 2;
                }
                continue;
            }

            count++;
        }

        throw "Element position in tree was not found";
    };

    /**
     * Get start offset according to the "original state" of DOM.
     * Ignores `annotation` elements, but not the elements inside the annotation.
     * @method getRealStartOffset
     * @param el - Start container
     * @param startoffset - Original start offset
     * @returns {int} Start offset according to the "original state" of the DOM.
     */
    var getRealStartOffset = function (el, startoffset) {

        var startType = el.nodeName;
        var storedOffset = startoffset;

        while (el.previousSibling !== null) {
            el = el.previousSibling;
            if (checkIfAnnotation(el)) {

                var innerElements = el.getElementsByClassName("highlighted")[0];
                storedOffset += innerElements.lastChild.innerHTML.length;

                if (innerElements.childNodes.length > 1) {
                    return storedOffset;
                }
            }
            else if (el.nodeName !== startType) {
                return storedOffset;
            } else {
                storedOffset += el.length;
            }
        }

        return storedOffset;
    };

    /**
     * Gets the start and end node numbers of created annotation element.
     * Ignores annoations elements, but not elements inside it.
     * @method getNodeNumbers
     * @param el - Start container
     * @param aid - Annotation ID
     * @param innerElement - Annotation content
     * @returns {Array} Array with the start and end node numbers
     */
    var getNodeNumbers = function (el, aid, innerElement) {
        var parent = el;
        while (parent.nodeName === "#text") {
            parent = parent.parentNode;
        }
        var num = 0;

        var prevNodeName = parent.childNodes[0].nodeName;

        for (var i = 0; i < parent.childNodes.length; i++) {

            if (checkIfAnnotation(parent.childNodes[i])) {

                if (parseInt(parent.childNodes[i].getAttribute("aid")) === aid) {

                    var startnum = num - 1;
                    num += innerElement.childNodes.length;

                    if (innerElement.firstChild.nodeName === prevNodeName) num--;
                    if (i < parent.childNodes.length - 1 && innerElement.lastChild.nodeName === parent.childNodes[i + 1].nodeName) num--;

                    if (startnum < 0) startnum = 0;
                    return [startnum, num];

                } else {
                    var innerEl = parent.childNodes[i].getElementsByClassName("highlighted")[0];
                    num += innerEl.childNodes.length;

                    if (innerEl.firstChild.firstChild.nodeName === prevNodeName) num--;
                    if (i < parent.childNodes.length - 1 && innerEl.lastChild.lastChild.nodeName === parent.childNodes[i + 1].nodeName) num--;

                    continue;
                }
            }

            num++;
            prevNodeName = parent.childNodes[i].nodeName;
        }

        throw "No node found";
    };

    /**
     * Gets the comments of the given annotation.
     * @method getAnnotationComments
     * @param id - Marking ID
     * @returns {Array} Annotation comments
     */
    $scope.getAnnotationComments = function (id) {
        for (var i = 0; i < $scope.annotations.length; i++) {
            if (id === $scope.annotations[i].id)
                return $scope.annotations[i].comments;
        }
    };

    /**
     * Creates the actual (pop over) annotation element.
     * @method createPopOverElement
     * @param annotation - Annotation info
     * @param show - Whether to show the annotation on creation or not
     * @returns {Element} Annotation element
     */
    $scope.createPopOverElement = function (annotation, show) {
        var element = document.createElement("span");

        //element.setAttribute("style", "line-height: 1em;");
        element.setAttribute("annotation", "");
        //element.classList.add("annotation-element");
        var velp_data = $scope.getVelpById(annotation.velp);

        var velp_content;

        if (velp_data !== null) {
            velp_content = velp_data.content;
        } else {
            velp_content = annotation.content;
        }
        console.log(annotation);
        element.setAttribute("velp", velp_content);
        element.setAttribute("points", annotation.points);
        element.setAttribute("aid", annotation.id);
        element.setAttribute("annotator", annotation.annotator_name);
        element.setAttribute("editaccess", annotation.edit_access);
        element.setAttribute("timesince", annotation.timesince);
        element.setAttribute("creationtime", annotation.creationtime);
        element.setAttribute("email", annotation.email);
        element.setAttribute("visibleto", annotation.visible_to);
        element.setAttribute("show", show);
        element.setAttribute("newannotation", annotation.newannotation);
        if (typeof annotation.reason !== "undefined")
            element.setAttribute("ismargin", true);
        else
            element.setAttribute("ismargin", false);
        element.setAttribute("comments", JSON.stringify(annotation.comments));

        return element;
    };

    /**
     * Shows the annotation (despite the name).
     * @todo If the annotation should be toggled, change all `showAnnotation()` methods to `toggleAnnotation()`.
     * @method toggleAnnotation
     * @param annotation - Annotation to be showed.
     */
    $scope.toggleAnnotation = function (annotation) {
        var parent = document.getElementById(annotation.coord.start.par_id);

        try {
                var annotationElement = parent.querySelectorAll("span[aid='{0}']".replace("{0}", annotation.id))[0];
                angular.element(annotationElement).isolateScope().showAnnotation();
                if (annotation.parentNode.classname === "notes") {
                    var abl = angular.element(parent.getElementsByTagName("ANSWERBROWSERLAZY")[0]);
                    abl.isolateScope().loadAnswerBrowser();
                }
                scrollToElement(annotationElement);
                //addAnnotationToElement(par, annotation, false, "Added also margin annotation");

        } catch (e) {
            // Find answer browser and isolate its scope
            // set answer id -> change answer to that
            // query selector element -> toggle annotation
            if (e.name === "TypeError" && annotation.answer_id !== null) {
                //var abl = angular.element(parent.getElementsByTagName("ANSWERBROWSERLAZY")[0]);
                var ab = parent.getElementsByTagName("ANSWERBROWSER")[0];

                if (typeof ab === UNDEFINED) {
                    var abl = angular.element(parent.getElementsByTagName("ANSWERBROWSERLAZY")[0]);
                    abl.isolateScope().loadAnswerBrowser();
                }
                if (this.selectedUser.id !== annotation.user_id){
                    for (var i = 0; i < this.users.length; i++) {
                        if (this.users[i].id === annotation.user_id) {
                             $scope.changeUser(this.users[i]);
                            break;
                        }
                    }

                }

                setTimeout(function () {
                    ab = angular.element(parent.getElementsByTagName("ANSWERBROWSER")[0]);
                    var abscope = ab.isolateScope();
                    abscope.review = true;
                    abscope.setAnswerById(annotation.answer_id);

                    setTimeout(function () {
                        var annotationElement = parent.querySelectorAll("span[aid='{0}']".replace("{0}", annotation.id))[0];
                        angular.element(annotationElement).isolateScope().showAnnotation();
                        $scope.$apply();
                        scrollToElement(annotationElement);
                        console.log(ab);
                    }, 500);
                }, 300);
            }
        }
    };

    /**
     * Scroll window to the given element.
     * @method scrollToElement
     * @param element - Element to scroll to.
     */
    var scrollToElement = function (element) {
        if (!!element && element.scrollIntoView) {
            element.scrollIntoView();
        }
    };
}]);