(function ()()

in synergy/client/app/js/controllers.js [3:7283]


(function () {
    function SampleSubscriber($scope, data, event) {
//    window.console.log("Received: " + event);
    }
    /**
     * Top level main controller. All other controllers are nested and have access to this $scope.
     * Handles all session related features - log in, log out, update session information in
     * SYNERGY.session and hides/displays login form
     * @param {SessionFct} sessionHttp description
     */
    function SynergyCtrl($scope, $location, sessionHttp, $cookieStore, $timeout, ngProgress, $templateCache, searchHttp, projectsHttp, sessionService, SynergyApp, specificationCache, SynergyUtils, SynergyCore) {
        $scope.SYNERGY = SynergyCore.init();
        SynergyApp.setApp($scope.SYNERGY);
        specificationCache.resetCurrentSpecification();
        $scope.suggestions = [];
        $scope.SYNERGY.publisher.subscribe(function ($scope, data, event) {
            new SampleSubscriber($scope, data, event);
        }, "specListLoaded");
        $scope.location = $location;
        $scope.$on("$routeChangeStart", function (scope, next, current) { // to hide alert box whenever path is changed
            $scope.SYNERGY.logger.print = false;
        });

        $scope.username = "";
        $scope.password = "";
        $scope.searchedItem = "";
        $scope.role = "";
        $scope.cookieChecked = false; // need to check at least once in case PHPSESSID cookie is different in Synergy.cookie
        $scope.isLoggedIn = false;
        $scope.breadcrumbs = [];
        $scope.busyBrand = "";

        /**
         * Shows generic dialog asking user to wait
         */
        $scope.showWaitDialog = function () {
            $scope.SYNERGY.modal.update("Processing data...", "Please wait");
            $scope.SYNERGY.modal.show();
        };

        /**
         * Makes Synergy logo glowing (and turning off)
         * @returns {undefined}
         */
        $scope.toggleBusyBrand = function () {
            $scope.busyBrand = $scope.busyBrand.length > 0 ? "" : "brand_busy";
        };

        $scope.getLocalTime = function (dateString, useShortMonth) {
            return SynergyUtils.UTCToLocal(dateString, useShortMonth);
        };
        $scope.getDate = function (dateString) {
            return SynergyUtils.UTCToDate(dateString);
        };
        $scope.getLocalDateTime = function (dateString) {
            return SynergyUtils.UTCToLocalDateTime(dateString);
        };
        $scope.getUTCTime = function (dateString) {
            return SynergyUtils.localToUTC(dateString);
        };

        /**
         * General method to show & log with level INFO message from HTTP Factory
         * @param {type} data
         * @param {type} status HTTP status     
         */
        $scope.generalHttpFactoryError = function (data, status) {
            $scope.SYNERGY.logger.log("Action failed", data, "INFO", "alert-error");
        };

        /**
         * Turns busy mode on, meaning logo is glowing and displays progress bar at the top of the page
         */
        $scope.busyModeOn = function () {
            $scope.busyBrand = "brand_busy";
            ngProgress.set(0);
            $timeout(function () {
                if (ngProgress.status() < 90) {
                    ngProgress.set(ngProgress.status() + Math.round(Math.random() * (10 - 5 + 1) + 5));
                }
            }, 50);
            window.document.body.style.cursor = "wait";
        };
        /**
         * Logs possible timeout
         */
        $scope.$on("possibleTimeout", function () {
            $scope.SYNERGY.logger.error("Uknown response", "Seems like timeout occurred, please try to reload page", "DEBUG");
        });

        $scope.$on("hideUserMenu", function () {
            $scope.SYNERGY.session.hideUserMenu();
        });
        $scope.$on("refreshRole", function () {
            $scope.role = $scope.SYNERGY.session.role;
        });

        $scope.$on("busyMode", function (event, args) {
            if (args) {
                $scope.busyBrand = "brand_busy";
            } else {
                window.document.body.style.cursor = "default";
                $scope.busyBrand = "";
                ngProgress.complete(100);
            }
        });

        /**
         * Sends only check for user session, if there is none, discards any session information stored in browser
         */
        $scope.init = function (callback) {
            var _c = $cookieStore.get("session"); //user key throws error
            if ($scope.cookieChecked && _c && $scope.SYNERGY.session.cookieIsValid(_c.created) && typeof _c.token !== "undefined") {// && _c.length > 0
                //var session = window.JSON.parse(_c);
                var session = _c;
                $scope.SYNERGY.session.hideLoginForm();
                $scope.SYNERGY.session.showUserMenu(session.username);
                $scope.SYNERGY.session.isLoggedIn = true;
                $scope.SYNERGY.session.username = session.username;
                $scope.SYNERGY.session.role = session.role;
                $scope.SYNERGY.session.lastName = (session.hasOwnProperty("lastName") && session.lastName.length > 0) ? session.lastName : "";
                $scope.SYNERGY.session.firstName = (session.hasOwnProperty("firstName") && session.firstName.length > 0) ? session.firstName : "";
                $scope.role = session.role;
                $scope.SYNERGY.session.session_id = session.session_id;
                $scope.SYNERGY.session.created = session.created;
                $scope.SYNERGY.session.token = session.session_id;
                sessionService.setSession($scope.SYNERGY.session);
                $timeout(callback, 0);
            } else {
                sessionHttp.infoConditional($scope, function (data) {
                    $scope.cookieChecked = true;
                    $scope.SYNERGY.session.hideLoginForm();
                    $scope.SYNERGY.session.showUserMenu(data.username);
                    $scope.SYNERGY.session.isLoggedIn = true;
                    $scope.SYNERGY.session.username = data.username;
                    $scope.SYNERGY.session.lastName = data.lastName;
                    $scope.SYNERGY.session.firstName = data.firstName;
                    $scope.SYNERGY.session.role = data.role;
                    $scope.role = data.role;
                    $scope.SYNERGY.session.session_id = data.session_id;
                    $scope.SYNERGY.session.token = data.session_id;
                    $scope.SYNERGY.session.created = data.created;
                    sessionService.setSession($scope.SYNERGY.session);
                    $cookieStore.put("session", ({firstName: data.firstName, lastName: data.lastName, username: data.username, role: data.role, token: data.token, created: 1000 * parseInt(data.created, 10), session_id: data.session_id}));
                    callback();
                }, function (data) {
                    $cookieStore.remove("session");
                    sessionService.clearSession();
                    $scope.cookieChecked = false;
                    //  $scope.SYNERGY.session.showLoginForm();
                    $scope.SYNERGY.session.hideUserMenu();
                    callback();
                });
            }
        };

        /**
         * Redirects to login page if SSO is not used. If SSO is used, it sends "active" request to server. Server checks SSO session
         * and if session exists and it's valid, returns user information. If session is not valid, server returns HTTP 307 and client redirects to 
         * SSO login page.
         */
        $scope.login = function () {
            if (!$scope.SYNERGY.useSSO) {
                $location.path("login");
            } else {
                sessionHttp.get($scope, function (data) {
                    $scope.SYNERGY.session.isLoggedIn = true;
                    $scope.SYNERGY.session.username = data.username;
                    $scope.SYNERGY.session.role = data.role;
                    $scope.role = data.role;
                    $scope.SYNERGY.session.lastName = data.lastName;
                    $scope.SYNERGY.session.firstName = data.firstName;
                    $scope.SYNERGY.session.created = 1000 * parseInt(data.created, 10);
                    $scope.SYNERGY.session.session_id = data.session_id;
                    $scope.SYNERGY.session.token = data.session_id;
                    $scope.SYNERGY.session.hideLoginForm();
                    $scope.SYNERGY.session.showUserMenu(data.username);
                    sessionService.setSession($scope.SYNERGY.session);
                    $cookieStore.put("session", ({username: data.username, role: data.role, token: data.token, created: 1000 * parseInt(data.created, 10), session_id: data.session_id}));
                    window.location.reload();
                }, function (data, status) {
                    $scope.SYNERGY.logger.log("Action failed", data + ":" + status, "DEBUG", "alert-error");
                    sessionService.clearSession();
                    status = parseInt(status, 10);
                    switch (status) {
                        case 307:
                            window.location.href = $scope.SYNERGY.getLoginRedirectUrl($scope.SYNERGY.ssoLoginUrl, window.location.href);
                            break;
                        case 400:
                            $scope.SYNERGY.logger.log("Login failed", data, "INFO", "alert-error");
                            break;
                        default:
                            $scope.SYNERGY.logger.log("Login failed", "Incorrect credentials, please try again ", "INFO", "alert-error");
                            break;
                    }
                });
            }
        };

        /**
         * Sends logout request to serverpr
         */
        $scope.logout = function () {
            $cookieStore.remove("session");
            $scope.SYNERGY.session.clearSession();
            $scope.role = "";
            sessionService.clearSession();
            sessionHttp.logout($scope, function (data) {
                if ($scope.SYNERGY.useSSO) {
                    window.location.href = $scope.SYNERGY.getLogoutRedirectUrl($scope.SYNERGY.ssoLogoutUrl, (window.location.href));
                } else {
                    window.location.reload();
                }
            }, function (data) {
                $scope.SYNERGY.logger.log("Logout failed", data.toString(), "INFO", "alert-error");
            });
        };

        /**
         * Listens to updateNavbar and highlights received link in top nav bar
         */
        $scope.$on("updateNavbar", function (event, args) {
            try {
                $("ul#navbar li").each(function () {
                    if ($(this).attr("class") === "active") {
                        $(this).attr("class", "");
                    }
                });
            } catch (e) {
            }
            try {
                $("ul#navbar li#" + args.item).attr("class", "active");
            } catch (e) {
            }
        });


        function splitMergeBreadCrumbs(currentTitle, currentTitleIndex, breadCrumbs) {
            var p1 = breadCrumbs.slice(0, currentTitleIndex);
            var p2 = breadCrumbs.slice(currentTitleIndex + 1);
            p2.push(currentTitle);
            return p1.concat(p2);
        }

        /**
         * Updates breadcrumbs menu
         */
        $scope.$on("updateBreadcrumbs", function (event, args) {
            var i = 0;
            if ($scope.breadcrumbs.length === 5) {
                for (i = 0; i < 5; i++) {
                    if (typeof $scope.breadcrumbs[i] !== "undefined" && $scope.breadcrumbs[i].title === args.title) {
                        $scope.breadcrumbs = splitMergeBreadCrumbs(args, i, $scope.breadcrumbs);
                        return;
                    }
                }

                for (i = 0; i < 4; i++) {
                    $scope.breadcrumbs[i] = $scope.breadcrumbs[i + 1];
                }
                $scope.breadcrumbs[4] = args;
            } else {
                for (i = 0; i < 4; i++) {
                    if (typeof $scope.breadcrumbs[i] !== "undefined" && $scope.breadcrumbs[i].title === args.title) {
                        $scope.breadcrumbs = splitMergeBreadCrumbs(args, i, $scope.breadcrumbs);
                        return;
                    }
                }

                if (typeof $scope.breadcrumbs[$scope.breadcrumbs.length] === "undefined" && (typeof $scope.breadcrumbs[$scope.breadcrumbs.length - 1] === "undefined" || $scope.breadcrumbs[$scope.breadcrumbs.length - 1].title !== args.title)) {
                    $scope.breadcrumbs[$scope.breadcrumbs.length] = args;
                }
            }
        });

        /**
         * Listens on key press (Enter) when cursor is in search field
         */
        $scope.synergySearch = function () {
            $location.path("search/" + ($scope.searchedItem));
        };

        $scope.goToSearch = function (suggestedItem) {
            $location.path(suggestedItem.link);
        };

        $scope.searchAhead = function () {
            if ($scope.searchedItem.length < 2) {
                return;
            }
            searchHttp.getFewSpecifications($scope, $scope.searchedItem, function (data) {
                var a = [];
                for (var i = 0, max = data.length; i < max; i++) {
                    if (data[i].project === null || data[i].project.length < 1) {
                        a.push({title: data[i].title + " (" + $scope.SYNERGY.product + " " + data[i].version + ")", link: data[i].type + "/" + data[i].id});
                    } else {
                        a.push({title: data[i].title + " (" + data[i].project + " " + data[i].version + ")", link: data[i].type + "/" + data[i].id});
                    }

                }
                $scope.suggestions = a;
                //   $("#typeahead").typeahead({source: a});
            }, function (data) {
                $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
            });
        };

        $scope.encodeURIComponent = function (s) {
            return encodeURIComponent(s);
        };

        // register bugtracking link scripts for all projects on page load  
        projectsHttp.getAll($scope, function (data) { // todo add caching
            for (var i = 0, max = data.length; i < max; i++) {
                if (data[i].reportLink !== null || data[i].viewLink !== null) {
                    var fnca,
                            fncb,
                            fncc;
                    /* jshint ignore:start */
                    try {
                        fnca = eval("(function(){ var a=" + data[i].viewLink + "; return a;})(); ");
                    } catch (e) {
                        fnca = null;
                    }
                    try {
                        fncb = eval("(function(){ var a=" + data[i].multiViewLink + "; return a;})(); ");
                    } catch (e) {
                        fncb = null;
                    }
                    try {
                        fncc = eval("(function(){ var a=" + data[i].reportLink + "; return a;})(); ");
                    } catch (e) {
                        fncc = null;
                    }

                    $scope.SYNERGY.bugtrackingSystems[data[i].name] = {
                        getDisplayLink: fnca,
                        getMultiDisplayLink: fncb,
                        getReportLink: fncc
                    };
                    /* jshint ignore:end */
                }
            }
        }, function () {
        });

    }
    function SearchCtrl($scope, $routeParams, searchHttp) {

        $scope.searched = $routeParams.search;
        $scope.results = [];
        $scope.escapedSearched = decodeURIComponent($scope.searched);

        $scope.fetch = function () {
            searchHttp.get($scope, $scope.searched, function (data) {
                $scope.results = data;
            }, function (data) {
                $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
            });
        };

        $scope.printResult = function (item) {
            switch (item.type) {
                case "specification":
                    return "<a href=\"#specification/" + item.id + "\">" + item.title + " (" + $scope.SYNERGY.product + " " + item.version + ")" + "</a>";
                case "suite":
                    return "<a href=\"#suite/" + item.id + "\">" + item.title + "</a>";
                default:
                    break;
            }
        };

        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
    }
    /**
     * Loads list of test specifications
     * @param {SpecificationsFct} specificationsHttp description
     * @param {VersionsFct} versionsHttp description
     * @param {UserFct} userFct description
     * @param {SpecificationFct} specificationFct description
     */
    function SpecPoolCtrl($scope, $routeParams, specificationsHttp, versionsHttp) {//authService
        $scope.$emit("updateNavbar", {item: "nav_specs"});
        $scope.specs = [];
        $scope.version = $routeParams.id || null; // selected version
        $scope.versions = []; // all versions available
        $scope.projects = [];
        $scope.orderProp = "title";
        $scope.rights = 0;
        $scope.isLoggedIn = (typeof $scope.SYNERGY.session.session_id !== "undefined" && $scope.SYNERGY.session.session_id.length > 1) ? 1 : 0;
        /**
         * used for caching specifications in given version. This in-memory cache is valid only while user in on this page
         */
        var cache = {};

        /**
         * Loads data from server
         */
        function init() {
            $scope.isLoggedIn = (typeof $scope.SYNERGY.session.session_id !== "undefined" && $scope.SYNERGY.session.session_id.length > 1) ? 1 : 0;
            if (typeof $scope.SYNERGY.session.session_id !== "undefined" && $scope.SYNERGY.session.session_id.length > 1) {
                $scope.rights = 1;
            }
            versionsHttp.get($scope, true, function (data) {
                data.unshift({id: -1, name: "all"});
                $scope.versions = data;
                $scope.version = $scope.version || data[0].name;
                specificationsHttp.get($scope, $scope.version, function (data) {


                    var _projects = [];
                    for (var i = 0, imax = data.length; i < imax; i++) {
                        if (data[i].projects.length > 0) {
                            data[i]._project = data[i].projects[0].name;
                            if (_projects.indexOf(data[i].projects[0].name) < 0) {
                                _projects.push(data[i].projects[0].name);
                            }
                        } else {
                            data[i]._project = $scope.SYNERGY.product;
                            data[i].projects = [{name: $scope.SYNERGY.product, id: -2}];
                            if (_projects.indexOf($scope.SYNERGY.product) < 0) {
                                _projects.push($scope.SYNERGY.product);
                            }
                        }
                    }
                    _projects.push("All");
                    $scope.projects = _projects;
                    cache[$scope.version + "projects"] = _projects;

                    $scope.specs = data;
                    cache[$scope.version] = data;
                    $scope.$emit("updateBreadcrumbs", {link: "specifications", title: "Test Specifications"});

                }, function (data, status) {
                    if (parseInt(status, 10) !== 404) {
                        $scope.SYNERGY.logger.log("Action failed", data.toString(), "INFO", "alert-error");
                    } else {
                        $scope.SYNERGY.logger.log("", "No results", "INFO");
                    }
                });
            }, $scope.generalHttpFactoryError);
        }

        $scope.init(function () {
            init();
        });

        /**
         * Loads specifications for given version. First it checks cache and if it doesn't contain data for
         * given version, asks server
         */
        $scope.filter = function () {

            if (cache.hasOwnProperty($scope.version)) {
                $scope.specs = cache[$scope.version];
                $scope.projects = cache[$scope.version + "projects"];
                return;
            }

            specificationsHttp.get($scope, $scope.version, function (data) {
                var _projects = [];
                for (var i = 0, imax = data.length; i < imax; i++) {
                    if (data[i].ext.hasOwnProperty("projects") && data[i].ext.projects.length > 0) {
                        data[i]._project = data[i].ext.projects[0].name;
                        if (_projects.indexOf(data[i].ext.projects[0].name) < 0) {
                            _projects.push(data[i].ext.projects[0].name);
                        }
                    } else {
                        data[i]._project = $scope.SYNERGY.product;
                        if (_projects.indexOf($scope.SYNERGY.product) < 0) {
                            _projects.push($scope.SYNERGY.product);
                        }
                    }
                }
                _projects.push("All");
                $scope.projects = _projects;
                $scope.specs = data;
                cache[$scope.version] = data;
                cache[$scope.version + "projects"] = _projects;
                $scope.$emit("updateBreadcrumbs", {link: "specifications", title: "Test Specifications"});

            }, function (data, status) {
                if (parseInt(status, 10) !== 404) {
                    $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
                } else {
                    $scope.SYNERGY.logger.log("", "No results", "INFO");
                }
            });
        };

    }
    /**
     *  
     * @param {SpecificationsFct} specificationsHttp
     * @param {RunsFct} runsHttp
     * @returns {undefined} 
     **/
    function HomeCtrl($scope, specificationsHttp, runsHttp, calendarHttp) {

        $scope.runs = {testRuns: []};
        $scope.specs = [];
        $scope.$emit("updateNavbar", {item: "nav_home"});

        /**
         * Loads latest test runs and test specifications
         */
        $scope.fetch = function () {
            runsHttp.getLatest($scope, 7, function (data) {
                if (typeof data.testRuns !== "undefined") {

                    data.testRuns.forEach(function (trun) {
                        if (trun.projectName === null || trun.projectName === "") {
                            trun.projectName = $scope.SYNERGY.product;
                        }
                    });

                    $scope.runs = data;
                }
            }, function (data) {
                $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
            });
            specificationsHttp.latest($scope, function (result) {
                $scope.specs = result;
                $scope.SYNERGY.publisher.publish(1, $scope, "specListLoaded");
            }, function (data) {
                $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
            });
        };

        function loadCalendar() {
            calendarHttp.getEvents($scope, function (data) {
                var items = [];

                for (var i = 0, max = data.length; i < max; i += 1) {
                    items.push({url: "#/run/" + data[i].id, title: data[i].title, start: new Date(data[i].start.substr(0, 4), parseInt(data[i].start.substr(4, 2), 10) - 1, data[i].start.substr(6, 2)), end: new Date(data[i].end.substr(0, 4), parseInt(data[i].end.substr(4, 2), 10) - 1, data[i].end.substr(6, 2))});
                }

                $("#cal").fullCalendar({
                    header: {
                        left: "prev,next today",
                        center: "title",
                        right: "month,agendaWeek,agendaDay"
                    },
                    editable: true,
                    events: items,
                    eventMouseover: function (event, jsEvent, view) {
                        if (view.name !== "agendaDay") {
                            $(jsEvent.target).attr("title", event.title);
                        }
                    }
                });
            }, function () {
                window.console.log("Failed to load calendar events");
            });
        }

        var self = $scope;
        $scope.init(function () {
            self.fetch();
            loadCalendar();
        });
    }
    /**
     * View 1
     * @param {RunFct} runHttp
     * @param {AssignmentFct} assignmentHttp description
     * @param {AttachmentFct} attachmentHttp description
     * @returns {undefined}
     */
    function RunCtrl($scope, utils, $location, $routeParams, runHttp, assignmentHttp, attachmentHttp, reviewHttp, SynergyUtils, SynergyHandlers, SynergyIssue) {

        $scope.$emit("updateNavbar", {item: "nav_runs"});
        $scope.$emit("updateBreadcrumbs", {link: "runs", title: "Test Runs"});
        $scope.id = $routeParams.id || -1;
        $scope.run = {};
        $scope.rights = 0;
        var currentActionId = -1;
        var currentAction = "";
        $scope.username = $scope.SYNERGY.session.username || "";
        $scope.attachmentBase = $scope.SYNERGY.server.buildURL("run_attachment", {});
        $scope.isLoading = false;
        $scope.tribes = [];
        var tribes = [];
        var leaderIsRemoving = false;
        $scope.explainModal = "";
        $scope.filter = {
            "assignee": "All",
            "specification": "All",
            "platform": "All",
            "tribe": "All"
        };
        var issueCollector = new SynergyIssue.RunIssuesCollector();
        $scope.P1Issues = [];
        $scope.P2Issues = [];
        $scope.P3Issues = [];
        $scope.unresolvedIssues = [];
        $scope.allIssues = [];
        $scope.specifications = [];
        $scope.assignees = [];
        $scope.project = {"name": "", "id": -1};
        $scope.pageSize = $scope.SYNERGY.assignmentPage;
        var _coverage = {};
        $scope.coverage = {};
        $scope.sortConfig = {
            property: ["id"],
            descending: [false]
        };
        $scope.orderingProperties = {
            "userDisplayName": {
                "desc": false,
                "asc": false
            },
            "specification": {
                "desc": false,
                "asc": false
            },
            "platform": {
                "desc": false,
                "asc": false
            }
        };
        $scope.testRunIsAvailable = false;
        $scope.pageReviewsExpanded = false;

        /**
         * Loads test run
         */
        $scope.fetch = function () {
            tribes = [];
            $scope.run = {};
            issueCollector = new SynergyIssue.RunIssuesCollector();
            $scope.P1Issues = [];
            $scope.P2Issues = [];
            $scope.P3Issues = [];
            $scope.unresolvedIssues = [];
            $scope.allIssues = [];
            $scope.specifications = [];
            $scope.assignees = [];
            $scope.project = {"name": "", "id": -1};
            _coverage = {};
            $scope.coverage = {};
            $scope.username = $scope.SYNERGY.session.username || "";
            runHttp.get($scope, $scope.id, function (data) {
                setProject(data);
                countResults(data);
                setTestRunIsAvailable(data);
                $scope.run = data;
                collectFilterData(data);
                $scope.$emit("updateBreadcrumbs", {link: "run/" + $scope.id + "/v/1", title: $scope.run.title});
                SynergyUtils.ProgressChart([((data.completed / data.total) * 100), 100 - ((data.completed / data.total) * 100)], ["#08c", "#ccc"], ["completed", ""], "canvas2");
                try {
                    if (data.controls.length > 0) {
                        $scope.rights = 1;
                    }
                } catch (e) {
                }
            }, $scope.generalHttpFactoryError);
        };

        function setTestRunIsAvailable(testRun) {
            var start = $scope.getDate(testRun.start);
            var stop = $scope.getDate(testRun.end);
            var today = new Date();
            $scope.testRunIsAvailable = (today >= start && today <= stop);
        }

        function setProject(data) {
            if (data.projectName !== null) {
                $scope.project = {"name": data.projectName, "id": data.projectId};
            } else {
                $scope.project = {"name": $scope.SYNERGY.product, "id": -2};
            }
        }

        $scope.toggleReivewSection = function () {
            $scope.pageReviewsExpanded = !$scope.pageReviewsExpanded;
            $("#pageReviews").collapse("toggle");
        };

        function collectFilterData(data) {
            var assignees = [];
            var platforms = [];
            var specifications = [];
            var tribes = [];
            for (var i = 0, max = data.assignments.length; i < max; i++) {
                if (assignees.indexOf(data.assignments[i].userDisplayName) < 0) {
                    assignees.push(data.assignments[i].userDisplayName);
                }
                if (specifications.indexOf(data.assignments[i].specification) < 0) {
                    specifications.push(data.assignments[i].specification);
                }
                if (platforms.indexOf(data.assignments[i].platform) < 0) {
                    platforms.push(data.assignments[i].platform);
                }
                for (var j = 0, max2 = data.assignments[i].tribes.length; j < max2; j++) {
                    if (tribes.indexOf(data.assignments[i].tribes[j]) < 0) {
                        tribes.push(data.assignments[i].tribes[j]);
                    }
                }
            }
            assignees.push("All");
            assignees.sort(function (a, b) {
                return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
            });

            specifications.push("All");
            specifications.sort(function (a, b) {
                return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
            });
            tribes.push("All");
            tribes.sort(function (a, b) {
                return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
            });

            platforms.push("All");
            platforms.sort(function (a, b) {
                return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
            });

            $scope.assignees = assignees;
            $scope.platforms = platforms;
            $scope.specifications = specifications;
            $scope.tribes = tribes;
        }

        $scope.assignessFilter = function (assignmentRecord) {
            if ($scope.filter.assignee && $scope.filter.assignee !== "All" && assignmentRecord.userDisplayName !== $scope.filter.assignee) {
                return false;
            }
            if ($scope.filter.specification && $scope.filter.specification !== "All" && assignmentRecord.specification !== $scope.filter.specification) {
                return false;
            }
            if ($scope.filter.platform && $scope.filter.platform !== "All" && assignmentRecord.platform !== $scope.filter.platform) {
                return false;
            }
            if ($scope.filter.tribe && $scope.filter.tribe !== "All" && assignmentRecord.tribes.indexOf($scope.filter.tribe) < 0) {
                return false;
            }
            return true;
        };

        /**
         * Counts number of passed/failed/skipped cases
         * @param {TestRun} run
         */
        function countResults(run) {
            var result = {
                "failed": 0,
                "passed": 0,
                "skipped": 0
            };

            if (SynergyUtils.definedNotNull(run) && SynergyUtils.definedNotNull(run.assignments)) {
                for (var i = 0, max = run.assignments.length; i < max; i++) {
                    if (!_coverage[run.assignments[i].platform]) {// FIXME
                        _coverage[run.assignments[i].platform] = {total: 0, completed: 0, name: run.assignments[i].platform};
                    }
                    issueCollector.addIssues(run.assignments[i].issues);
                    _coverage[run.assignments[i].platform].total += parseInt(run.assignments[i].total, 10);
                    _coverage[run.assignments[i].platform].completed += parseInt(run.assignments[i].completed, 10);
                    result.failed += Math.floor(run.assignments[i].failed);
                    result.passed += Math.floor(run.assignments[i].passed);
                    result.skipped += Math.floor(run.assignments[i].skipped);
                }

                var t = (result.failed + result.passed + result.skipped) / 100;
                if (t > 0) {
                    var f = Math.floor(result.failed * 10 / t) / 10;
                    var p = Math.round(result.passed * 10 / t) / 10;
                    SynergyUtils.ProgressChart([p, f, Math.round(10 * (100 - (f + p))) / 10], ["#62c462", "#ee5f5b", "#c67605"], ["passed", "failed", "skipped"], "canvas1");
                }
            }

            for (var j in _coverage) {
                if (_coverage.hasOwnProperty(j)) {
                    _coverage[j].progress = Math.round(10 * 100 * (_coverage[j].completed / _coverage[j].total)) / 10;
                }
            }
            $scope.coverage = _coverage;
            SynergyUtils.ProgressChart([issueCollector.issuesStats.opened / (issueCollector.issuesStats.total / 100), 100 - (issueCollector.issuesStats.opened / (issueCollector.issuesStats.total / 100))], ["#ccc", "#62c462"], ["Unresolved", "Resolved"], "issuesResolution");
            SynergyUtils.ProgressChart([issueCollector.issuesStats.P1 / (issueCollector.issuesStats.total / 100), issueCollector.issuesStats.P2 / (issueCollector.issuesStats.total / 100), issueCollector.issuesStats.P3 / (issueCollector.issuesStats.total / 100), issueCollector.issuesStats.P4 / (issueCollector.issuesStats.total / 100)], ["#ee5f5b", "#f89406", "#fbeed5", "#ccc"], ["P1 (" + issueCollector.issuesStats.P1 + ")", "P2 (" + issueCollector.issuesStats.P2 + ")", "P3 (" + issueCollector.issuesStats.P3 + ")", "P4 (" + issueCollector.issuesStats.P4 + ")"], "issuesPriority");
            $scope.allIssues = issueCollector.issues;
            $scope.unresolvedIssues = issueCollector.issuesStats.unresolvedIssues;
            $scope.P1Issues = issueCollector.issuesStats.P1Issues;
            $scope.P2Issues = issueCollector.issuesStats.P2Issues;
            $scope.P3Issues = issueCollector.issuesStats.P3Issues;

        }
        $scope.createCoverageChart = function (c) {
            try {
                SynergyUtils.ProgressChart([c.progress, 100 - c.progress], ["#08c", "#ccc"], ["finished", ""], "coverage" + c.name);
            } catch (e) {
            }
        };

        $scope.nextPage = function () {
            $scope.isLoading = true;
            $scope.pageSize += $scope.SYNERGY.assignmentPage;
            $scope.isLoading = false;
        };

        function resetFilters() {
            $scope.sortConfig = {
                "property": ["id"],
                "descending": [false]
            };
            $scope.orderingProperties = {
                "userDisplayName": {
                    "desc": false,
                    "asc": false
                },
                "specification": {
                    "desc": false,
                    "asc": false
                },
                "platform": {
                    "desc": false,
                    "asc": false
                }
            };
        }

        $scope.changeSorting = function (prop, order) {
            var _p = prop;
            prop = order ? prop : "-" + prop;
            if ($scope.sortConfig.property.indexOf(_p) > -1 || $scope.sortConfig.property.indexOf("-" + _p) > -1) {
                var i = $scope.sortConfig.property.indexOf(_p);
                if (i < 0) {
                    i = $scope.sortConfig.property.indexOf("-" + _p);
                }
                if ($scope.sortConfig.descending[i] === order) { // click on the same order arrow => remove it from filter
                    $scope.sortConfig.property.splice(i, 1);
                    $scope.sortConfig.descending.splice(i, 1);
                    $scope.orderingProperties[_p].desc = false; // reset css for this property
                    $scope.orderingProperties[_p].asc = false;
                    if ($scope.sortConfig.property.length === 0) { // resel css to default values and reset filter to ID 
                        resetFilters();
                    }
                } else {
                    $scope.orderingProperties[_p].desc = !$scope.orderingProperties[_p].desc; // invert css
                    $scope.orderingProperties[_p].asc = !$scope.orderingProperties[_p].asc;
                    var _orig = {// just place holder
                        "descending": !$scope.sortConfig.descending[i],
                        "property": $scope.sortConfig.property[i]
                    };
                    $scope.sortConfig.descending.splice(i, 1); // remove it from array of filters
                    $scope.sortConfig.property.splice(i, 1);
                    $scope.sortConfig.descending.splice(0, 0, _orig.descending); // insert it to level of filters at the beginning
                    (_orig.property.indexOf("-") === 0) ? $scope.sortConfig.property.splice(0, 0, _orig.property.substring(1, _orig.property.length)) : $scope.sortConfig.property.splice(0, 0, "-" + _orig.property);
                }
            } else {
                if ($scope.sortConfig.property.length === 1 && $scope.sortConfig.property[0] === "id") { // if no ordering so far, simply replace ID with selected property
                    $scope.sortConfig = {
                        property: [prop],
                        descending: [order]
                    };
                } else {
                    $scope.sortConfig.property.splice(0, 0, prop); // insert at the beginning
                    $scope.sortConfig.descending.splice(0, 0, order);
                }

                $scope.orderingProperties[_p].desc = !order; // css update
                $scope.orderingProperties[_p].asc = order;
            }
        };

        $scope.bugs = [];
        $scope.bugsAssignmentId = -1;

        $scope.alterBugs = function (assignment) {
            $scope.bugsAssignmentId = assignment.id;
            var a = [];
            for (var i = 0, max = assignment.issues.length; i < max; i++) {
                a.push({
                    "id": assignment.issues[i].bugId,
                    "stillValid": true,
                    "changeCount": false
                });
            }
            $scope.bugs = a;
            $("#ticketsModal").modal("toggle");
        };

        $scope.performAlterBugs = function () {
            var newIssues = [];
            var countDiff = 0;
            for (var i = 0, max = $scope.bugs.length; i < max; i++) {
                if ($scope.bugs[i].stillValid) {
                    newIssues.push($scope.bugs[i].id);
                } else {
                    if ($scope.bugs[i].changeCount) {
                        countDiff++;
                    }
                }
            }

            assignmentHttp.alterBugs($scope, $scope.bugsAssignmentId, {issues: newIssues.join(";"), diffCount: countDiff}, function () {
                $scope.SYNERGY.logger.log("Done", "Issues updated", "INFO", "alert-success");
                $scope.fetch();
            }, $scope.generalHttpFactoryError);


            $("#ticketsModal").modal("toggle");
        };
        /**
         * Redirects to page so user starts testing and completing his assignments
         * @param {Number} mode
         * @param {Number} assignmentId assignment ID
         */
        $scope.startAssignment = function (mode, assignmentId) {
            if (parseInt(mode, 10) === 2) {// restart => show modal confirmation
                $("#deleteModalLabel").text("Restart assignment?");
                $("#deleteModalBody").html("<p>Do you really want to restart this assignment? All saved progress will be lost as if you never started it. If you want to Continue saved assignment, please use 'Play' button instead</p>");
                $("#deleteModal").modal("toggle");
                currentAction = "restartAssignment";
                currentActionId = assignmentId;
            } else {
                $location.path("/assignment/" + assignmentId + "/v/" + mode);
            }
        };

        $scope.startReviewAssignment = function (mode, assignmentId) {
            if (parseInt(mode, 10) === 2) {// restart => show modal confirmation
                $("#deleteModalLabel").text("Restart assignment?");
                $("#deleteModalBody").html("<p>Do you really want to restart this assignment? All saved comments will be lost as if you never started it. If you want to Continue saved assignment, please use 'Play' button instead</p>");
                $("#deleteModal").modal("toggle");
                currentAction = "restartReviewAssignment";
                currentActionId = assignmentId;
            } else {
                $location.path("/review/" + assignmentId + "/continue");
            }
        };

        /**
         * Starts with action on given test run. If the action name is different than "delete", redirection is done.
         * Otherwise confirmation dialog is opened
         * @param {String} action action name
         */
        $scope.performRun = function (action) {
            switch (action) {
                case "delete":
                    $("#deleteModalLabel").text("Delete test run?");
                    $("#deleteModalBody").html("<p>Do you really want to delete test run?</p>");
                    $("#deleteModal").modal("toggle");
                    currentAction = "deleteRun";
                    break;
                case "notify":
                    $("#deleteModalLabel").text("Send notifications?");
                    $("#deleteModalBody").html("<p>Do you really want to send email notifications to testers with incomplete test assignment?</p>");
                    $("#deleteModal").modal("toggle");
                    currentAction = "notify";
                    break;
                case "freeze":
                    var target = ($scope.run.isActive ? 0 : 1);
                    runHttp.freezeRun($scope, $scope.id, target, function (data) {
                        $scope.run.isActive = target;
                        $scope.SYNERGY.logger.log("Done", "Test run " + (target === 1 ? "unfrozen" : "frozen"), "INFO", "alert-success");
                    }, $scope.generalHttpFactoryError);
                    break;
                default:
                    $location.path("/administration/run/" + $scope.id + "/" + action);
                    break;
            }
        };

        /**
         * Starts with action on given test assignment
         * Otherwise confirmation dialog is opened
         * @param {String} action action name
         * @param {Number} id assignment ID
         */
        $scope.performAssignment = function (action, id, createdBy) {
            if (action !== "delete") {
                $location.path("suite/" + id + "/" + action);
            } else {
                switch (action) {
                    case "delete":
                        currentAction = "deleteAssignment";
                        currentActionId = id;
                        leaderIsRemoving = (createdBy === 3) ? true : false;
                        $("#deleteModalLabel").text("Delete test assignment?");
                        $("#deleteModalBody").html("<p>Do you really want to delete test assignment?</p>");
                        $("#deleteModal").modal("toggle");
                        break;
                    default:
                        break;
                }
            }
        };
        $scope.performReviewAssignment = function (action, id, createdBy) {
            switch (action) {
                case "delete":
                    currentAction = "deleteReviewAssignment";
                    currentActionId = id;
                    leaderIsRemoving = (createdBy === 3) ? true : false;
                    $("#deleteModalLabel").text("Delete review assignment?");
                    $("#deleteModalBody").html("<p>Do you really want to delete review assignment?</p>");
                    $("#deleteModal").modal("toggle");
                    break;
                default:
                    $location.path("/review/" + id + "/" + action);
                    break;
            }
        };

        function deleteReviewAssignment() {
            if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                return;
            }
            reviewHttp.remove($scope, currentActionId, function (data) {
                $scope.SYNERGY.logger.log("Done", "Assignment deleted", "INFO", "alert-success");
                $scope.fetch();
            }, $scope.generalHttpFactoryError);
        }

        $scope.deleteAssignment = function () {
            if (!leaderIsRemoving) {

                if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                    return;
                }
                assignmentHttp.remove($scope, currentActionId, function (data) {
                    $scope.SYNERGY.logger.log("Done", "Assignment deleted", "INFO", "alert-success");
                    $scope.fetch();
                }, $scope.generalHttpFactoryError);
            } else {
                $("#explainModal").modal("toggle");
                if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1 || $scope.explanation.length < 1) {
                    return;
                }
                assignmentHttp.removeByLeader($scope, currentActionId, $scope.explanation, function (data) {
                    $scope.SYNERGY.logger.log("Done", "Assignment deleted", "INFO", "alert-success");
                    $scope.fetch();
                }, $scope.generalHttpFactoryError);
            }
        };

        /**
         * Executes some action based on value of $scope.currentAction
         */
        $scope.performAction = function () {
            switch (currentAction) {
                case "restartAssignment":
                    $("#deleteModal").modal("toggle");
                    $location.path("/assignment/" + currentActionId + "/v/2");
                    break;
                case "restartReviewAssignment":
                    $("#deleteModal").modal("toggle");
                    $location.path("/review/" + currentActionId + "/restart");
                    break;
                case "deleteAssignment":
                    $("#deleteModal").modal("toggle");
                    leaderIsRemoving ? $("#explainModal").modal("toggle") : $scope.deleteAssignment();
                    break;
                case "deleteReviewAssignment":
                    $("#deleteModal").modal("toggle");
                    deleteReviewAssignment();
                    break;
                case "notify":
                    $("#deleteModal").modal("toggle");
                    runHttp.sendNotifications($scope, $scope.id, function (data) {
                        $scope.SYNERGY.logger.log("Done", data, "INFO", "alert-success");
                    }, function (data) {
                        $scope.SYNERGY.logger.log("Action failed", "", "INFO", "alert-error");
                        $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
                    });
                    break;
                case "deleteRun":
                    $("#deleteModal").modal("toggle");
                    if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                        return;
                    }
                    runHttp.remove($scope, $scope.id, function (data) {
                        $scope.SYNERGY.modal.update("Test run removed", "");
                        $scope.SYNERGY.modal.show();
                        $location.path("/runs");
                    }, function (data) {
                        $scope.SYNERGY.modal.update("Action failed", "");
                        $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
                        $scope.SYNERGY.modal.show();
                    });
                    break;
                case "deleteAttachment":
                    $("#deleteModal").modal("toggle");
                    if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                        return;
                    }
                    attachmentHttp.removeRunAttachment($scope, currentActionId, function (data) {
                        $scope.SYNERGY.logger.log("Done", "Attachment deleted", "INFO", "alert-success");
                        $scope.fetch();
                    }, function (data) {
                        $scope.SYNERGY.logger.log("Action failed", "", "INFO", "alert-error");
                        $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
                        $scope.fetch();
                    });
                    break;
                default:
                    break;
            }
        };

        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });

        // ATTACHMENT UPLOAD HANDLING
        new SynergyHandlers.FileUploader([], "dropbox", $scope.SYNERGY.uploadFileLimit, $scope.SYNERGY.server.buildURL("run_attachment", {"id": $scope.id}), function (title, msg, level, style, fileName) {
            $scope.SYNERGY.logger.log(title, msg, level, style);
            $scope.fileName = fileName;
            $scope.fetch();
        }, function (title, msg, level, style) {
            $scope.SYNERGY.logger.log(title, msg, level, style);
        });
    }



    function RunCtrlCase($scope, utils, $location, $routeParams, runHttp, SynergyUtils, SynergyHandlers, SynergyIssue) {
        $scope.$emit("updateNavbar", {item: "nav_runs"});
        $scope.id = $routeParams.id || -1;

        $scope.run = {};
        $scope.project = {"name": "", "id": -1};
        $scope.isLoading = false;
        $scope.testRunIsAvailable = false;
        $scope.specs = [];
        $scope.overview = {
            totalTc: 0,
            pRate: 0,
            testers: 0,
            duration: "",
            time: ""
        };

        $scope.labels = [];
        $scope.selectedLabels = [];
        $scope.resultFilter = {
            passed: true,
            failed: true,
            skipped: true,
            passed_with_issues: true
        };

        var allExpanded = false;

        function setProject(data) {
            if (data.projectName !== null) {
                $scope.project = {"name": data.projectName, "id": data.projectId};
            } else {
                $scope.project = {"name": $scope.SYNERGY.product, "id": -2};
            }
        }

        /**
         * Loads test run
         */
        $scope.fetch = function () {
            $scope.project = {"name": "", "id": -1};
            $scope.isLoading = true;
            $scope.run = {};
            runHttp.getBlobs($scope, $scope.id, function (data) {
                $scope.run = data;
                setProject(data);
                $scope.$emit("updateBreadcrumbs", {link: "run/" + $scope.id + "/v/3", title: $scope.run.title});
                $scope.isLoading = false;
                buildData();
            }, $scope.generalHttpFactoryError);
        };

        $scope.toggleExpand = function (item) {
            item.expanded = !item.expanded;
            if (item.suites) {
                $("#spec" + item.id).collapse("toggle");
            } else {
                $("#suite" + item.id).collapse("toggle");
            }
        };

        $scope.toggleAll = function () {
            if (allExpanded) {
                $(".collapse").collapse("hide");
            } else {
                $(".collapse").collapse("show");
            }
            allExpanded = !allExpanded;
        };

        function buildData() {
            var arrObj = {};
            var knownSpecs = [];
            var _currentSuite;
            var _currentSpec;

            var totalTc = 0;
            var passedTc = 0;
            var failedTc = 0;
            var skippedTc = 0;
            var totalMins = 0;

            $scope.run.durations.forEach(function (x) {
                totalMins += x.duration;
            });

            var passedWithIssues = 0;
            var _users = [];
            var _allLabels = [];

            $scope.run.blobs.forEach(function (blob) {
                if (_users.indexOf(blob.user) < 0) {
                    _users.push(blob.user);
                }
                if (blob.label.length > 0 && _allLabels.indexOf(blob.label) < 0) {
                    _allLabels.push(blob.label);
                }

                if (knownSpecs.indexOf(blob.specification.id) < 0) {
                    knownSpecs.push(blob.specification.id);
                    arrObj["_" + blob.specification.id] = {
                        name: blob.specification.name,
                        id: blob.specification.id,
                        expanded: false,
                        label: blob.label,
                        version: blob.specification.version,
                        suites: {}
                    };
                }
                _currentSpec = arrObj["_" + blob.specification.id];
                // for each suite
                blob.specification.suites.forEach(function (suite) {

                    if (!_currentSpec.suites.hasOwnProperty("_" + suite.id)) {
                        _currentSpec.suites["_" + suite.id] = {
                            name: suite.name,
                            id: suite.id,
                            expanded: false,
                            testCases: {}
                        };
                    }

                    _currentSuite = _currentSpec.suites["_" + suite.id];

                    suite.testCases.forEach(function (tc) {
                        if (!_currentSuite.testCases.hasOwnProperty("_" + tc.id)) {
                            _currentSuite.testCases["_" + tc.id] = {
                                name: tc.name,
                                id: tc.id,
                                results: []
                            };
                        }
                        if (tc.finished === 1) {
                            totalTc++;
                            var _s = getResult(tc);
                            if (_s === "passed") {
                                passedTc++;
                            } else if (_s === "passed with issues") {
                                passedTc++;
                                passedWithIssues++;
                            } else if (_s === "failed") {
                                failedTc++;
                            } else if (_s === "skipped") {
                                skippedTc++;
                            }
                            _currentSuite.testCases["_" + tc.id].results.push({
                                result: _s,
                                visible: true,
                                issuesLbl: tc.issues.length > 1 ? "issues" : (tc.issues.length === 0 ? "" : tc.issues[0]),
                                link: "#/case/" + tc.id + "/suite/" + suite.id,
                                resultClass: _s.replace(/\s/g, "_"),
                                platform: blob.platform,
                                user: blob.user,
                                issues: tc.issues.length > 0 ? tc.issues : []
                            });
                        }

                    });
                });
            });


            var arr = [];
            var _x;
            var _y;
            var _suites;
            var _cases;
            for (var k in arrObj) {
                if (arrObj.hasOwnProperty(k)) {
                    _x = arrObj[k];
                    _suites = [];
                    for (var l in _x.suites) {
                        if (_x.suites.hasOwnProperty(l)) {

                            _y = _x.suites[l];
                            _cases = [];
                            for (var m in _y.testCases) {
                                if (_y.testCases.hasOwnProperty(m)) {
                                    _cases.push(_y.testCases[m]);
                                }
                            }
                            _y.testCases = _cases;
                            _suites.push(_y);
                        }
                    }
                    _x.suites = _suites;
                    arr.push(_x);
                }
            }

            var _pRateRound = Math.round(100 * 10 * passedTc / totalTc) / 10;
            var _fRateRound = Math.round(100 * 10 * failedTc / totalTc) / 10;
            var _pRateRound2 = Math.round(100 * 10 * passedWithIssues / totalTc) / 10;
            var _sRateRound = Math.round(10 * (100 - _pRateRound - _fRateRound - _pRateRound2)) / 10;

            var start = $scope.getDate($scope.run.start);
            var stop = $scope.getDate($scope.run.end);

            $scope.overview = {
                totalTc: totalTc,
                pRate: _pRateRound + "% passed, " + _pRateRound2 + "% passed with issues, " + _fRateRound + "% failed and " + _sRateRound + "% skipped",
                testers: _users.length,
                duration: getDuration(stop.getTime() - start.getTime()),
                time: totalMins > 59 ? Math.floor(totalMins / 60) + " hours and " + (totalMins % 60) + " minutes" : totalMins + " minutes"
            };
            $scope.specs = arr;
            $scope.labels = _allLabels;
        }


        function getDuration(duration) {

            var seconds = Math.floor(duration / 1000);
            var minutes = Math.floor(seconds / 60);
            var hours = Math.floor(minutes / 60);
            var days = Math.floor(hours / 24);
            hours = hours - (days * 24);
            minutes = minutes - (days * 24 * 60) - (hours * 60);
            seconds = seconds - (days * 24 * 60 * 60) - (hours * 60 * 60) - (minutes * 60);

            var result = "";
            if (days > 0) {
                result += (days + " days|");
            }
            if (hours > 0) {
                result += (hours + " hours|");
            }
            if (minutes > 0) {
                result += (minutes + " minutes|");
            }
            if (seconds > 0) {
                result += (seconds + " seconds|");
            }

            result = result.replace(/\|/g, " ");
            if (result.length === 0) {
                result = "0 seconds";
            }

            return result;
        }

        $scope.filter = function () {
            var _v;
            $scope.specs.forEach(function (spec) {
                spec.suites.forEach(function (suite) {
                    suite.testCases.forEach(function (tcase) {
                        tcase.results.forEach(function (result) {
                            _v = shouldDisplay(result);
                            _v = _v && ($scope.selectedLabels.length > 0 ? $scope.selectedLabels.indexOf(spec.label) > -1 : true);
                            result.visible = _v;
                        });
                    });
                });
            });





        };

        function shouldDisplay(result) {
            return $scope.resultFilter[result.resultClass];
        }

        function getResult(tc) {
            if (tc.result === "passed" && tc.issues.length > 0) {
                return "passed with issues";
            } else {
                return tc.result;
            }
        }

        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
    }

    function RunCtrlUser($scope, $location, $routeParams, runHttp, assignmentHttp, attachmentHttp, reviewHttp, SynergyUtils, SynergyHandlers, SynergyIssue) {

        $scope.$emit("updateNavbar", {item: "nav_runs"});
        $scope.id = $routeParams.id || -1;
        $scope.platforms = [];
        $scope.run = {};
        $scope.rights = 0;
        var leaderIsRemoving = false;
        $scope.explainModal = "";
        var currentActionId = -1;
        var currentAction = "";
        $scope.username = $scope.SYNERGY.session.username || "";
        $scope.attachmentBase = $scope.SYNERGY.server.buildURL("run_attachment", {});
        $scope.assignees = [];
        $scope.specifications = [];
        $scope.tribes = [];
        $scope.project = {"name": "", "id": -1};
        var tribes = [];
        $scope.filter = {
            "assignee": "All",
            "specification": "All",
            "tribe": "All"
        };
        $scope.coverage = [];
        var _coverage = [];
        $scope.isLoading = false;
        $scope.pageSize = $scope.SYNERGY.assignmentPage;
        $scope.sortConfig = {
            property: ["id"],
            descending: [false]
        };
        $scope.P1Issues = [];
        $scope.P2Issues = [];
        $scope.P3Issues = [];
        $scope.unresolvedIssues = [];
        $scope.allIssues = [];
        $scope.orderingProperties = {
            "userDisplayName": {
                "desc": false,
                "asc": false
            },
            "specification": {
                "desc": false,
                "asc": false
            }
        };
        $scope.pageReviewsExpanded = false;
        $scope.testRunIsAvailable = false;
        var issueCollector = new SynergyIssue.RunIssuesCollector();

        /**
         * Loads test run
         */
        $scope.fetch = function () {
            issueCollector = new SynergyIssue.RunIssuesCollector();
            $scope.P1Issues = [];
            tribes = [];
            _coverage = [];
            $scope.project = {"name": "", "id": -1};
            $scope.P2Issues = [];
            $scope.P3Issues = [];
            $scope.unresolvedIssues = [];
            $scope.allIssues = [];
            $scope.isLoading = true;
            $scope.coverage = [];
            $scope.tribes = [];
            $scope.platforms = [];
            $scope.assignees = [];
            $scope.specifications = [];
            $scope.run = {};
            $scope.username = $scope.SYNERGY.session.username || "";
            runHttp.getUserCentric($scope, $scope.id, function (data) {
                setTestRunIsAvailable(data);
                $scope.run = data;
                setProject(data);
                $scope.$emit("updateBreadcrumbs", {link: "run/" + $scope.id + "/v/2", title: $scope.run.title});
                getPlatforms();
                buildAssignments();
                SynergyUtils.ProgressChart([((data.completed / data.total) * 100), 100 - ((data.completed / data.total) * 100)], ["#08c", "#ccc"], ["completed", ""], "canvas2");
                try {
                    if (data.controls.length > 0) {
                        $scope.rights = 1;
                    }
                } catch (e) {
                }
                $scope.isLoading = false;
            }, $scope.generalHttpFactoryError);
        };

        $scope.toggleReivewSection = function () {
            $scope.pageReviewsExpanded = !$scope.pageReviewsExpanded;
            $("#pageReviews").collapse("toggle");
        };

        function setTestRunIsAvailable(testRun) {
            var start = $scope.getDate(testRun.start);
            var stop = $scope.getDate(testRun.end);
            var today = new Date();
            $scope.testRunIsAvailable = (today >= start && today <= stop);
        }

        function setProject(data) {
            if (data.projectName !== null) {
                $scope.project = {"name": data.projectName, "id": data.projectId};
            } else {
                $scope.project = {"name": $scope.SYNERGY.product, "id": -2};
            }
        }

        $scope.assignessFilter = function (assignmentRecord) {
            if ($scope.filter.assignee && $scope.filter.assignee !== "All" && assignmentRecord.userDisplayName !== $scope.filter.assignee) {
                return false;
            }
            if ($scope.filter.specification && $scope.filter.specification !== "All" && assignmentRecord.specification !== $scope.filter.specification) {
                return false;
            }
            if ($scope.filter.tribe && $scope.filter.tribe !== "All" && assignmentRecord.tribes.indexOf($scope.filter.tribe) < 0) {
                return false;
            }
            return true;
        };

        $scope.nextPage = function () {
            $scope.isLoading = true;
            $scope.pageSize += $scope.SYNERGY.assignmentPage;
            $scope.isLoading = false;
        };

        $scope.bugs = [];
        $scope.bugsAssignmentId = -1;

        $scope.alterBugs = function (assignment) {
            $scope.bugsAssignmentId = assignment.id;
            var a = [];
            for (var i = 0, max = assignment.issues.length; i < max; i++) {
                a.push({
                    "id": assignment.issues[i].bugId,
                    "stillValid": true,
                    "changeCount": false
                });
            }
            $scope.bugs = a;
            $("#ticketsModal").modal("toggle");
        };

        $scope.performAlterBugs = function () {
            var newIssues = [];
            var countDiff = 0;
            for (var i = 0, max = $scope.bugs.length; i < max; i++) {
                if ($scope.bugs[i].stillValid) {
                    newIssues.push($scope.bugs[i].id);
                } else {
                    if ($scope.bugs[i].changeCount) {
                        countDiff++;
                    }
                }
            }

            assignmentHttp.alterBugs($scope, $scope.bugsAssignmentId, {issues: newIssues.join(";"), diffCount: countDiff}, function () {
                $scope.SYNERGY.logger.log("Done", "Issues updated", "INFO", "alert-success");
                $scope.fetch();
            }, $scope.generalHttpFactoryError);


            $("#ticketsModal").modal("toggle");
        };

        function resetFilters() {
            $scope.sortConfig = {
                "property": ["id"],
                "descending": [false]
            };
            $scope.orderingProperties = {
                "userDisplayName": {
                    "desc": false,
                    "asc": false
                },
                "specification": {
                    "desc": false,
                    "asc": false
                }
            };
        }

        $scope.changeSorting = function (prop, order) {
            var _p = prop;
            prop = order ? prop : "-" + prop;
            if ($scope.sortConfig.property.indexOf(_p) > -1 || $scope.sortConfig.property.indexOf("-" + _p) > -1) {
                var i = $scope.sortConfig.property.indexOf(_p);
                if (i < 0) {
                    i = $scope.sortConfig.property.indexOf("-" + _p);
                }
                if ($scope.sortConfig.descending[i] === order) { // click on the same order arrow => remove it from filter
                    $scope.sortConfig.property.splice(i, 1);
                    $scope.sortConfig.descending.splice(i, 1);
                    $scope.orderingProperties[_p].desc = false; // reset css for this property
                    $scope.orderingProperties[_p].asc = false;
                    if ($scope.sortConfig.property.length === 0) { // resel css to default values and reset filter to ID 
                        resetFilters();
                    }
                } else {
                    $scope.orderingProperties[_p].desc = !$scope.orderingProperties[_p].desc; // invert css
                    $scope.orderingProperties[_p].asc = !$scope.orderingProperties[_p].asc;
                    var _orig = {// just place holder
                        "descending": !$scope.sortConfig.descending[i],
                        "property": $scope.sortConfig.property[i]
                    };
                    $scope.sortConfig.descending.splice(i, 1); // remove it from array of filters
                    $scope.sortConfig.property.splice(i, 1);
                    $scope.sortConfig.descending.splice(0, 0, _orig.descending); // insert it to level of filters at the beginning
                    (_orig.property.indexOf("-") === 0) ? $scope.sortConfig.property.splice(0, 0, _orig.property.substring(1, _orig.property.length)) : $scope.sortConfig.property.splice(0, 0, "-" + _orig.property);
                }
            } else {
                if ($scope.sortConfig.property.length === 1 && $scope.sortConfig.property[0] === "id") { // if no ordering so far, simply replace ID with selected property
                    $scope.sortConfig = {
                        property: [prop],
                        descending: [order]
                    };
                } else {
                    $scope.sortConfig.property.splice(0, 0, prop); // insert at the beginning
                    $scope.sortConfig.descending.splice(0, 0, order);
                }

                $scope.orderingProperties[_p].desc = !order; // css update
                $scope.orderingProperties[_p].asc = order;
            }
        };

        $scope.createCoverageChart = function (c) {
            try {
                SynergyUtils.ProgressChart([c.progress, 100 - c.progress], ["#08c", "#ccc"], ["finished", ""], "coverage" + c.name);
            } catch (e) {
            }
        };

        function buildAssignments() {
            var result = {
                "failed": 0,
                "passed": 0,
                "skipped": 0
            };
            var assignees = [];
            var specs = [];
            tribes = [];
            var duplicateAssignments = [];
            var prettyAssignments = [];
            var allResolved;
            for (var username in $scope.run.assignments) {
                if ($scope.run.assignments.hasOwnProperty(username)) {
                    var user = $scope.run.assignments[username];
                    var line = {
                        assignments: []
                    };
                    for (var assign = 0, maxAssign = user.assignments.length; assign < maxAssign; assign++) {
                        var assignment = user.assignments[assign];
                        line.userDisplayName = assignment.userDisplayName;
                        line.username = assignment.username;
                        line.specificationId = assignment.specificationId;
                        line.label = assignment.label;
                        line.labelId = assignment.labelId;
                        line.specification = assignment.specification;
                        line.tribes = assignment.tribes;
                        assignees.indexOf(line.userDisplayName) < 0 && assignees.push(line.userDisplayName);
                        specs.indexOf(line.specification) < 0 && specs.push(line.specification);
                        getTribes(assignment);
                        assignment.total = parseInt(assignment.total, 10);
                        assignment.completed = parseInt(assignment.completed, 10);
                        assignment.failedColor = "#62c462";
                        allResolved = allIssuesResolved(assignment.issues);
                        if (parseInt(assignment.failed, 10) > 0 && !allResolved) {
                            assignment.failedColor = "#ee5f5b";
                        }
                        if (assignment.total > 0) {
                            assignment.progress = Math.round(100 * 10 * assignment.completed / assignment.total) / 10;
                            assignment.progressLabel = "Completed " + assignment.progress + "%";
                        } else {
                            assignment.progressLabel = "No cases to test";
                            assignment.progress = 100;
                        }
                        assignment.info = (allResolved && assignment.progress === 100 && parseInt(assignment.failed, 10) === 0) ? "finished" : assignment.info;
                        var _t = (assignment.started.length > 0 ? "Started: " + assignment.started : "");
                        _t += (assignment.lastUpdated.length > 0 ? "; Last updated: " + assignment.lastUpdated : "");
                        assignment.tooltip = assignment.total > 0 ? _t : "At the time of assignment creation, there were no matching cases. You can try to start this assignment to see if situation has changed";
                        if (assignment.issues.length === 1) {
                            assignment.issuesLink = (assignment.issues.length > 0) ? "<a style='color: " + assignment.failedColor + "; font-weight: bold' href='" + $scope.SYNERGY.issues.viewLink($scope.project.name, assignment.issues) + "'>" + assignment.issues.length + " issue</a>&nbsp;" : "&nbsp;";
                        } else if (assignment.issues.length > 1) {
                            assignment.issuesLink = (assignment.issues.length > 0) ? "<a style='color: " + assignment.failedColor + "; font-weight: bold' href='" + $scope.SYNERGY.issues.viewLink($scope.project.name, assignment.issues) + "'>" + assignment.issues.length + " issues</a>&nbsp;" : "&nbsp;";
                        }
                        if (typeof line.assignments[$scope.platforms.indexOf(assignment.platform)] !== "undefined") {
                            line.assignments[$scope.platforms.indexOf(assignment.platform)].duplicates = true;
                            assignment.duplicates = true;
                            duplicateAssignments.push(assignment);
                        } else {
                            assignment.duplicates = false;
                            line.assignments[$scope.platforms.indexOf(assignment.platform)] = assignment;
                        }
                        if (!_coverage[$scope.platforms.indexOf(assignment.platform)]) { // FIXME
                            _coverage[$scope.platforms.indexOf(assignment.platform)] = {total: 0, completed: 0, name: assignment.platform};
                        }

                        _coverage[$scope.platforms.indexOf(assignment.platform)].total += parseInt(assignment.total, 10);
                        _coverage[$scope.platforms.indexOf(assignment.platform)].completed += parseInt(assignment.completed, 10);

                        result.failed += Math.floor(assignment.failed);
                        result.passed += Math.floor(assignment.passed);
                        result.skipped += Math.floor(assignment.skipped);
                    }

                    for (var p = 0, maxp = $scope.platforms.length; p < maxp; p++) {
                        if (!line.assignments[p]) {
                            line.assignments[p] = {"_hidden": new Date()};
                        }
                    }

                    prettyAssignments.push(line);
                }
            }

            // duplicates
            for (var dupl = 0, maxa = duplicateAssignments.length; dupl < maxa; dupl++) {
                prettyAssignments.push(getLineForDupliciteAssignment(duplicateAssignments[dupl]));
            }

            assignees.push("All");
            assignees.sort(function (a, b) {
                return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
            });
            specs.push("All");
            specs.sort(function (a, b) {
                return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
            });
            tribes.push("All");
            tribes.sort(function (a, b) {
                return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
            });
            $scope.assignees = assignees;
            $scope.specifications = specs;
            $scope.tribes = tribes;
            countResults(result);
            $scope.prettyAssignments = prettyAssignments;
            SynergyUtils.ProgressChart([issueCollector.issuesStats.opened / (issueCollector.issuesStats.total / 100), 100 - (issueCollector.issuesStats.opened / (issueCollector.issuesStats.total / 100))], ["#ccc", "#62c462"], ["Unresolved", "Resolved"], "issuesResolution");
            SynergyUtils.ProgressChart([issueCollector.issuesStats.unknown / (issueCollector.issuesStats.total / 100), issueCollector.issuesStats.P1 / (issueCollector.issuesStats.total / 100), issueCollector.issuesStats.P2 / (issueCollector.issuesStats.total / 100), issueCollector.issuesStats.P3 / (issueCollector.issuesStats.total / 100), issueCollector.issuesStats.P4 / (issueCollector.issuesStats.total / 100)], ["#E8EBAB", "#ee5f5b", "#f89406", "#fbeed5", "#ccc"], ["Unknown (" + issueCollector.issuesStats.unknown + ")", "P1 (" + issueCollector.issuesStats.P1 + ")", "P2 (" + issueCollector.issuesStats.P2 + ")", "P3 (" + issueCollector.issuesStats.P3 + ")", "P4 (" + issueCollector.issuesStats.P4 + ")"], "issuesPriority");
            $scope.allIssues = issueCollector.issues;
            $scope.unresolvedIssues = issueCollector.issuesStats.unresolvedIssues;
            $scope.P1Issues = issueCollector.issuesStats.P1Issues;
            $scope.P2Issues = issueCollector.issuesStats.P2Issues;
            $scope.P3Issues = issueCollector.issuesStats.P3Issues;
        }

        function getLineForDupliciteAssignment(assignment) {
            var line = {
                assignments: []
            };
            line.userDisplayName = assignment.userDisplayName;
            line.username = assignment.username;
            line.specificationId = assignment.specificationId;
            line.label = assignment.label;
            line.labelId = assignment.labelId;
            line.specification = assignment.specification;
            line.tribes = assignment.tribes;
            line.assignments[$scope.platforms.indexOf(assignment.platform)] = assignment;
            for (var p = 0, maxp = $scope.platforms.length; p < maxp; p++) {
                if (!line.assignments[p]) {
                    line.assignments[p] = {"_hidden": new Date()};
                }
            }
            return line;
        }

        function allIssuesResolved(issues) {
            var result = true;
            for (var i = 0, max = issues.length; i < max; i++) {
                issueCollector.addIssue(issues[i]);
                if (issues[i].status.toLowerCase() !== "resolved" && issues[i].status.toLowerCase() !== "closed" && issues[i].status.toLowerCase() !== "verified") {
                    result = false;
                }
            }
            return result;
        }

        function getTribes(assignment) {
            for (var i = 0, max = assignment.tribes.length; i < max; i++) {
                if (tribes.indexOf(assignment.tribes[i]) < 0) {
                    tribes.push(assignment.tribes[i]);
                }
            }
        }

        // retrieves all distinct platforms from all assignments and assign them to $scope.platforms
        function getPlatforms() {
            for (var username in $scope.run.assignments) {
                if ($scope.run.assignments.hasOwnProperty(username)) {
                    var user = $scope.run.assignments[username];
                    for (var assignment in user.assignments) {
                        if ($scope.platforms.indexOf(user.assignments[assignment].platform) < 0) {
                            $scope.platforms.push(user.assignments[assignment].platform);
                        }
                    }
                }
            }
            $scope.platforms.sort();
        }
        /**
         * Counts number of passed/failed/skipped cases
         * @param {TestRun} run
         */
        function countResults(result) {
            var t = (result.failed + result.passed + result.skipped) / 100;
            if (t > 0) {
                var f = Math.floor(result.failed * 10 / t) / 10;
                var p = Math.round(result.passed * 10 / t) / 10;
                SynergyUtils.ProgressChart([p, f, Math.round(10 * (100 - (f + p))) / 10], ["#62c462", "#ee5f5b", "#c67605"], ["passed", "failed", "skipped"], "canvas1");
            }

            for (var i = 0, max = _coverage.length; i < max; i++) {
                _coverage[i].progress = Math.round(10 * 100 * (_coverage[i].completed / _coverage[i].total)) / 10;
            }
            $scope.coverage = _coverage;

        }

        /**
         * Redirects to page so user starts testing and completing his assignments
         * @param {Number} mode
         * @param {Number} assignmentId assignment ID
         */
        $scope.startAssignment = function (mode, assignmentId) {
            if (parseInt(mode, 10) === 2) {// restart => show modal confirmation
                $("#deleteModalLabel").text("Restart assignment?");
                $("#deleteModalBody").html("<p>Do you really want to restart this assignment? All saved progress will be lost as if you never started it. If you want to Continue saved assignment, please use 'Play' button instead</p>");
                $("#deleteModal").modal("toggle");
                currentAction = "restartAssignment";
                currentActionId = assignmentId;
            } else {
                $location.path("/assignment/" + assignmentId + "/v/" + mode);
            }
        };

        /**
         * Starts with action on given test run. If the action name is different than "delete", redirection is done.
         * Otherwise confirmation dialog is opened
         * @param {String} action action name
         */
        $scope.performRun = function (action) {
            switch (action) {
                case "delete":
                    $("#deleteModalLabel").text("Delete test run?");
                    $("#deleteModalBody").html("<p>Do you really want to delete test run?</p>");
                    $("#deleteModal").modal("toggle");
                    currentAction = "deleteRun";
                    break;
                case "notify":
                    $("#deleteModalLabel").text("Send notifications?");
                    $("#deleteModalBody").html("<p>Do you really want to send email notifications to testers with incomplete test assignment?</p>");
                    $("#deleteModal").modal("toggle");
                    currentAction = "notify";
                    break;
                case "freeze":
                    var target = ($scope.run.isActive ? 0 : 1);
                    runHttp.freezeRun($scope, $scope.id, target, function (data) {
                        $scope.run.isActive = target;
                        (target === 1) ? $scope.SYNERGY.logger.log("Done", "Test run unfrozen", "INFO", "alert-success") : $scope.SYNERGY.logger.log("Done", "Test run frozen", "INFO", "alert-success");
                    }, $scope.generalHttpFactoryError);
                    break;
                default:
                    $location.path("/administration/run/" + $scope.id + "/" + action);
                    break;
            }
        };

        /**
         * Starts with action on given run attachment
         * Otherwise confirmation dialog is opened
         * @param {String} action action name
         * @param {Number} id attachment ID
         */
        $scope.performAttachment = function (action, id) {
            switch (action) {
                case "delete":
                    $("#deleteModalLabel").text("Delete attachment?");
                    $("#deleteModalBody").html("<p>Do you really want to delete attachment?</p>");
                    $("#deleteModal").modal("toggle");
                    currentAction = "deleteAttachment";
                    currentActionId = id;
                    break;
                default:
                    break;
            }
        };

        /**
         * Redirects user to page where he can create a new run assignment
         */
        $scope.forwardToCreateAssignment = function () {
            $location.path("/administration/assignment/create/run/" + $scope.id);
        };

        /**
         * Redirects user to page where he can create a new matrix run assignment
         */
        $scope.forwardToCreateMatrixAssignment = function () {
            $location.path("/administration/assignment/creatematrix/run/" + $scope.id);
        };

        /**
         * Starts with action on given test assignment
         * Otherwise confirmation dialog is opened
         * @param {String} action action name
         * @param {Number} id assignment ID
         */
        $scope.performAssignment = function (action, id, createdBy) {
            if (action !== "delete") {
                $location.path("suite/" + id + "/" + action);
            } else {
                switch (action) {
                    case "delete":
                        leaderIsRemoving = (createdBy === 3) ? true : false;
                        $("#deleteModalLabel").text("Delete test assignment?");
                        $("#deleteModalBody").html("<p>Do you really want to delete test assignment?</p>");
                        $("#deleteModal").modal("toggle");
                        currentAction = "deleteAssignment";
                        currentActionId = id;
                        break;
                    default:
                        break;
                }
            }
        };

        $scope.deleteAssignment = function () {
            if (!leaderIsRemoving) {
                if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                    return;
                }
                assignmentHttp.remove($scope, currentActionId, function (data) {
                    $scope.SYNERGY.logger.log("Done", "Assignment deleted", "INFO", "alert-success");
                    $scope.fetch();
                }, $scope.generalHttpFactoryError);
            } else {
                $("#explainModal").modal("toggle");
                if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1 || $scope.explanation.length < 1) {
                    return;
                }
                assignmentHttp.removeByLeader($scope, currentActionId, $scope.explanation, function (data) {
                    $scope.SYNERGY.logger.log("Done", "Assignment deleted", "INFO", "alert-success");
                    $scope.fetch();
                }, $scope.generalHttpFactoryError);
            }
        };

        $scope.startReviewAssignment = function (mode, assignmentId) {
            if (parseInt(mode, 10) === 2) {// restart => show modal confirmation
                $("#deleteModalLabel").text("Restart assignment?");
                $("#deleteModalBody").html("<p>Do you really want to restart this assignment? All saved comments will be lost as if you never started it. If you want to Continue saved assignment, please use 'Play' button instead</p>");
                $("#deleteModal").modal("toggle");
                currentAction = "restartReviewAssignment";
                currentActionId = assignmentId;
            } else {
                $location.path("/review/" + assignmentId + "/continue");
            }
        };

        $scope.performReviewAssignment = function (action, id, createdBy) {
            switch (action) {
                case "delete":
                    currentAction = "deleteReviewAssignment";
                    currentActionId = id;
                    leaderIsRemoving = (createdBy === 3) ? true : false;
                    $("#deleteModalLabel").text("Delete review assignment?");
                    $("#deleteModalBody").html("<p>Do you really want to delete review assignment?</p>");
                    $("#deleteModal").modal("toggle");
                    break;
                default:
                    $location.path("/review/" + id + "/" + action);
                    break;
            }
        };

        function deleteReviewAssignment() {
            if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                return;
            }
            reviewHttp.remove($scope, currentActionId, function (data) {
                $scope.SYNERGY.logger.log("Done", "Assignment deleted", "INFO", "alert-success");
                $scope.fetch();
            }, $scope.generalHttpFactoryError);
        }
        /**
         * Executes some action based on value of $scope.currentAction
         */
        $scope.performAction = function () {
            switch (currentAction) {
                case "restartAssignment":
                    $("#deleteModal").modal("toggle");
                    $location.path("/assignment/" + currentActionId + "/v/2");
                    break;
                case "deleteAssignment":
                    $("#deleteModal").modal("toggle");
                    leaderIsRemoving ? $("#explainModal").modal("toggle") : $scope.deleteAssignment();
                    break;
                case "restartReviewAssignment":
                    $("#deleteModal").modal("toggle");
                    $location.path("/review/" + currentActionId + "/restart");
                    break;
                case "deleteReviewAssignment":
                    $("#deleteModal").modal("toggle");
                    deleteReviewAssignment();
                    break;
                case "notify":
                    $("#deleteModal").modal("toggle");
                    runHttp.sendNotifications($scope, $scope.id, function (data) {
                        $scope.SYNERGY.logger.log("Done", data, "INFO", "alert-success");
                    }, $scope.generalHttpFactoryError);
                    break;
                case "deleteRun":
                    $("#deleteModal").modal("toggle");
                    if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                        return;
                    }
                    runHttp.remove($scope, $scope.id, function (data) {
                        $scope.SYNERGY.modal.update("Test run removed", "");
                        $scope.SYNERGY.modal.show();
                        $location.path("/runs");
                    }, function (data) {
                        $scope.SYNERGY.modal.update("Action failed", "");
                        $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
                        $scope.SYNERGY.modal.show();
                    });
                    break;
                case "deleteAttachment":
                    $("#deleteModal").modal("toggle");
                    if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                        return;
                    }
                    attachmentHttp.removeRunAttachment($scope, currentActionId, function (data) {
                        $scope.SYNERGY.logger.log("Done", "Attachment deleted", "INFO", "alert-success");
                        $scope.fetch();
                    }, function (data) {
                        $scope.SYNERGY.logger.log("Action failed", "", "INFO", "alert-error");
                        $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
                        $scope.fetch();
                    });
                    break;
                default:
                    break;
            }
        };

        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });

        // ATTACHMENT UPLOAD HANDLING
        new SynergyHandlers.FileUploader([], "dropbox", $scope.SYNERGY.uploadFileLimit, $scope.SYNERGY.server.buildURL("run_attachment", {"id": $scope.id}), function (title, msg, level, style, fileName) {
            $scope.SYNERGY.logger.log(title, msg, level, style);
            $scope.fileName = fileName;
            $scope.fetch();
        }, function (title, msg, level, style) {
            $scope.SYNERGY.logger.log(title, msg, level, style);
        });
    }
    /**
     * @param {VersionsFct} versionsHttp
     * @param {SpecificationFct} specificationHttp description
     * @param {SuiteFct} suiteHttp description
     * @param {AttachmentFct} attachmentHttp description
     * @param {UsersFct} usersHttp description
     *  * @param {JobFct} jobHttp description
     */
    function SpecificationCtrl($scope, utils, $location, $routeParams, versionsHttp, specificationHttp, suiteHttp, attachmentHttp, usersHttp, jobHttp, userHttp, sanitizerHttp, labelsHttp, projectsHttp, specificationCache, SynergyUtils, SynergyModels, SynergyHandlers) {//authService

        var self = this;
        $scope.project = null;
        $scope.$emit("updateNavbar", {item: "nav_specs"});
        $scope.specification = {};
        $scope.refreshCodemirror = false; // on edit/create page it is necessary to refresh editor element
        $scope.id = $routeParams.id || -1;
        $scope.rights = 0;
        $scope.readOnlyJobs = [];
        $scope.users = []; // used on edit page to select owner
        $scope.filename = "";
        $scope.attachmentBase = $scope.SYNERGY.server.buildURL("attachment", {});
        $scope.versions = [];
        $scope.version = "";
        $scope.newLabel = "";
        $scope.removeLabel = "";
        self.simpleName = $routeParams.simpleName || "";
        self.simpleVersion = $routeParams.simpleVersion || "";
        self.originalSimpleName = "";
        $scope.keepSimpleNameTrack = true;
        $scope.filterLabel = $routeParams.label || "All";
        $scope.labels = [];
        $scope.realLabels = []; // without all
        $scope.removalUsers = "";
        $scope.requestMsg = "";
        $scope.projects = [];
        var specificationDurationCache = {};
        var currentAction = "";
        var currentActionId = -1;
        var currentSuiteId = -1;

        /**
         * Loads data from server
         */
        $scope.fetch = function (useCache) {
            if (self.simpleName.length > 0) {
                loadSpecificationFromAlias();
                return;
            }

            if (window.location.href.indexOf("/title/") > -1) {// shouldn't be, caused by some issue in .htaccess
                $location.path("");
                return;
            }

            switch (getAction()) {
                case "create":
                    versionsHttp.get($scope, true, function (data) {
                        $scope.versions = data;
                        $scope.version = data[0].name || "";
                        $scope.refreshCodemirror = true;
                    }, $scope.generalHttpFactoryError);

                    projectsHttp.getAll($scope, function (data) {
                        $scope.projects = data;
                        $scope.project = $scope.projects[0];
                    }, $scope.generalHttpFactoryError);

                    break;
                case "1":
                    if ($scope.id < 0) {
                        return;
                    }
                    if (specificationCache.getCurrentSpecificationId() === parseInt($scope.id, 10)) {
                        displaySimpleSpecification(specificationCache.getCurrentSpecification());
                    } else {
                        specificationCache.resetCurrentSpecification();
                        specificationHttp.get($scope, useCache, $scope.id, function (data) {
                            displaySimpleSpecification(data);
                        }, $scope.generalHttpFactoryError);
                    }
                    break;
                case "2":
                    if ($scope.id < 0) {
                        return;
                    }
                    specificationHttp.getFull($scope, useCache, $scope.id, function (data) {
                        $scope.specification = data;
                        setProject(data);
                        specificationDurationCache.All = data.estimation;
                        specificationCache.setCurrentSpecification(data, $scope.project);
                        getRemovalUsers();
                        $scope.labels = getLabels(data);
                        $scope.newname = data.title;
                        resolveContinuousJobs(data.ext.continuous_integration);
                        $scope.$emit("updateBreadcrumbs", {link: "specification/" + $scope.id + "/v/2", title: data.title});
                        try {
                            if (data.controls.length > 0) {
                                $scope.rights = 1;
                            }
                        } catch (e) {
                        }

                    }, $scope.generalHttpFactoryError);
                    break;
                default : // edit
                    if ($scope.id < 0) {
                        return;
                    }
                    if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                        break;
                    }

                    specificationHttp.get($scope, false, $scope.id, function (data) {
                        self.originalSimpleName = data.simpleName;
                        setProject(data);
                        $scope.refreshCodemirror = true;
                        if (SynergyUtils.definedNotNull(data.ext.continuous_integration)) {
                            for (var j = 0, max = data.ext.continuous_integration.length; j < max; j++) {
                                data.ext.continuous_integration[j].jobUrl = data.ext.continuous_integration[j].jobUrl.substring(0, data.ext.continuous_integration[j].jobUrl.indexOf("/lastCompletedBuild"));
                            }
                        }
                        $scope.$emit("updateBreadcrumbs", {link: "specification/" + $scope.id, title: data.title});
                        try {
                            if (data.controls.length > 0) {
                                $scope.rights = 1;
                            }
                        } catch (e) {
                        }
                        $scope.specification = data;
                        $scope.specification.originalOwner = data.owner;

                        projectsHttp.getAll($scope, function (data) {

                            var defaultProject = true;
                            for (var p = 0, maxp = data.length; p < maxp; p++) {
                                if (data[p].name === $scope.project.name) {
                                    $scope.project.id = data[p].id;
                                    defaultProject = false;
                                    break;
                                }
                            }
                            if (defaultProject) {
                                data.push($scope.project);
                            }
                            $scope.projects = data;

                        }, $scope.generalHttpFactoryError);

                        usersHttp.getAll($scope, function (data) {
                            $scope.users = data.users;
                        }, $scope.generalHttpFactoryError);
                    }, $scope.generalHttpFactoryError);

                    break;
            }
        };

        function displaySimpleSpecification(data) {
            $scope.specification = data;
            $scope.newname = data.title;
            setProject(data);
            resolveContinuousJobs(data.ext.continuous_integration);
            getRemovalUsers();
            $scope.$emit("updateBreadcrumbs", {link: "specification/" + $scope.id + "/v/1", title: data.title});
            try {
                if (data.controls.length > 0) {
                    $scope.rights = 1;
                }
            } catch (e) {
            }
        }

        function setProject(data) {
            if (data.hasOwnProperty("ext") && data.ext.hasOwnProperty("projects") && data.ext.projects.length > 0) {
                $scope.project = data.ext.projects[0];
            } else {
                $scope.project = {"name": $scope.SYNERGY.product, id: -2};
            }
        }

        /**
         * Sets all users that requested specification removal to $scope.removalUsers
         */
        function getRemovalUsers() {
            var _l = "";
            for (var i = 0, max = $scope.specification.ext.removalRequests.length; i < max; i++) {
                _l += $scope.specification.ext.removalRequests[i].username + ", ";
            }
            $scope.removalUsers = (_l.length === 1) ? "" : _l.substr(0, _l.length - 2);
        }

        /**
         * Returns action based on URL (edit, create, 1,2)
         * @returns {String} action
         */
        function getAction() {
            var url = window.location.href;
            var stringId = $scope.id + "";
            var _s = url.lastIndexOf("/" + $scope.id + "/") + stringId.length + 2;
            var _e = url.indexOf("/", _s);
            var action = (_e > -1) ? url.substring(_s, _e) : url.substring(_s, url.length);
            if (action === "v") {
                return (url.indexOf($scope.id + "/v/1") > -1) ? "1" : "2";
            }
            return action;
        }

        $scope.getSpecificationDuration = function () {
            if (!$scope.filterLabel || $scope.filterLabel === "All") {
                return $scope.specification.estimation;
            }

            if (typeof specificationDurationCache[$scope.filterLabel] !== "undefined") {
                return specificationDurationCache[$scope.filterLabel];
            }

            var time = 0;
            for (var i = 0, max = $scope.specification.testSuites.length; i < max; i++) {
                for (var j = 0, max2 = $scope.specification.testSuites[i].testCases.length; j < max2; j++) {
                    for (var k = 0, max3 = $scope.specification.testSuites[i].testCases[j].keywords.length; k < max3; k++) {
                        if ($scope.specification.testSuites[i].testCases[j].keywords[k] === $scope.filterLabel) {
                            time += $scope.specification.testSuites[i].testCases[j].duration;
                        }
                    }
                }
            }
            specificationDurationCache[$scope.filterLabel] = time;
            return time;
        }

        /**
         * Returns true or false if test case matches label filter
         * @param {type} testCase
         * @returns {Boolean} true if test case has given label or if searched label is set to All, false if it doesn't 
         */
        $scope.hasLabel = function (testCase, a) {
            if (!$scope.filterLabel || $scope.filterLabel === "All") {
                return true;
            }
            for (var i = 0, max = testCase.keywords.length; i < max; i++) {
                if (testCase.keywords[i] === $scope.filterLabel) {
                    return true;
                }
            }
            return false;
        };
        /**
         * Asks server to sanitize input and renders it to the Preview tab
         */
        $scope.loadPreview = function () {
            var _t = "<h1>" + ($scope.specification.title || "") + "</h1><h3>Description</h3><div class='well'>" + ($scope.specification.desc || "") + "</div>";
            sanitizerHttp.getSanitizedInput($scope, _t, function (data) {
                $scope.preview = data;
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Toggle state of specification in user's favorites list
         */
        $scope.toggleFavorite = function () {
            var target = parseInt($scope.specification.isFavorite, 10) > 0 ? 0 : 1;
            var spec = new SynergyModels.Specification("", "", "", "", $scope.id);
            spec.isFavorite = target;
            userHttp.toggleFavorite($scope, spec, function (data) {
                $scope.specification.isFavorite = target;
                if (target === 0) {
                    $scope.SYNERGY.logger.log("Done", "Specification removed from favorites", "INFO", "alert-success");
                } else {
                    $scope.SYNERGY.logger.log("Done", "Specification added to favorites", "INFO", "alert-success");
                }
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Loads specification based on simple name property in URL
         * @returns {undefined}
         */
        function loadSpecificationFromAlias() {
            self.simpleName = decodeURIComponent(decodeURIComponent(self.simpleName));
            specificationHttp.getFullAlias($scope, false, self.simpleName, self.simpleVersion, function (data) {
                $scope.specification = data;
                $scope.labels = getLabels(data);
                specificationDurationCache.All = data.estimation;
                setProject(data);
                specificationCache.setCurrentSpecification(data, $scope.project);
                $scope.newname = data.title;
                $scope.id = data.id;
                resolveContinuousJobs(data.ext.continuous_integration);
                $scope.$emit("updateBreadcrumbs", {link: "specification/" + data.id + "/v/2", title: data.title});
                try {
                    if (data.controls.length > 0) {
                        $scope.rights = 1;
                    }
                } catch (e) {
                }
            }, $scope.generalHttpFactoryError);
        }

        /**
         * Collects all distinct labels from specification, adds "All" label and sorts them alphabetically
         * @param {Specification} data
         * @returns {Array} array of strings
         */
        function getLabels(data) {
            var labels = [];
            var realLabels = [];
            for (var i = 0, max = data.testSuites.length; i < max; i++) {
                for (var j = 0, max2 = data.testSuites[i].testCases.length; j < max2; j++) {
                    for (var k = 0, max3 = data.testSuites[i].testCases[j].keywords.length; k < max3; k++) {
                        if (labels.indexOf(data.testSuites[i].testCases[j].keywords[k]) < 0) {
                            labels.push(data.testSuites[i].testCases[j].keywords[k]);
                            realLabels.push(data.testSuites[i].testCases[j].keywords[k]);
                        }
                    }
                }
            }

            labels.sort(function (a, b) {
                return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
            });
            realLabels.sort(function (a, b) {
                return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
            });
            $scope.realLabels = realLabels;
            if ($scope.realLabels.length > 0) {
                $scope.removeLabel = $scope.realLabels[0];
            }
            labels.push("All");
            return labels;
        }

        /**
         * Resolve each continuous job
         */
        function resolveContinuousJobs(jobs) {

            function doResolve(data) {
                $scope.readOnlyJobs.push(data);
            }

            function logError(data) {
                $scope.SYNERGY.logger.log("Failed to load data", data, "DEBUG", "alert-error");
            }

            $scope.readOnlyJobs = [];
            if (!SynergyUtils.definedNotNull(jobs)) {
                return;
            }
            for (var i = 0, max = jobs.length; i < max; i++) {
                jobHttp.resolve($scope, jobs[i], doResolve, logError);
            }
        }

        /**
         * General attachment action
         * @param {String} action
         * @param {Number} id attachment ID
         */
        $scope.performAttachment = function (action, id) {
            switch (action) {
                case "delete":
                    $("#deleteModalLabel").text("Delete attachment?");
                    $("#deleteModalBody").html("<p>Do you really want to delete attachment?</p>");
                    $("#modal_confirm_ok").attr("ng-click", "deleteAttachment()");
                    $("#deleteModal").modal("toggle");
                    currentAction = "deleteAttachment";
                    currentActionId = id;
                    break;
                default:
                    $location.path("specification_attachment/" + id + "/" + action);
                    break;
            }
        };

        $scope.showJobsModal = function () {
            $("#jobsModal").modal("toggle");
        };

        /**
         * Removes job from specification
         */
        $scope.removeJob = function (jobId) {
            jobHttp.remove($scope, jobId, $scope.specification.id, function (data) {
                for (var i = 0, max = $scope.specification.ext.continuous_integration.length; i < max; i++) {
                    if ($scope.specification.ext.continuous_integration[i].id === jobId) {
                        $scope.specification.ext.continuous_integration.splice(i, 1);
                        break;
                    }
                }
                $scope.SYNERGY.modal.update("Job removed", "");
                $scope.SYNERGY.modal.show();
            }, function (data) {
                $scope.SYNERGY.modal.show();
                $scope.SYNERGY.logger.log("Action failed", data, "INFO", "alert-error");
                $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
            });
        };
        /**
         * Adds job to specification
         */
        $scope.addJob = function () {
            var job = new SynergyModels.Job($scope.jobToBeAdded, $scope.id, -1);
            jobHttp.create($scope, job, function (data) {
                $scope.specification.ext.continuous_integration.push(job);
                $scope.SYNERGY.modal.update("Job added", "");
                $scope.SYNERGY.modal.show();
            }, $scope.generalHttpFactoryError);
        };
        /**
         * Clones specification (sends request to server)
         * @returns {unresolved}
         */
        $scope.clone = function () {
            if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                return;
            }
            if (!$scope.newname) {
                $scope.newname = $scope.specification.title;
            }
            if ($scope.newname.length < 1 || $scope.cloneVersion.length < 1) {
                $scope.SYNERGY.modal.update("Missing parameters", "Please add a new name and version");
                $scope.SYNERGY.modal.show();
            } else {
                specificationHttp.clone($scope, currentActionId, $scope.newname, $scope.cloneVersion, function (newLink) {
                    var id = newLink.substring(newLink.indexOf("=") + 1);
                    $scope.SYNERGY.logger.log("Done", "Specification created, see " + window.location.hostname + window.location.pathname + "#/specification/" + id, "INFO", "alert-success");
                }, $scope.generalHttpFactoryError);
            }
        };

        /**
         * Starts with action on given test suite
         * Otherwise confirmation dialog is opened
         * @param {String} action action name
         * @param {Number} id suite ID
         */
        $scope.performSuite = function (action, id) {
            // delete could be done on a same page
            switch (action) {
                case "delete":
                    $("#deleteModalLabel").text("Delete test suite?");
                    $("#deleteModalBody").html("<p>Do you really want to delete test suite?</p>");
                    $("#deleteModal").modal("toggle");
                    currentAction = "deleteSuite";
                    currentActionId = id;
                    break;
                case "labels":
                    //  $("#addLabelsModalLabel").text("Add label to all cases in suite");
                    $("#addLabelsModal").modal("toggle");
                    currentAction = "labels";
                    currentActionId = id;
                    break;
                default:
                    $location.path("suite/" + id + "/" + action);
                    break;
            }

        };

        $scope.performCase = function (action, id, suiteId) {
            switch (action) {
                case "delete":
                    $("#deleteModalLabel").text("Delete test case?");
                    $("#deleteModalBody").html("<p>This will only remove reference to this test case in this suite. Continue?</p>");
                    $("#deleteModal").modal("toggle");
                    currentAction = "deleteCase";
                    currentActionId = id;
                    currentSuiteId = suiteId;
                    break;
                default:
                    break;
            }
        };

        $scope.deleteCase = function () {
            $("#deleteModal").modal("toggle");
            if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                return;
            }
            specificationCache.resetCurrentSpecification();
            var toBeRemovedCaseId = parseInt(currentActionId, 10);
            var toBeRemovedSuiteId = parseInt(currentSuiteId, 10);
            suiteHttp.removeCase($scope, currentSuiteId, currentActionId, function (data) {
                $scope.SYNERGY.logger.log("Done", "Test Case removed from test suite", "INFO", "alert-success");
                for (var i = 0, max = $scope.specification.testSuites.length; i < max; i++) {
                    if ($scope.specification.testSuites[i].id === toBeRemovedSuiteId) {
                        var s = $scope.specification.testSuites[i];
                        for (var j = 0, max2 = s.testCases.length; j < max2; j++) {
                            if (s.testCases[j].id === toBeRemovedCaseId) {
                                s.testCases.splice(j, 1);
                                return;
                            }
                        }

                    }
                }
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Executes some action based on value of $scope.currentAction
         */
        $scope.performAction = function (labelMode) {
            switch (currentAction) {
                case "deleteAttachment":
                    $scope.deleteAttachment();
                    break;
                case "cloneSpecification":
                    $scope.clone();
                    break;
                case "ownershipRequest":
                    specificationHttp.requestOwnership($scope, new SynergyModels.OwnershipRequest($scope.id, $scope.SYNERGY.session.username, $scope.requestMsg), function () {
                        $scope.SYNERGY.logger.log("Done", "Request has been sent to owner", "INFO", "alert-success");
                        $scope.fetch();
                    }, $scope.generalHttpFactoryError);
                    break;
                case "deleteSpecification":
                    $scope.deleteSpecification();
                    break;
                case "deleteSuite":
                    $("#deleteModal").modal("toggle");
                    if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                        return;
                    }
                    specificationCache.resetCurrentSpecification();
                    var toBeRemovedSuiteId = parseInt(currentActionId, 10);
                    suiteHttp.remove($scope, currentActionId, function () {
                        $scope.SYNERGY.logger.log("Done", "Test suite deleted", "INFO", "alert-success");
                        for (var i = 0, max = $scope.specification.testSuites.length; i < max; i++) {
                            if ($scope.specification.testSuites[i].id === toBeRemovedSuiteId) {
                                $scope.specification.testSuites.splice(i, 1);
                                return;
                            }
                        }
                    }, $scope.generalHttpFactoryError);
                    break;
                case "deleteCase":
                    $scope.deleteCase();
                    break;
                case "labels":
                    if (labelMode === "add") {
                        addLabels();
                    } else {
                        removeLabels();
                    }
                    break;
                default:
                    break;
            }
        };

        /**
         * Removes label from suite and then removes it from $scope.specification
         */
        function removeLabels() {
            if ($scope.removeLabel.length < 1) {
                return;
            }
            var id = parseInt(currentActionId, 10);
            specificationCache.resetCurrentSpecification();
            labelsHttp.removeFromSuite($scope, currentActionId, $scope.removeLabel, function () {
                $scope.SYNERGY.logger.log("Done", "Labels removed", "INFO", "alert-success");
                for (var i = 0, max = $scope.specification.testSuites.length; i < max; i++) {
                    if (parseInt($scope.specification.testSuites[i].id, 10) === id) {
                        for (var j = 0, max2 = $scope.specification.testSuites[i].testCases.length; j < max2; j++) {
                            var index = $scope.specification.testSuites[i].testCases[j].keywords.indexOf($scope.removeLabel);
                            if (index > -1) {
                                $scope.specification.testSuites[i].testCases[j].keywords.splice(index, 1);
                            }
                        }
                        break;
                    }
                }
                $scope.labels = getLabels($scope.specification);
            }, $scope.generalHttpFactoryError);
            $("#addLabelsModal").modal("toggle");
        }

        function addLabels() {
            if ($scope.newLabel.length < 1) {
                return;
            }
            var id = parseInt(currentActionId, 10);
            specificationCache.resetCurrentSpecification();
            labelsHttp.createForSuite($scope, currentActionId, $scope.newLabel, function () {
                $scope.SYNERGY.logger.log("Done", "Labels added", "INFO", "alert-success");
                for (var i = 0, max = $scope.specification.testSuites.length; i < max; i++) {
                    if (parseInt($scope.specification.testSuites[i].id, 10) === id) {
                        for (var j = 0, max2 = $scope.specification.testSuites[i].testCases.length; j < max2; j++) {
                            if ($scope.specification.testSuites[i].testCases[j].keywords.indexOf($scope.newLabel) < 0) {
                                $scope.specification.testSuites[i].testCases[j].keywords.push($scope.newLabel);
                            }
                        }
                        break;
                    }
                }
                $scope.labels = getLabels($scope.specification);
            }, $scope.generalHttpFactoryError);
            $("#addLabelsModal").modal("toggle");
        }

        /**
         * Removes attachment (calls server)
         * @returns {unresolved}
         */
        $scope.deleteAttachment = function () {
            $("#deleteModal").modal("toggle");
            if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                return;
            }
            var toBeRemoved = currentActionId;
            attachmentHttp.removeSpecAttachment($scope, currentActionId, $scope.specification.id, function (data) {
                $scope.SYNERGY.logger.log("Done", "Attachment deleted", "INFO", "alert-success");
                for (var i = 0, max = $scope.specification.attachments.length; i < max; i++) {
                    if ($scope.specification.attachments[i].id === toBeRemoved) {
                        $scope.specification.attachments.splice(i, 1);
                        return;
                    }
                }
            }, function (data) {
                $scope.SYNERGY.logger.log("Action failed", data, "INFO", "alert-error");
                $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
                $scope.fetch();
            });
        };

        /**
         * Executes some action with specification based on value of $scope.currentAction
         */
        $scope.performSpecification = function (action) {
            // delete could be done on a same page
            switch (action) {
                case "delete":
                    $("#deleteModalLabel").text("Delete specification?");
                    $("#deleteModalBody").html("<p>Do you really want to delete specification?</p>");
                    $("#deleteModal").modal("toggle");
                    currentAction = "deleteSpecification";
                    break;
                case "clone":
                    currentActionId = $scope.id;
                    currentAction = "cloneSpecification";
                    versionsHttp.get($scope, true, function (data) {
                        $scope.versions = data;
                        $("#dupliciteSpecModal").modal("toggle");
                    }, $scope.generalHttpFactoryError);
                    break;
                case "ownershipRequest":
                    if ($scope.SYNERGY.session.username.length < 1) {
                        return;
                    }
                    $("#ownershipRequestModal").modal("toggle");
                    currentActionId = $scope.id;
                    currentAction = "ownershipRequest";
                    break;
                default:
                    $location.path("specification/" + $scope.specification.id + "/" + action);
                    break;
            }

        };

        /**
         * Goes back in history by 1 step
         */
        $scope.cancel = function () {
            window.history.back();
        };

        /**
         * Deletes specification (calls server)
         * @returns {unresolved}
         */
        $scope.deleteSpecification = function () {
            $("#deleteModal").modal("toggle");
            if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                return;
            }
            specificationCache.resetCurrentSpecification();
            specificationHttp.remove($scope, $scope.specification.id, function (data, status) {
                if (status === 202) {
                    $scope.SYNERGY.logger.log("Done", "Request to remove this specification has been sent to owner", "INFO", "alert-success");
                    $scope.specification.ext.removalRequests.push({"username": $scope.SYNERGY.session.username});
                    getRemovalUsers();
                } else {
                    $scope.SYNERGY.modal.update("Specification deleted", "");
                    $scope.SYNERGY.modal.show();
                    $location.path("specifications");
                }
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Saves modified specification
         */
        $scope.save = function () {
            if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                return;
            }
            var spec = new SynergyModels.Specification($scope.specification.title, $scope.specification.desc, "", $scope.specification.owner, $scope.specification.id);
            var intProjectId = parseInt($scope.project.id, 10);
            spec.ext.projects = [{"name": $scope.projects.filter(function (item, index) {
                        if (parseInt(item.id, 10) === intProjectId) {
                            return item.name;
                        }
                    })[0].name, "id": intProjectId}];
            spec.setSimpleName($scope.specification.simpleName);
            if ($scope.myForm.$invalid || typeof spec.desc === "undefined" || spec.desc.length < 0) {
                $scope.SYNERGY.modal.update("Missing required fields", "");
                $scope.SYNERGY.modal.show();
                return;
            }
            specificationCache.resetCurrentSpecification();
            specificationHttp.edit($scope, spec, $scope.minorEdit ? true : false, $scope.keepSimpleNameTrack, function (data) {
                $scope.SYNERGY.modal.update("Specification updated", "");
                $scope.SYNERGY.modal.show();
                $location.path("specification/" + $scope.specification.id);
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Creates a new specification
         * @returns {unresolved}
         */
        $scope.create = function () {
            if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                return;
            }
            if ($scope.myForm.$invalid || typeof $scope.specification.desc === "undefined" || $scope.specification.desc.length < 0) {
                $scope.SYNERGY.modal.update("Missing required fields", "");
                $scope.SYNERGY.modal.show();
                return;
            }
            specificationCache.resetCurrentSpecification();
            var spec = new SynergyModels.Specification($scope.specification.title, $scope.specification.desc, $scope.version, "", -1);
            var intProjectId = parseInt($scope.project.id, 10);
            spec.ext.projects = [{"name": $scope.projects.filter(function (item, index) {
                        if (parseInt(item.id, 10) === intProjectId) {
                            return item.name;
                        }
                    })[0].name, "id": intProjectId}];
            spec.setSimpleName($scope.specification.title);
            specificationHttp.create($scope, spec, function (data) {
                $scope.SYNERGY.modal.update("Specification created", "");
                var id = data.substring(data.indexOf("=") + 1, data.length - 1);
                $location.path("specification/" + id);
                $scope.SYNERGY.modal.show();
            }, $scope.generalHttpFactoryError);
        };

// ATTACHMENT UPLOAD
        new SynergyHandlers.FileUploader([], "dropbox", $scope.SYNERGY.uploadFileLimit, $scope.SYNERGY.server.buildURL("attachment", {"id": $scope.id, "type": "specification"}), function (title, msg, level, style, fileName) {
            $scope.SYNERGY.logger.log(title, msg, level, style);
            $scope.fileName = fileName;
            specificationCache.resetCurrentSpecification();
            attachmentHttp.getAttachmentsForSpecification($scope, $scope.specification.id, function (data) {
                $scope.specification.attachments = data;
            }, function (data) {
                $scope.SYNERGY.logger.log("Failed to refresh list of attachments", data, "INFO", "alert-error");
                $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
                try {
                    if (!$scope.$$phase) {
                        $scope.$apply();
                    }
                } catch (e) {
                }
            });
        }, function (title, msg, level, style) {
            $scope.SYNERGY.logger.log("Action failed", msg, "INFO", "alert-error");
            $scope.SYNERGY.logger.log(title, msg, level, style);
            try {
                if (!$scope.$$phase) {
                    $scope.$apply();
                }
            } catch (e) {
            }
        });

        $scope.uploadFile = function () {
            new SynergyHandlers.FileUploader([], "dropbox", $scope.SYNERGY.uploadFileLimit, $scope.SYNERGY.server.buildURL("attachment", {"id": $scope.id, "type": "specification"}), function (title, msg, level, style, fileName) {
                $scope.SYNERGY.logger.log(title, msg, level, style);
                $scope.fileName = fileName;
                specificationCache.resetCurrentSpecification();
                attachmentHttp.getAttachmentsForSpecification($scope, $scope.specification.id, function (data) {
                    $scope.specification.attachments = data;
                }, function (data) {
                    $scope.SYNERGY.logger.log("Failed to refresh list of attachments", data, "INFO", "alert-error");
                    $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
                    try {
                        if (!$scope.$$phase) {
                            $scope.$apply();
                        }
                    } catch (e) {
                    }
                });
            }, function (title, msg, level, style) {

                $scope.SYNERGY.logger.log("Action failed", msg, "INFO", "alert-error");
                $scope.SYNERGY.logger.log(title, msg, level, style);
                try {
                    if (!$scope.$$phase) {
                        $scope.$apply();
                    }
                } catch (e) {
                }
            }).uploadFileFromFileChooser("fileToUpload");
        };
        var scope = $scope;
        $scope.init(function () {
            scope.fetch(true);
        });
    }
    /**
     * 
     * @param {SpecificationFct} specificationHttp
     * @param {SuiteFct} suiteHttp
     * @param {CasesFct} casesHttp
     * @returns {undefined}
     */
    function SuiteCtrl($scope, utils, $location, $routeParams, $timeout, specificationHttp, suiteHttp, casesHttp, productsHttp, sanitizerHttp, specificationCache, SynergyModels) {//authService
        $scope.$emit("updateNavbar", {item: "nav_specs"});
        $scope.suite = {};
        $scope.project = "";
        $scope.refreshCodemirror = false;
        $scope.id = $routeParams.id || -1;
        $scope.rights = 0;
        // for create only
        $scope.c_version = $routeParams.version || ""; // used on create page to display version
        $scope.c_specificationId = $routeParams.specification || ""; // used on create page to display specification link
        $scope.c_specification = {}; // used on create page to display specification title
        $scope.case_suggestions = []; // matching cases when adding existing case to suite
        $scope.caseToBeAdded = ""; // the name that user types in add existing case dialog
        $scope.availableProducts = false;
        $scope.products = [];
        $scope.oldNotification = "";
        $scope.components = [];

        var currentAction = "";
        var currentActionId = -1;

        /**
         * Loads data from server
         */
        $scope.fetch = function () {
            var action = window.location + "";
            action = action.substring(action.lastIndexOf("/") + 1);
            switch (action) {
                case "create":
                    if (parseInt($scope.c_specificationId, 10) > 0) {
                        specificationHttp.get($scope, true, $scope.c_specificationId, function (data) {
                            setLastSuiteOrder(data);
                            setProject(data);
                            $scope.c_specification = data;
                            loadProducts();
                        }, $scope.generalHttpFactoryError);
                    }
                    break;
                case "1":
                    if ($scope.id < 0) {
                        return;
                    }
                    var cachedSuite = specificationCache.getCurrentSuite(parseInt($scope.id, 10));
                    if (cachedSuite) {
                        $scope.suite = cachedSuite;
                        $scope.project = specificationCache.getCurrentProjectName();
                        $scope.$emit("updateBreadcrumbs", {link: "suite/" + $scope.id + "/v/1", title: $scope.suite.title});
                        try {
                            if ($scope.suite.controls.length > 0) {
                                $scope.rights = 1;
                            }
                        } catch (e) {
                        }
                        return;
                    }



                    suiteHttp.get($scope, true, $scope.id, function (data) {
                        $scope.suite = data;
                        setProject(data);
                        $scope.$emit("updateBreadcrumbs", {link: "suite/" + $scope.id + "/v/1", title: data.title});
                        try {
                            if (data.controls.length > 0) {
                                $scope.rights = 1;
                            }
                        } catch (e) {
                        }
                    }, $scope.generalHttpFactoryError);
                    break;
                default :
                    if ($scope.id < 0) {
                        return;
                    }
                    if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                        break;
                    }
                    suiteHttp.get($scope, false, $scope.id, function (data) {
                        $scope.suite = data;
                        setProject(data);
                        $scope.refreshCodemirror = true;
                        try {
                            if (data.controls.length > 0) {
                                $scope.rights = 1;
                            }
                        } catch (e) {
                        }

                        loadProducts();

                    }, $scope.generalHttpFactoryError);
                    break;
            }
        };

        function setProject(data) {
            if (data.hasOwnProperty("ext") && data.ext.hasOwnProperty("projects") && data.ext.projects.length > 0) {
                $scope.project = data.ext.projects[0].name;
            } else {
                $scope.project = $scope.SYNERGY.product;
            }
        }

        function setLastSuiteOrder(data) {
            try {
                $scope.suite.order = (data.testSuites[data.testSuites.length - 1].order + 1) || 1;
            } catch (e) {
                $scope.suite.order = 1;
            }
        }
        /**
         * Loads sanitized preview from server
         */
        $scope.loadPreview = function () {
            var _t = "<h1>" + ($scope.suite.title || "") + "</h1><h3>Setup</h3><div class='well'>" + ($scope.suite.desc || "") + "</div>";
            sanitizerHttp.getSanitizedInput($scope, _t, function (data) {
                $scope.preview = data;
            }, $scope.generalHttpFactoryError);
        };

        function loadProducts() {
            productsHttp.get($scope, function (data) {
                if (data.length > 0) {
                    var p = [];
                    for (var j = 0, max2 = data.length; j < max2; j++) {
                        p.push(new SynergyModels.Product(data[j].name, data[j].components));
                    }
                    $scope.products = p;
                    $scope.availableProducts = true;

                    var oldPreferences = $scope.SYNERGY.cache.get("product_component");
                    if (oldPreferences && (($scope.suite.product === "unknown" && $scope.suite.component === "unknown") || (typeof $scope.suite.product === "undefined"))) {
                        for (var i = 0, max = $scope.products.length; i < max; i++) {
                            if (data[i].name === oldPreferences.product) {
                                $scope.suite.product = $scope.products[i];//select current product in form
                                $scope.suite.component = oldPreferences.component;
                                setComponent(i);
                                $scope.oldNotification = "Selected product/component are based on previously used values and do not match actual settings of this suite";
                                return;
                            }
                        }
                    } else {
                        for (var i = 0, max = $scope.products.length; i < max; i++) {
                            if (data[i].name === $scope.suite.product || typeof $scope.suite.product === "undefined") {
                                $scope.suite.product = $scope.products[i];//select current product in form
                                setComponent(i);
                                return;
                            }
                        }
                    }
                    $scope.suite.product = $scope.products[0];
                    setComponent(0);

                }
            }, function () {
            });
        }

        function setComponent(productIndex) {
            $scope.components = $scope.products[productIndex].components;
            for (var i = 0, max = $scope.components.length; i < max; i++) {
                if ($scope.components[i].name === $scope.suite.component || typeof $scope.suite.component === "undefined" || (typeof $scope.suite.component.name !== "undefined" && $scope.components[i].name === $scope.suite.component.name)) {
                    $scope.suite.component = $scope.components[i];
                    return;
                }
            }

            $scope.suite.component = $scope.components[0];
        }

        $scope.productChanged = function () {
            for (var i = 0, max = $scope.products.length; i < max; i++) {
                if ($scope.products[i].name === $scope.suite.product.name) {
                    $scope.suite.product = $scope.products[i];//select current product in form
                    setComponent(i);
                    break;
                }
            }
        };

        /**
         * Starts with action on given test case
         * Otherwise confirmation dialog is opened
         * @param {String} action action name
         * @param {Number} id suite ID
         */
        $scope.performCase = function (action, id) {
            switch (action) {
                case "delete":
                    $("#deleteModalLabel").text("Delete test case?");
                    $("#deleteModalBody").html("<p>This will only remove reference to this test case in this suite. Continue?</p>");
                    $("#deleteModal").modal("toggle");
                    currentAction = "deleteCase";
                    currentActionId = id;
                    break;
                default:
                    $location.path("case/" + id + "/suite/" + $scope.id + "/" + action);
                    break;
            }
        };

        /**
         * Executes some action based on value of currentAction
         */
        $scope.performAction = function () {
            switch (currentAction) {
                case "deleteCase":
                    $scope.deleteCase();
                    break;
                case "deleteSuite":
                    $scope.deleteSuite();
                    break;
                default:
                    break;
            }
        };

        /**
         * Removes test case
         */
        $scope.deleteCase = function () {
            $("#deleteModal").modal("toggle");
            if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                return;
            }
            specificationCache.resetCurrentSpecification();
            var caseToBeRemovedId = parseInt(currentActionId, 10);
            suiteHttp.removeCase($scope, $scope.suite.id, currentActionId, function (data) {
                $scope.SYNERGY.logger.log("Done", "Test Case removed from test suite", "INFO", "alert-success");
                for (var i = 0, max = $scope.suite.testCases.length; i < max; i++) {
                    if ($scope.suite.testCases[i].id === caseToBeRemovedId) {
                        $scope.suite.testCases.splice(i, 1);
                        return;
                    }
                }
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Adds case to suite
         * @param {type} caseId
         */
        $scope.addCase = function (caseId) {
            $("#addCaseModal").modal("toggle");
            specificationCache.resetCurrentSpecification();
            suiteHttp.addCase($scope, $scope.id, caseId, function (data) {
                $scope.SYNERGY.logger.log("Done", "Test Case added to test suite", "INFO", "alert-success");
                $scope.fetch();
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Starts with action on given test suite
         * Otherwise confirmation dialog is opened
         * @param {String} action action name
         * @param {Number} id suite ID
         */
        $scope.performSuite = function (action) {
            switch (action) {
                case "delete":
                    $("#deleteModalLabel").text("Delete test suite?");
                    $("#deleteModalBody").html("<p>Do you really want to delete test suite?</p>");
                    $("#deleteModal").modal("toggle");
                    currentAction = "deleteSuite";
                    break;
                default:
                    $location.path("suite/" + $scope.suite.id + "/" + action);
                    break;
            }
        };

        $scope.cancel = function () {
            window.history.back();
        };

        /**
         * Deletes test suite
         */
        $scope.deleteSuite = function () {
            $("#deleteModal").modal("toggle");
            if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                return;
            }
            specificationCache.resetCurrentSpecification();
            suiteHttp.remove($scope, $scope.suite.id, function (data) {
                $scope.SYNERGY.modal.update("Test Suite deleted", "");
                $scope.SYNERGY.modal.show();
                $location.path("specification/" + $scope.suite.specificationId);
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Saves modifications in suite
         */
        $scope.save = function () {
            if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                return;
            }
            if ($scope.myForm.$invalid || $scope.myForm2.$invalid || typeof $scope.suite.desc === "undefined" || $scope.suite.desc.length < 0) {
                $scope.SYNERGY.modal.update("Missing required fields", "");
                $scope.SYNERGY.modal.show();
                return;
            }
            specificationCache.resetCurrentSpecification();
            var product = ($scope.availableProducts) ? $scope.suite.product.name : $scope.suite.product;
            var component = ($scope.availableProducts) ? $scope.suite.component.name : $scope.suite.component;
            var suite = new SynergyModels.Suite($scope.suite.title, $scope.suite.desc, product, component, $scope.suite.id);
            suite.order = $scope.suite.order;
            suiteHttp.edit($scope, suite, $scope.minorEdit ? true : false, function (data) {
                $scope.SYNERGY.modal.update("Test Suite updated", "");
                $scope.SYNERGY.modal.show();
                window.history.back();
            }, $scope.generalHttpFactoryError);

            if ($scope.availableProducts) {
                $scope.SYNERGY.cache.put("product_component", {"product": $scope.suite.product.name, "component": $scope.suite.component.name});
            }

        };

        /**
         * Creates a new test suite
         */
        $scope.create = function () {
            $scope.suite.specificationId = $scope.c_specificationId;

            if ($scope.myForm.$invalid || $scope.myForm2.$invalid || typeof $scope.suite.desc === "undefined" || $scope.suite.desc.length < 0) {
                $scope.SYNERGY.modal.update("Missing required fields", "");
                $scope.SYNERGY.modal.show();
                return;
            }

            if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                return;
            }
            specificationCache.resetCurrentSpecification();
            var product = ($scope.availableProducts) ? $scope.suite.product.name : $scope.suite.product;
            var component = ($scope.availableProducts) ? $scope.suite.component.name : $scope.suite.component;
            var suite = new SynergyModels.Suite($scope.suite.title, $scope.suite.desc, product, component, -1);
            suite.order = $scope.suite.order;
            suite.specificationId = $scope.suite.specificationId;
            suiteHttp.create($scope, suite, function (data) {
                $scope.SYNERGY.modal.update("Test Suite created", "");
                $scope.SYNERGY.modal.show();
                window.history.back();
            }, $scope.generalHttpFactoryError);

            if ($scope.availableProducts) {
                $scope.SYNERGY.cache.put("product_component", {"product": $scope.suite.product.name, "component": $scope.suite.component});
            }

        };

        /**
         * Displays dialog for adding existing cases to suite
         */
        $scope.showAddCaseModal = function () {
            $scope.case_suggestions = [];
            $scope.caseToBeAdded = "";
            $("#addCaseModal").modal("toggle");
        };

        /**
         * Reduces list of offered cases based on $scope.caseToBeAdded
         */
        $scope.filterCases = function () {
            $timeout(function () {
                casesHttp.getMatching($scope, $scope.caseToBeAdded, function (data) {
                    $scope.case_suggestions = data;
                }, $scope.generalHttpFactoryError);
            }, 600);
        };
// INIT
        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
    }
    /**
     * 
     * @param {SuiteFct} suiteHttp
     * @param {CaseFct} caseHttp
     * @param {ImageFct} imageHttp
     * @returns {undefined} */
    function CaseCtrl($scope, utils, $location, $routeParams, suiteHttp, caseHttp, imageHttp, issueHttp, labelHttp, sanitizerHttp, specificationCache, SynergyModels, SynergyHandlers) {//authService
        $scope.$emit("updateNavbar", {item: "nav_specs"});
        $scope.testCase = {};
        $scope.project = "";
        $scope.refreshCodemirror = false;
        $scope.id = $routeParams.id || -1;
        $scope.parentSuite = $routeParams.parent || -1;
        $scope.rights = 0;
        var currentAction = "";
        var currentActionId = -1;
        $scope.labelToBeAdded = "";
        $scope.preview = ($scope.parentSuite < 0) ? 1 : 0; // whether or not the case is displayed without suite context
        $scope.c_suite = {}; // used in create page to display suite information
        var originalDuration = 0; // used in edit page, when user submits edited case, when submitted duration is different than originalDuration, server restarts duration count

        $scope.loadPreview = function () {
            var _t = "<h1>" + ($scope.testCase.title || "") + "</h1><h3>Steps</h3><div>" + ($scope.testCase.steps || "") + "</div><h3>Expected result:</h3><div class='result well'>" + ($scope.testCase.result || "") + "</div>";
            sanitizerHttp.getSanitizedInput($scope, _t, function (data) {
                $scope.preview = data;
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Loads data from server
         */
        $scope.fetch = function (useCache) {
            var action = window.location + "";
            action = action.substring(action.lastIndexOf("/") + 1);
            switch (action) {
                case "create":
                    $scope.testCase.steps = "<ol>\n<li></li>\n<li></li>\n<li></li>\n<li></li>\n<ol>";
                    $scope.testCase.duration = 1;
                    if (parseInt($scope.parentSuite, 10) > 0) {
                        suiteHttp.get($scope, useCache, $scope.parentSuite, function (data) {
                            setProject(data);
                            setLastCaseOrder(data);

                            $scope.c_suite = data;
                        }, $scope.generalHttpFactoryError);
                    }
                    break;
                case "1":
                    if ($scope.id < 0) {
                        return;
                    }
                    var cachedCase = specificationCache.getCurrentCase(parseInt($scope.id, 10), parseInt($scope.parentSuite, 10));
                    if (cachedCase) {
                        $scope.testCase = cachedCase;
                        $scope.project = specificationCache.getCurrentProjectName();
                        $scope.$emit("updateBreadcrumbs", {link: "case/" + $scope.id + "/suite/" + $scope.parentSuite + "/v/1", title: $scope.testCase.title});
                        try {
                            if ($scope.testCase.controls.length > 0) {
                                $scope.rights = 1;
                            }
                        } catch (e) {
                        }
                        return;
                    }

                    caseHttp.get($scope, useCache, $scope.id, $scope.parentSuite, function (data) {
                        $scope.testCase = data;
                        setProject(data);
                        $scope.$emit("updateBreadcrumbs", {link: "case/" + $scope.id + "/suite/" + $scope.parentSuite + "/v/1", title: data.title});
                        try {
                            if (data.controls.length > 0) {
                                $scope.rights = 1;
                            }
                        } catch (e) {
                        }
                    }, $scope.generalHttpFactoryError);
                    break;
                default :
                    if ($scope.id < 0) {
                        return;
                    }
                    if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                        break;
                    }
                    caseHttp.get($scope, false, $scope.id, $scope.parentSuite, function (data) {
                        $scope.testCase = data;
                        setProject(data);
                        $scope.refreshCodemirror = true;
                        $scope.testCase.suiteId = $scope.parentSuite;
                        originalDuration = parseInt(data.duration, 10);
                        try {
                            if (data.controls.length > 0) {
                                $scope.rights = 1;
                            }
                        } catch (e) {
                        }
                    }, $scope.generalHttpFactoryError);
                    break;
            }
            // FIXME this is loaded twice on document load
        };

        function setProject(data) {
            if (data.hasOwnProperty("ext") && data.ext.hasOwnProperty("projects") && data.ext.projects.length > 0) {
                $scope.project = data.ext.projects[0].name;
            } else {
                $scope.project = $scope.SYNERGY.product;
            }
        }

        /**
         * When creating a new case, set order to be (previous case+1)
         * @param {Suite} data
         * @returns {undefined}
         */
        function setLastCaseOrder(data) {
            try {
                $scope.testCase.order = (data.testCases[data.testCases.length - 1].order + 1) || 1;
            } catch (e) {
                $scope.testCase.order = 1;
            }
        }

        /**
         * Creates a new test case
         */
        $scope.create = function () {
            if (parseInt($scope.parentSuite, 10) < 1) {// parent suite HAS to be defined
                return;
            }
            if ($scope.myForm.$invalid || $scope.myForm2.$invalid || typeof $scope.testCase.steps === "undefined" || typeof $scope.testCase.steps.length < 0 || typeof $scope.testCase.result === "undefined" || typeof $scope.testCase.result.length < 0) {
                $scope.SYNERGY.modal.update("Missing required fields", "");
                $scope.SYNERGY.modal.show();
                return;
            }
            specificationCache.resetCurrentSpecification();
            var testCase = new SynergyModels.TestCase($scope.testCase.title, $scope.testCase.steps, $scope.testCase.result, $scope.testCase.duration, -1);
            testCase.suiteId = $scope.parentSuite;
            testCase.order = $scope.testCase.order;
            if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                return;
            }
            caseHttp.create($scope, testCase, function (data) {
                $scope.SYNERGY.modal.update("Test Case created", "");
                $scope.SYNERGY.modal.show();
                window.history.back();
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Based on $scope.currentAction, performs some action
         */
        $scope.performAction = function () {
            switch (currentAction) {
                case "deleteCase":
                    $("#deleteModal").modal("toggle");
                    if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1 || $scope.parentSuite < 1) {
                        return;
                    }
                    specificationCache.resetCurrentSpecification();
                    suiteHttp.removeCase($scope, $scope.parentSuite, $scope.id, function (data) {
                        $scope.SYNERGY.modal.update("Test Case removed from test suite", "");
                        $scope.SYNERGY.modal.show();
                        window.history.back();
                    }, $scope.generalHttpFactoryError);
                    break;
                case "deleteImage":
                    var idToBeRemoved = currentActionId;
                    specificationCache.resetCurrentSpecification();
                    $("#deleteImageModal").modal("toggle");
                    imageHttp.remove($scope, currentActionId, $scope.parentSuite, function (data) {
                        $scope.SYNERGY.logger.log("Done", "Image removed", "INFO", "alert-success");
                        for (var i = 0, max = $scope.testCase.images.length; i < max; i++) {
                            if ($scope.testCase.images[i].id === idToBeRemoved) {
                                $scope.testCase.images.splice(i, 1);
                                return;
                            }
                        }
                    }, $scope.generalHttpFactoryError);
                    break;
                default:
                    break;
            }
        };

        /**
         * Starts with action on current test case
         * Otherwise confirmation dialog is opened
         * @param {String} action action name
         */
        $scope.performCase = function (action) {
            // delete could be done on a same page
            if ($scope.parentSuite < 0) {
                return;
            }
            switch (action) {
                case "delete":
                    $("#deleteModalLabel").text("Delete test case?");
                    $("#deleteModalBody").html("<p>This will only remove reference to this test case in this suite. Continue?</p>");
                    $("#deleteModal").modal("toggle");
                    currentAction = "deleteCase";
                    break;
                default:
                    $location.path("case/" + $scope.testCase.id + "/suite/" + $scope.parentSuite + "/" + action);
                    break;
            }
        };

        /**
         * Starts with action on given test case's image
         * Otherwise confirmation dialog is opened
         * @param {String} action action name
         * @param {Number} id image ID
         */
        $scope.performImage = function (action, id) {
            // delete could be done on a same page
            if ($scope.parentSuite < 0) {
                return;
            }
            switch (action) {
                case "delete":
                    $("#deleteImageModalLabel").text("Delete image?");
                    $("#deleteImageModalBody").html("<p>This will delete image from this test case. Continue?</p>");
                    $("#deleteImageModal").modal("toggle");
                    currentAction = "deleteImage";
                    currentActionId = id;
                    break;
                default:
                    $location.path("image/" + id + "/" + action);
                    break;
            }

        };

        $scope.cancel = function () {
            window.history.back();
        };

        /**
         * Saves modified test case to server
         * @param {Number} mode if 0, this test case will be cloned and modifications will be applied only to this suite, if 1 all suites will be affected
         */
        $scope.save = function (mode) {
            if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                return;
            }
            if ($scope.myForm.$invalid || $scope.myForm2.$invalid) {
                $scope.SYNERGY.modal.update("Missing required fields", "");
                $scope.SYNERGY.modal.show();
                return;
            }
            specificationCache.resetCurrentSpecification();
            var t_case = new SynergyModels.TestCase($scope.testCase.title, $scope.testCase.steps, $scope.testCase.result, $scope.testCase.duration, $scope.testCase.id);
            t_case.suiteId = $scope.parentSuite;
            t_case.order = $scope.testCase.order;
            t_case.orginalDuration = originalDuration;
            caseHttp.edit($scope, mode, t_case, $scope.minorEdit ? true : false, function (data) {
                $scope.SYNERGY.modal.update("Test Case updated", "");
                $scope.SYNERGY.modal.show();
                window.history.back();
            }, $scope.generalHttpFactoryError);
        };
        $scope.showAddIssueModal = function () {
            $scope.issueToBeAdded = "";
            $("#addIssueModal").modal("toggle");
        };
        $scope.addIssue = function () {
            specificationCache.resetCurrentSpecification();
            issueHttp.create($scope, {testCaseId: $scope.testCase.id, id: $scope.issueToBeAdded}, function (data) {
                $scope.SYNERGY.logger.log("Issue added", "", "INFO", "alert-success");
                $scope.testCase.issues.push({"bugId": $scope.issueToBeAdded, "title": "", "resolution": "", "id": -1});
            }, $scope.generalHttpFactoryError);
        };

        $scope.removeIssue = function (id) {
            specificationCache.resetCurrentSpecification();
            issueHttp.remove($scope, {testCaseId: $scope.testCase.id, id: id}, function (data) {
                $scope.SYNERGY.logger.log("Issue removed", "", "INFO", "alert-success");
                for (var i = 0, max = $scope.testCase.issues.length; i < max; i++) {
                    if ($scope.testCase.issues[i].bugId === id) {
                        $scope.testCase.issues.splice(i, 1);
                        return;
                    }
                }
            }, $scope.generalHttpFactoryError);
        };

        $scope.showAddLabelModal = function () {
            $scope.labelToBeAdded = "";
            $("#addLabelModal").modal("toggle");
        };
        $scope.addLabel = function () {
            specificationCache.resetCurrentSpecification();
            labelHttp.create($scope, {"label": $scope.labelToBeAdded, "testCaseId": $scope.testCase.id, "suiteId": $scope.parentSuite}, function (data) {
                $scope.SYNERGY.logger.log("Label added", "", "INFO", "alert-success");
                $scope.testCase.keywords.push($scope.labelToBeAdded.toLowerCase());
            }, $scope.generalHttpFactoryError);
        };
        $scope.removeLabel = function (label) {
            specificationCache.resetCurrentSpecification();
            labelHttp.remove($scope, {"label": label, "testCaseId": $scope.testCase.id, "suiteId": $scope.parentSuite}, function (data) {
                $scope.SYNERGY.logger.log("Label removed", "", "INFO", "alert-success");
                $scope.testCase.keywords.splice($scope.testCase.keywords.indexOf(label), 1);
            }, $scope.generalHttpFactoryError);
        };
// INIT
        var self = $scope;
        $scope.init(function () {
            self.fetch(true);
        });

        // D&D images

        var fu = new SynergyHandlers.FileUploader([], "fakeID", $scope.SYNERGY.uploadFileLimit, $scope.SYNERGY.server.buildURL("image", {"id": $scope.id, "suiteId": $scope.parentSuite, "title": encodeURIComponent($scope.imageTitle)}), function (title, msg, level, style, fileName) {
            $scope.SYNERGY.logger.log(title, msg, level, style);
            $scope.fileName = fileName;
            specificationCache.resetCurrentSpecification();
            imageHttp.getImagesForCase($scope, $scope.testCase.id, $scope.parentSuite, function (data) {
                $scope.testCase.images = data;
            }, function (data) {
                $scope.SYNERGY.logger.log("Unable to refresh list of images", "", "INFO", "alert-error");
                $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
            });
        }, function (title, msg, level, style) {
            $scope.SYNERGY.logger.log("Action failed", msg, "INFO", "alert-error");
            $scope.SYNERGY.logger.log(title, msg, level, style);
            try {
                if (!$scope.$$phase) {
                    $scope.$apply();
                }
            } catch (e) {
            }
        });
        fu.initForImages("dropbox");

        $scope.uploadFile = function () {
            fu.uploadImage($scope.SYNERGY.server.buildURL("image", {"id": $scope.id, "suiteId": $scope.parentSuite, "title": encodeURIComponent($scope.imageTitle)}));
        };

    }
    /**
     * 
     * @param {UserFct} userHttp
     */
    function ProfileCtrl($scope, $routeParams, userHttp, $location, SynergyModels, SynergyHandlers) {//authService
        $scope.$emit("updateNavbar", {item: "nav_home"});
        $scope.user = {authorOf: [], membership: [], favorites: [], assignments: []};
        $scope.username = $routeParams.user || "";
        $scope.rights = 0;
        $scope.passwordChangeAllowed = !$scope.SYNERGY.useSSO;
        $scope.updatePassword = false;

        $scope.isLoggedIn = (typeof $scope.SYNERGY.session.session_id !== "undefined" && $scope.SYNERGY.session.session_id.length > 1) ? 1 : 0;
        /**
         * Loads data from server
         */
        $scope.fetch = function () {
            $scope.isLoggedIn = (typeof $scope.SYNERGY.session.session_id !== "undefined" && $scope.SYNERGY.session.session_id.length > 1) ? 1 : 0;
            var action = window.location + "";
            action = action.substring(action.lastIndexOf("/") + 1);
            if ($scope.username.length < 1) {
                if (typeof $scope.SYNERGY.session.session_id !== "undefined" || $scope.SYNERGY.session.session_id.length > 1) {
                    $scope.username = $scope.SYNERGY.session.username;
                }
            }
            if ($scope.username.length < 1) {
                return;
            }
            userHttp.get($scope, $scope.username, function (data) {

                data.assignments.forEach(function (trun) {
                    if (trun.projectName === null || trun.projectName === "") {
                        trun.projectName = $scope.SYNERGY.product;
                    }
                });
                setProject(data.authorOf);
                setProject(data.ownerOf);
                setProject(data.favorites);
                $scope.user = data;
                if ($scope.username === $scope.SYNERGY.session.username) {
                    $scope.rights = 1;
                }

                $scope.$emit("updateBreadcrumbs", {link: "user/" + $scope.username, title: $scope.username});
            }, $scope.generalHttpFactoryError);
        };

        function setProject(specifications) {
            for (var i = 0, max = specifications.length; i < max; i++) {
                specifications[i]._project = specifications[i].ext.hasOwnProperty("projects") && specifications[i].ext.projects.length > 0 ? specifications[i].ext.projects[0].name : $scope.SYNERGY.product;
            }
        }

        $scope.editName = function () {
            if ($scope.rights === 1) {
                var invalidPassword = typeof $scope.user.password === "undefined" || $scope.user.password === null || $scope.user.password.length < 1;
                if ($scope.profileForm.$invalid || ($scope.updatePassword && invalidPassword)) {
                    $scope.SYNERGY.modal.update("Missing required fields", "");
                    $scope.SYNERGY.modal.show();
                    return;
                }

                var _u = new SynergyModels.User($scope.user.firstName, $scope.user.lastName, $scope.user.username, "", -1);
                _u.emailNotifications = $scope.user.emailNotifications;
                _u.email = $scope.user.email;
                if ($scope.updatePassword) {
                    _u.password = $scope.user.password;
                }
                userHttp.edit($scope, _u, function (data) {
                    $scope.SYNERGY.logger.log("Done", "updated", "INFO", "alert-success");
                }, $scope.generalHttpFactoryError);
            }
        };

        /**
         * Removes specification from user's list of favorites
         * @param {Number} id specification ID
         */
        $scope.toggleFavorite = function (id) {
            var spec = {"id": id, "isFavorite": 0};
            userHttp.toggleFavorite($scope, spec, function (data) {
                $scope.SYNERGY.logger.log("Done", "Specification removed from favorites", "INFO", "alert-success");
                for (var k = 0, max = $scope.user.favorites.length; k < max; k += 1) {
                    if (parseInt($scope.user.favorites[k].id, 10) === parseInt(id, 10)) {
                        $scope.user.favorites.splice(k, 1);
                        return;
                    }
                }
            }, $scope.generalHttpFactoryError);
        };
        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });

        $scope.uploadFile = function () {
            new SynergyHandlers.FileUploader([], "dropbox", $scope.SYNERGY.uploadFileLimit, $scope.SYNERGY.server.buildURL("profile_img", {"id": $scope.user.id}), function (title, msg, level, style, fileName, newSrc) {
                $scope.SYNERGY.logger.log(title, msg, level, style);
                $scope.user.profileImg = newSrc;
                try {
                    if (!$scope.$$phase) {
                        $scope.$apply();
                    }
                } catch (e) {
                }
            }, function (title, msg, level, style) {
                $scope.SYNERGY.logger.log("Action failed", msg, "INFO", "alert-error");
                try {
                    if (!$scope.$$phase) {
                        $scope.$apply();
                    }
                } catch (e) {
                }
            }).uploadFileFromFileChooser("fileToUpload");
        };

        $scope.resetFile = function () {
            userHttp.resetProfileImg($scope, $scope.user.id, function (data) {
                $scope.user.profileImg = data;
            }, $scope.generalHttpFactoryError);
        };

    }
    /**
     * @param {LabelFct} labelHttp
     * @returns {undefined} 
     */
    function LabelFilterCtrl($scope, $routeParams, labelHttp) {//authService
        $scope.$emit("updateNavbar", {item: "nav_home"});
        $scope.result = {};
        $scope.label = $routeParams.label || "";
        $scope.page = $routeParams.page || 1;
        $scope.next = 0;
        $scope.prev = 0;
        $scope.nextPage = 1;
        $scope.prevPage = 1;
        /**
         * Loads data from server
         */
        $scope.fetch = function () {
            if ($scope.label.length > 0) {
                labelHttp.findCases($scope, $scope.label, $scope.page, function (data) {
                    $scope.result = data;
                    $scope.$emit("updateBreadcrumbs", {link: "label/" + $scope.label + "/page/1", title: $scope.label});
                    $scope.next = (data.nextUrl.length > 1) ? 1 : 0;
                    $scope.prev = (data.prevUrl.length > 1) ? 1 : 0;
                    $scope.nextPage = parseInt($scope.page, 10) + 1;
                    $scope.prevPage = parseInt($scope.page, 10) - 1;
                }, $scope.generalHttpFactoryError);
            }
        };
        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
    }
    /**
     * @param {UsersFct} usersHttp
     * @param {TribeFct} tribeHttp description
     * @returns {undefined} 
     */
    function TribeCtrl($scope, $location, $routeParams, $timeout, usersHttp, tribeHttp, sanitizerHttp, specificationsHttp, SynergyUtils, SynergyModels) {//authService
        $scope.$emit("updateNavbar", {item: "nav_home"});
        $scope.tribe = {};
        $scope.id = $routeParams.id || -1;
        $scope.rights = 0;
        $scope.refreshCodemirror = false;
        var currentAction = "";
        var currentId = "";
        $scope.suggestions = [];
        $scope.users_suggestions = [];
        $scope.userToBeAdded = "pepa";
        $scope.specifications = [];
        $scope.newSpecification = -1;
        $scope.users = [];
        $scope.loadingUsers = false;
        $scope.toggleMembers = false;
        /**
         * Loads data from server
         */
        $scope.fetch = function () {
            var action = window.location + "";
            action = action.substring(action.lastIndexOf("/") + 1);
            if ($scope.id > 0) {
                tribeHttp.get($scope, false, $scope.id, function (data) {
                    setProject(data.ext);
                    $scope.tribe = data;
                    $scope.refreshCodemirror = true;
                    $scope.$emit("updateBreadcrumbs", {link: "tribe/" + $scope.id, title: data.name});
                    if ($scope.tribe.controls.length > 0) {
                        $scope.rights = 1;
                    }
                    if (action === "edit") {
                        loadSpecifications();
                        loadUsers();
                    }
                }, $scope.generalHttpFactoryError);
            }
        };

        function setProject(ext) {
            if (!ext.hasOwnProperty("specifications")) {
                return;
            }
            for (var i = 0, max = ext.specifications.length; i < max; i++) {
                ext.specifications[i]._project = ext.specifications[i].hasOwnProperty("projects") && ext.specifications[i].projects.length > 0 ? ext.specifications[i].projects[0].name : $scope.SYNERGY.product;
            }
        }

        $scope.loadPreview = function () {
            var _t = "<h1>" + ($scope.tribe.name || "") + "</h1><h3>Description</h3><div class='well'>" + ($scope.tribe.description || "") + "</div>";
            sanitizerHttp.getSanitizedInput($scope, _t, function (data) {
                $scope.preview = data;
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Loads all specifications (in edit page so tribe leader can add specification to tribe)
         */
        function loadSpecifications() {
            specificationsHttp.get($scope, "allRaw", function (data) {
                var d = [];
                var p;
                for (var i = 0, max = data.length; i < max; i += 1) {
                    p = data[i].ext.hasOwnProperty("projects") && data[i].ext.projects.length > 0 ? data[i].ext.projects[0].name : $scope.SYNERGY.product;
                    d[i] = {
                        title: data[i].title,
                        version: data[i].version,
                        value: data[i].title + " (" + p + " " + data[i].version + ")",
                        id: data[i].id
                    };
                }
                $scope.specifications = d;
            }, $scope.generalHttpFactoryError);
        }

        $scope.addSpecification = function () {
            var _index = parseInt($scope.newSpecification);
            for (var i = 0, max = $scope.tribe.ext.specifications.length; i < max; i++) {
                if (parseInt($scope.tribe.ext.specifications[i].id) === _index) {
                    $scope.SYNERGY.logger.log("Oops", "Specification already added to tribe", "INFO", "alert-info");
                    return;
                }
            }

            tribeHttp.addSpecification($scope, $scope.id, $scope.newSpecification, function (data, specificationId) {
                $scope.SYNERGY.logger.log("Done", "Specification added to tribe", "INFO", "alert-success");
                var matchingIndex = findSpecification(parseInt(specificationId, 10));
                if (matchingIndex > 0) {
                    $scope.tribe.ext.specifications.push($scope.specifications[matchingIndex]);
                }
            }, $scope.generalHttpFactoryError);
        };

        $scope.removeSpecification = function (specificationId) {
            tribeHttp.removeSpecification($scope, $scope.id, specificationId, function (data, specificationId) {
                $scope.SYNERGY.logger.log("Done", "Specification removed from tribe", "INFO", "alert-success");
                for (var i = 0, max = $scope.tribe.ext.specifications.length; i < max; i++) {
                    if (parseInt($scope.tribe.ext.specifications[i].id, 10) === specificationId) {
                        $scope.tribe.ext.specifications.splice(i, 1);
                        return;
                    }
                }
            }, $scope.generalHttpFactoryError);
        };

        function findSpecification(id) {
            for (var i = 0, max = $scope.specifications.length; i < max; i++) {
                if ($scope.specifications[i].id === id) {
                    return i;
                }
            }
            return -1;
        }

        function loadUsers() {
            if ($scope.users.length > 0) { // already loaded
                return;
            }
            $scope.loadingUsers = true;
            usersHttp.getAll($scope, function (data) {
                if (SynergyUtils.definedNotNull(data) && SynergyUtils.definedNotNull(data.users)) {
                    for (var i = 0, max = data.users.length; i < max; i++) {
                        data.users[i].displayName = data.users[i].firstName + " " + data.users[i].lastName + " (" + data.users[i].username + ")";
                    }
                }
                $scope.loadingUsers = false;
                $scope.users = data.users;
            }, function (data) {
                $scope.SYNERGY.logger.log("Action failed", "Unable to load list of users", "INFO", "alert-error");
                $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
            });
        }
        /**
         * Shows confirmation dialog to remove user from tribe and saves selected username as currentId
         * @param {String} username
         */
        $scope.removeFromTribe = function (username) {
            $("#deleteModalLabel").text("Remove user from tribe?");
            $("#deleteModalBody").html("<p>Do you really want to revoke user's membership?</p>");
            $("#deleteModal").modal("toggle");
            currentAction = "deleteUser";
            currentId = username;
        };

        /**
         * Removes users from tribe (calls server)
         * @param {String} username username of users to be removed from tribe
         */
        $scope.performRemoveFromTribe = function (username) {
            $("#deleteModal").modal("toggle");
            var memberToBeRemoved = username;
            tribeHttp.revokeMembership($scope, username, $scope.id, function (data) {
                $scope.SYNERGY.logger.log("Done", "User removed from tribe", "INFO", "alert-success");
                for (var i = 0, max = $scope.tribe.members.length; i < max; i++) {
                    if ($scope.tribe.members[i].username === memberToBeRemoved) {
                        $scope.tribe.members.splice(i, 1);
                        return;
                    }
                }
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Bases od $scope.currentAction it calls some method
         */
        $scope.performAction = function () {
            switch (currentAction) {
                case "deleteUser":
                    $scope.performRemoveFromTribe(currentId);
                    break;
                default:
                    break;
            }
        };

        /**
         * Performrs action with current tribe
         * @param {String} action unless it is "delete", it reroutes to tribe/id/action otherwise shows confirmation dialog
         */
        $scope.performTribeAction = function (action) {
            if (action !== "delete") {
                $location.path("tribe/" + $scope.id + "/" + action);
            }
        };

        $scope.cancel = function () {
            window.history.back();
        };

        /**
         * Saves modifications made to tribe
         * @returns {unresolved}
         */
        $scope.save = function () {
            if ($scope.myForm.$invalid || $scope.myForm2.$invalid || typeof $scope.tribe.description === "undefined" || $scope.tribe.description.length < 0) {
                $scope.SYNERGY.modal.update("Missing required fields", "");
                $scope.SYNERGY.modal.show();
                return;
            }
            var tribe = new SynergyModels.Tribe($scope.tribe.name, $scope.tribe.description, $scope.tribe.leaderUsername, $scope.tribe.id);
            tribeHttp.edit($scope, tribe, function (data) {
                $scope.SYNERGY.modal.update("Tribe updated", "");
                $scope.SYNERGY.modal.show();
                $location.path("tribe/" + $scope.tribe.id);
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Adds user of given username to tribe
         */
        $scope.addUser = function () {
            tribeHttp.newMembership($scope, {username: $scope.userToBeAdded}, $scope.tribe.id, function (data) {
                $scope.SYNERGY.logger.log("Done", "User added to tribe", "INFO", "alert-success");
                $scope.fetch();
            }, $scope.generalHttpFactoryError);
        };

        $scope.showAddUserModal = function () {
            $scope.toggleMembers = !$scope.toggleMembers;
            loadUsers();
        };
        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
    }
    /**
     * @param {RunsFct} runsHttp
     */
    function RunsCtrl($scope, $routeParams, runsHttp) {
        $scope.runs = [];
        $scope.page = $routeParams.page || 1;
        $scope.next = 0;
        $scope.prev = 0;
        $scope.nextPage = 1;
        $scope.prevPage = 1;

        /**
         * Retrieves list of test runs
         */
        $scope.fetch = function () {
            $scope.$emit("updateBreadcrumbs", {link: "runs", title: "Test Runs"});
            $scope.$emit("updateNavbar", {item: "nav_runs"});
            runsHttp.get($scope, $scope.page, function (data) {

                data.testRuns.forEach(function (trun) {
                    if (trun.projectName === null || trun.projectName === "") {
                        trun.projectName = $scope.SYNERGY.product;
                    }
                });

                $scope.runs = data;
                $scope.next = (data.nextUrl.length > 1) ? 1 : 0;
                $scope.prev = (data.prevUrl.length > 1) ? 1 : 0;
                $scope.nextPage = parseInt($scope.page, 10) + 1;
                $scope.prevPage = parseInt($scope.page, 10) - 1;
            }, $scope.generalHttpFactoryError, true);
        };
        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
    }
    /**
     * @param {AssignmentFct} assignmentHttp
     * @returns {undefined} */
    function AssignmentCtrl($scope, $routeParams, assignmentHttp) {//authService
        $scope.$emit("updateNavbar", {item: "nav_home"});
        $scope.assignment = {};
        $scope.currentCase = {};
        $scope.currentCaseId = -1;
        $scope.currentSuiteId = -1;
        $scope.id = $routeParams.id || -1;
        $scope.mode = $routeParams.mode || 2;
        $scope.project = {};
        $scope.timeLeft = 0;
        $scope.suiteIndex = 0;
        $scope.caseIndex = 0;
        $scope.casesFinished = 0;
        $scope.newIssue = "";
        $scope.attachmentBase = $scope.SYNERGY.server.buildURL("attachment", {});
        var timeFinished = 0;
        var _time = 0;
        $scope.failedAttempt = false;
        var started = 0;
        var cachedData = {};
        $scope.cacheDate = {};
        $scope.comments = [];
        $scope.allCases = [];
        $scope.suiteSetupDisplayed = false;
        $scope.toggleAction = "display";
        $scope.caseToPrint = {};
        $scope.errorMsg = "";
        $scope.somethingCompleted = false;
        $scope.pauseButtonTitle = "No test case has been completed yet or nothing has changed since resuming testing";
        /**
         * Loads data from server
         */
        $scope.fetch = function () {
            if ($scope.id > 0) {

                if (cachedDataExists()) {
                    $scope.cacheDate = cachedData.date;
                    $("#attemptModal").modal("toggle");
                    return;
                }

                assignmentHttp.getCommentTypes($scope, function (data) {
                    data.push({"name": "No comment", "id": -1});
                    $scope.comments = data;
                }, $scope.generalHttpFactoryError);

                switch ($scope.mode) {
                    case "1":
                        assignmentHttp.start($scope, $scope.id, function (data) {
                            $scope.assignment = data;
                            setProject(data.specificationData);
                            getTimeLeft();
                            collectCases();
                            $scope.casesFinished = parseInt(100 * (parseInt(data.completed, 10) / parseInt(data.total, 10)), 10) || 0;
                            parseData(0, 0, 0);
                            $scope.caseToPrint = $scope.allCases.filter(function (e) {
                                return e.caseId === $scope.currentCase.caseId && e.suiteId === $scope.currentCase.suiteId;
                            })[0];
                        }, $scope.generalHttpFactoryError);
                        break;
                    case "2":
                        assignmentHttp.restart($scope, $scope.id, function (data) {
                            $scope.assignment = data;
                            setProject(data.specificationData);
                            $scope.timeLeft = parseInt($scope.assignment.specificationData.estimation, 10);
                            collectCases();
                            $scope.casesFinished = parseInt(100 * (parseInt(data.completed, 10) / parseInt(data.total, 10)), 10) || 0;
                            parseData(0, 0, 0);
                            $scope.caseToPrint = $scope.allCases.filter(function (e) {
                                return e.caseId === $scope.currentCase.caseId && e.suiteId === $scope.currentCase.suiteId;
                            })[0];
                        }, $scope.generalHttpFactoryError);
                        break;
                    default:
                        break;
                }

            }
        };
        function setProject(data) {
            if (data.hasOwnProperty("ext") && data.ext.hasOwnProperty("projects") && data.ext.projects.length > 0) {
                $scope.project = data.ext.projects[0];
            } else {
                $scope.project = {"name": $scope.SYNERGY.product, id: -2};
            }
        }
        /**
         * Collects all cases to show them in navigation combo box together with their potential progress
         * @returns {undefined}
         */
        function collectCases() {
            var cases = [];
            for (var i = 0, max = $scope.assignment.specificationData.testSuites.length; i < max; i += 1) {
                for (var j = 0, max2 = $scope.assignment.specificationData.testSuites[i].testCases.length; j < max2; j += 1) {
                    var p = getProgressForCase($scope.assignment.specificationData.testSuites[i].testCases[j].id, $scope.assignment.specificationData.testSuites[i].id);
                    cases.push({
                        "name": $scope.assignment.specificationData.testSuites[i].testCases[j].title + " (" + $scope.assignment.specificationData.testSuites[i].title + ")" + ((parseInt(p.finished, 10) === 1) ? " - [" + p.result + "]" : ""),
                        "caseId": $scope.assignment.specificationData.testSuites[i].testCases[j].id,
                        "suiteId": $scope.assignment.specificationData.testSuites[i].id,
                        "progress": p,
                        "result": p.result
                    });
                }
            }
            $scope.allCases = cases;
        }

        /**
         * Sets common properties and prints case selected in combo box
         */
        $scope.traverseCase = function () {
            var oldSuiteId = $scope.currentSuiteId;
            $scope.currentCaseId = parseInt($scope.caseToPrint.caseId, 10);
            $scope.currentSuiteId = parseInt($scope.caseToPrint.suiteId, 10);

            var _s;
            for (var i = 0, max = $scope.assignment.progress.specification.testSuites.length; i < max; i += 1) {
                _s = $scope.assignment.progress.specification.testSuites[i];
                if (parseInt(_s.id, 10) === parseInt($scope.caseToPrint.suiteId, 10)) {
                    for (var j = 0, max2 = _s.testCases.length; j < max2; j += 1) {
                        if (parseInt(_s.testCases[j].id, 10) === parseInt($scope.caseToPrint.caseId, 10)) {
                            $scope.suiteIndex = i;
                            $scope.caseIndex = j;
                        }
                    }
                }
            }

            if (oldSuiteId !== $scope.currentSuiteId) {
                $scope.suiteSetupDisplayed = true;
                $scope.toggleAction = "hide";
            }

            printCase($scope.currentCaseId, $scope.currentSuiteId);
        };

        $scope.toggleSetup = function () {
            if ($scope.suiteSetupDisplayed) {
                $scope.toggleAction = "display";
            } else {
                $scope.toggleAction = "hide";
            }
            $scope.suiteSetupDisplayed = !$scope.suiteSetupDisplayed;
        };

        /**
         * Checks cache for possibly not submitted data
         * @returns {Boolean} true if there are cached data
         */
        function cachedDataExists() {
            cachedData = $scope.SYNERGY.cache.get("assignment_progress_" + $scope.id);
            return cachedData && cachedData.date && cachedData.progress ? true : false;
        }

        $scope.sendCached = function (send) {
            if (send) {
                $("#attemptModal").modal("toggle");
                $scope.assignment.progress = cachedData.progress;
                $scope.sendResults();
            } else {
                $scope.SYNERGY.cache.clear("assignment_progress_" + $scope.id);
                $scope.fetch();
            }
        };

        /**
         * Finds first unfinished test case. There are 2 approaches: iterate over progress and find matching test case or iterate over specification
         * data and find matching progress. The better is 2nd one because if specification has some new cases thar are not in this progress, it
         * can be dynamically adjusted.
         * @param {Number} caseStartIndex
         * @param {Number} suiteStartIndex
         * @param {Number} timeOffset
         */
        function parseData(caseStartIndex, suiteStartIndex, timeOffset) {
            var _s;
            // find 1st not yet tested test case
            for (var i = suiteStartIndex, max = $scope.assignment.progress.specification.testSuites.length; i < max; i += 1) {
                _s = $scope.assignment.progress.specification.testSuites[i];
                for (var j = caseStartIndex, max2 = _s.testCases.length; j < max2; j += 1) {
                    if (parseInt(_s.testCases[j].finished, 10) === 0) { // find first not finished case
                        $scope.currentCaseId = parseInt(_s.testCases[j].id, 10);
                        $scope.currentSuiteId = parseInt(_s.id, 10);
                        printCase($scope.currentCaseId, $scope.currentSuiteId);
                        $scope.suiteIndex = i;
                        $scope.caseIndex = j;
                        if (i !== suiteStartIndex || (caseStartIndex === 0 && suiteStartIndex === 0)) {
                            $scope.suiteSetupDisplayed = true;
                            $scope.toggleAction = "hide";
                        }
                        return;
                    }
                }
                caseStartIndex = 0; // to start from beginning in next suite
            }
            $scope.timeLeft = 0;
            $scope.sendResults(); // this happens if all cases are finished (otherwise return in the for statement above is used this code not executed)
        }

        function getTimeLeft() {
            var _s;
            for (var i = 0, max = $scope.assignment.progress.specification.testSuites.length; i < max; i += 1) {
                _s = $scope.assignment.progress.specification.testSuites[i];
                for (var j = 0, max2 = _s.testCases.length; j < max2; j += 1) {
                    if (parseInt(_s.testCases[j].finished, 10) !== 0) {
                        if (_s.testCases[j].hasOwnProperty("originalDuration")) {
                            timeFinished += parseInt(Math.round(_s.testCases[j].originalDuration), 10);
                        } else {
                            timeFinished += parseInt(Math.round(_s.testCases[j].duration), 10);
                        }
                    }
                }

            }
            $scope.timeLeft = parseInt($scope.assignment.specificationData.estimation, 10) - timeFinished;
        }

        /**
         * Sends results of testing to server
         */
        $scope.sendResults = function () {
            if ($scope.failedAttempt) {
                $("#deleteModal").modal("toggle");
            }
            $scope.showWaitDialog();
            assignmentHttp.submitResults($scope, $scope.id, $scope.assignment.progress, function (data) {
                $scope.SYNERGY.modal.update("Results submitted", "Thank you for testing");
                $scope.SYNERGY.modal.show();
                $scope.SYNERGY.cache.clear("assignment_progress_" + $scope.id);
                $scope.assignment = {};
                $scope.currentCase = {};
                $scope.currentCaseId = -1;
                $scope.currentSuiteId = -1;
                $scope.id = $routeParams.id || -1;
                $scope.mode = $routeParams.mode || 2;
                $scope.timeLeft = 0;
                $scope.suiteIndex = 0;
                $scope.caseIndex = 0;
                $scope.casesFinished = 0;
                $scope.newIssue = "";
                $scope.failedAttempt = false;
                window.history.back();
            }, function (data) {
                $scope.errorMsg = data || "";
                $scope.SYNERGY.modal.show();// hide wait dialog
                if (!$scope.failedAttempt) {
                    $scope.failedAttempt = true;
                    $("#deleteModal").modal("toggle");
                }
                $scope.SYNERGY.logger.log("Action failed", data, "INFO", "alert-error");
                $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
            });
        };

        /**
         * Procceeds to the next case in testing. Based on result parameter it marks test case as passed, failed or skipped
         * and if $scope.trackCaseDuration, it sets new duration of the case (not that it sends time to server but server must be
         *  configured to track case duration as well)
         * @param {type} result
         * @returns {unresolved}
         */
        $scope.next = function (result) {

            if (parseInt($scope.assignment.completed, 10) === parseInt($scope.assignment.total, 10)) {
                $scope.SYNERGY.modal.update("Testing finished", "");
                $scope.SYNERGY.modal.show();
                return;
            }
            var alreadyTested = (parseInt($scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].finished, 10) === 1) ? true : false;
            switch (result) {
                case "passed":
                    if (!alreadyTested) {
                        $scope.assignment.completed++;
                        $scope.casesFinished = parseInt(100 * (parseInt($scope.assignment.completed, 10) / parseInt($scope.assignment.total, 10)), 10);

                        if ($scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].hasOwnProperty("originalDuration")) {
                            timeFinished += parseInt(Math.round($scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].originalDuration), 10);
                        } else {
                            timeFinished += parseInt(Math.round($scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].duration), 10);
                        }
                        $scope.timeLeft = parseInt($scope.assignment.specificationData.estimation, 10) - timeFinished;

                    }

                    var timeTaken = (new Date().getTime()) - started;
                    $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].finished = 1;
                    $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].result = result;
                    $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].comment = $scope.currentCase.comment;
                    $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].commentFreeText = ($scope.currentCase.comment !== -1) ? $scope.currentCase.commentFreeText.substr(0, 100) : "";

                    if (!$scope.SYNERGY.trackCaseDuration) {
                        timeTaken = $scope.currentCase.duration * 60000;
                    }

                    if ($scope.newIssue.length > 0) {
                        $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].issue = $scope.newIssue.split(" ");
                    } else {
                        $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].issue = "";
                    }
                    $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].duration = timeTaken;
                    $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].originalDuration = $scope.currentCase.duration;
                    parseData($scope.caseIndex + 1, $scope.suiteIndex, $scope.currentCase.duration);
                    break;
                case "failed":
                    if ($scope.newIssue.length > 0) {
                        if (!alreadyTested) {
                            $scope.assignment.completed++;
                            $scope.casesFinished = parseInt(100 * (parseInt($scope.assignment.completed, 10) / parseInt($scope.assignment.total, 10)), 10);

                            if ($scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].hasOwnProperty("originalDuration")) {
                                timeFinished += parseInt(Math.round($scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].originalDuration), 10);
                            } else {
                                timeFinished += parseInt(Math.round($scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].duration), 10);
                            }
                            $scope.timeLeft = parseInt($scope.assignment.specificationData.estimation, 10) - timeFinished;

                        }
                        var timeTaken = (new Date().getTime()) - started;
                        if (!$scope.SYNERGY.trackCaseDuration) {
                            timeTaken = $scope.currentCase.duration * 60000;
                        }



                        $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].issue = $scope.newIssue.split(" ");
                        $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].finished = 1;
                        $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].result = result;
                        $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].duration = timeTaken;
                        $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].originalDuration = $scope.currentCase.duration;
                        $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].comment = $scope.currentCase.comment;
                        $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].commentFreeText = ($scope.currentCase.comment !== -1) ? $scope.currentCase.commentFreeText.substr(0, 100) : "";
                        parseData($scope.caseIndex + 1, $scope.suiteIndex, $scope.currentCase.duration);

                    } else {
                        $scope.SYNERGY.modal.update("Missing issue number", "");
                        $scope.SYNERGY.modal.show();
                        return;
                    }
                    break;
                case "skipped":
                    if ($scope.currentCase.comment === -1) {
                        $scope.SYNERGY.modal.update("Missing reason for skipping", "Please select comment using the combo box below Skip button");
                        $scope.SYNERGY.modal.show();
                        return;
                    }



                    if (!alreadyTested) {
                        $scope.assignment.completed++;
                        $scope.casesFinished = parseInt(100 * (parseInt($scope.assignment.completed, 10) / parseInt($scope.assignment.total, 10)), 10);
                        if ($scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].hasOwnProperty("originalDuration")) {
                            timeFinished += parseInt(Math.round($scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].originalDuration), 10);
                        } else {
                            timeFinished += parseInt(Math.round($scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].duration), 10);
                        }
                        $scope.timeLeft = parseInt($scope.assignment.specificationData.estimation, 10) - timeFinished;
                    }
                    $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].issue = "";
                    $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].finished = 1;
                    $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].result = result;
                    $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].originalDuration = $scope.currentCase.duration;
                    $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].comment = $scope.currentCase.comment;
                    $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].commentFreeText = ($scope.currentCase.comment !== -1) ? $scope.currentCase.commentFreeText.substr(0, 100) : "";
                    parseData($scope.caseIndex + 1, $scope.suiteIndex, $scope.currentCase.duration);
                    break;
                default:
                    break;
            }
            $scope.somethingCompleted = true;
            $scope.pauseButtonTitle = "Save current progress and continue later";
            $scope.SYNERGY.cache.put("assignment_progress_" + $scope.id, {"date": new Date().toString(), "progress": $scope.assignment.progress});
            collectCases();
            $scope.caseToPrint = $scope.allCases.filter(function (e) {
                return e.caseId === $scope.currentCase.caseId && e.suiteId === $scope.currentCase.suiteId;
            })[0];
            $scope.SYNERGY.util.scrollTo("caseTitle");
        };

        /**
         * It goes through specification and sets case in suite with suiteId with caseId to current so it is displayed to user.
         * If caseId is -1, first case in first suite is displayed
         * @param {Number} caseId
         * @param {Number} suiteId
         */
        function printCase(caseId, suiteId) {
            $scope.newIssue = "";
            if (caseId === -1) {
                for (var i = 0, max = $scope.assignment.specificationData.testSuites.length; i < max; i += 1) {
                    for (var j = 0, max2 = $scope.assignment.specificationData.testSuites[i].testCases.length; j < max2; j += 1) {

                        $scope.suiteIndex = i;
                        $scope.caseIndex = j;
                        started = new Date().getTime();
                        $scope.currentCase = {
                            "title": $scope.assignment.specificationData.testSuites[i].testCases[j].title,
                            "caseId": $scope.assignment.specificationData.testSuites[i].testCases[j].id,
                            "suiteId": $scope.assignment.specificationData.testSuites[i].id,
                            "images": $scope.assignment.specificationData.testSuites[i].testCases[j].images,
                            "duration": parseInt($scope.assignment.specificationData.testSuites[i].testCases[j].duration, 10),
                            "steps": $scope.assignment.specificationData.testSuites[i].testCases[j].steps,
                            "suiteTitle": $scope.assignment.specificationData.testSuites[i].title,
                            "result": $scope.assignment.specificationData.testSuites[i].testCases[j].result,
                            "issues": $scope.assignment.specificationData.testSuites[i].testCases[j].issues,
                            "suiteSetup": $scope.assignment.specificationData.testSuites[i].desc,
                            "product": $scope.assignment.specificationData.testSuites[i].product,
                            "comment": -1,
                            "commentFreeText": "",
                            "progress": {"finished": 0, "id": caseId, "result": "", "duration": 0, "issue": [], "comment": -1, "commentFreeText": ""},
                            "component": $scope.assignment.specificationData.testSuites[i].component
                        };
                        return;
                    }
                }

                return;
            }
            for (var i = 0, max = $scope.assignment.specificationData.testSuites.length; i < max; i += 1) {
                if (suiteId === parseInt($scope.assignment.specificationData.testSuites[i].id, 10)) {
                    for (var j = 0, max2 = $scope.assignment.specificationData.testSuites[i].testCases.length; j < max2; j += 1) {
                        if (caseId === parseInt($scope.assignment.specificationData.testSuites[i].testCases[j].id, 10)) {
                            $scope.currentCase = {
                                "title": $scope.assignment.specificationData.testSuites[i].testCases[j].title,
                                "caseId": $scope.assignment.specificationData.testSuites[i].testCases[j].id,
                                "suiteId": $scope.assignment.specificationData.testSuites[i].id,
                                "images": $scope.assignment.specificationData.testSuites[i].testCases[j].images,
                                "duration": parseInt($scope.assignment.specificationData.testSuites[i].testCases[j].duration, 10),
                                "steps": $scope.assignment.specificationData.testSuites[i].testCases[j].steps,
                                "suiteTitle": $scope.assignment.specificationData.testSuites[i].title,
                                "result": $scope.assignment.specificationData.testSuites[i].testCases[j].result,
                                "issues": $scope.assignment.specificationData.testSuites[i].testCases[j].issues,
                                "suiteSetup": $scope.assignment.specificationData.testSuites[i].desc,
                                "product": $scope.assignment.specificationData.testSuites[i].product,
                                "comment": -1,
                                "commentFreeText": "",
                                "progress": getProgressForCase(caseId, suiteId),
                                "component": $scope.assignment.specificationData.testSuites[i].component
                            };
                            initValuesFromProgress();
                            started = new Date().getTime();
                            return;
                        }
                    }
                }
            }
        }


        function initValuesFromProgress() {
            // issues
            if ($scope.currentCase.progress.issue && $scope.currentCase.progress.issue.length > 0) {
                $scope.newIssue = $scope.currentCase.progress.issue.join(" ");
            }
            // comment
            if ($scope.currentCase.progress.comment && $scope.currentCase.progress.comment > 0) {
                $scope.currentCase.comment = $scope.currentCase.progress.comment;
                $scope.currentCase.commentFreeText = ($scope.currentCase.progress.hasOwnProperty("commentFreeText")) ? $scope.currentCase.progress.commentFreeText : "";
            }
        }

        function getProgressForCase(caseId, suiteId) {
            for (var i = 0, max = $scope.assignment.progress.specification.testSuites.length; i < max; i++) {
                if (parseInt($scope.assignment.progress.specification.testSuites[i].id, 10) === parseInt(suiteId, 10)) {
                    for (var j = 0, max2 = $scope.assignment.progress.specification.testSuites[i].testCases.length; j < max2; j++) {
                        if (parseInt($scope.assignment.progress.specification.testSuites[i].testCases[j].id, 10) === parseInt(caseId, 10)) {
                            return $scope.assignment.progress.specification.testSuites[i].testCases[j];
                        }
                    }
                }
            }
            return {"finished": 0, "id": caseId, "result": "", "duration": 0, "issue": [], "comment": -1};
        }

        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
    }
// ADMINISTRATION
    function AdminHomeCtrl($scope) {
        if (!$scope.SYNERGY.session.hasAdminRights()) {
            return;
        }

    }
    /**
     * 
     * @param {VersionsFct} versionsHttp
     * @param {VersionFct} versionHttp
     */
    function AdminVersionCtrl($scope, versionsHttp, versionHttp, SynergyModels) {

        $scope.versions = [];
        $scope.versionAffectedId = 0; // when editing version, modal dialog is opened, this is to show version name when typing new name
        $scope.versionAffected = "";
        $scope.newname = "";

        $scope.fetch = function () {
            if (!$scope.SYNERGY.session.hasAdminRights()) {
                return;
            }
            versionsHttp.getAll($scope, function (data) {
                $scope.versions = data;
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Based on action parameter, it displays some window and sets versionId and version as to be affected by future modifications
         * @param {String} action
         * @param {Number} versionId
         * @param {String} version
         */
        $scope.perform = function (action, versionId, version, isObsolete) {
            switch (action) {
                case "edit":
                    $scope.newname = version;
                    $scope.isObsolete = (parseInt(isObsolete, 10) === 1) ? true : false;
                    $scope.versionAffectedId = versionId;
                    $scope.versionAffected = version;
                    $("#editVersionModal").modal("toggle");
                    break;
                case "create":
                    $scope.newname = "";
                    $("#createVersionModal").modal("toggle");
                    break;
                case "delete":
                    $scope.versionAffectedId = versionId;
                    $scope.versionAffected = version;
                    $("#deleteModal").modal("toggle");
                    break;
                default :
                    break;
            }
        };

        /**
         * Renames version
         */
        $scope.rename = function () {

            if ($scope.myForm.$invalid) {
                $scope.SYNERGY.modal.update("Missing required fields", "");
                $scope.SYNERGY.modal.show();
                return;
            }

            versionHttp.edit($scope, new SynergyModels.Version($scope.newname, $scope.versionAffectedId, $scope.isObsolete), function () {
                $scope.SYNERGY.logger.log("Done", "Version renamed", "INFO", "alert-success");
                $scope.fetch();
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Deletes version
         */
        $scope.remove = function () {
            $("#deleteModal").modal("toggle");
            versionHttp.remove($scope, $scope.versionAffectedId, function () {
                $scope.SYNERGY.logger.log("Done", "Version removed", "INFO", "alert-success");
                $scope.fetch();
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Creates a new version
         */
        $scope.create = function () {

            if ($scope.myForm2.$invalid) {
                $scope.SYNERGY.modal.update("Missing required fields", "");
                $scope.SYNERGY.modal.show();
                return;
            }
            versionHttp.create($scope, new SynergyModels.Version($scope.newname, -1, false), function (data) {
                $scope.SYNERGY.logger.log("Done", "Version created", "INFO", "alert-success");
                $scope.fetch();
            }, $scope.generalHttpFactoryError);
        };
        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
    }
    /**
     * 
     * @param {type} $scope
     * @param {PlatformsFct} platformsHttp
     * @param {PlatformFct} platformHttp
     * @returns {undefined} */
    function AdminPlatformsCtrl($scope, platformsHttp, platformHttp, SynergyModels) {

        $scope.platforms = [];
        $scope.platformAffectedId = 0;
        $scope.platformAffected = "";
        $scope.newname = "";

        $scope.fetch = function () {
            if (!$scope.SYNERGY.session.hasAdminRights()) {
                return;
            }
            platformsHttp.get($scope, function (data) {
                $scope.platforms = data;
            }, $scope.generalHttpFactoryError, false);
        };

        /**
         * Opens dialog based on action value and saves $scope.platformAffectedId and $scope.platformAffected (platform name and ID to be affected
         * by future changes)
         * @param {String} action action name
         * @param {Number} platformId platform ID
         * @param {Number} platform platform name
         */
        $scope.perform = function (action, platformId, platform, isActive) {
            switch (action) {
                case "edit":
                    $scope.platformAffectedId = platformId;
                    $scope.platformAffected = platform;
                    $scope.newname = platform;
                    $scope.isActive = (parseInt(isActive, 10) === 1) ? true : false;
                    $("#editPlatformModal").modal("toggle");
                    break;
                case "create":
                    $("#createPlatformModal").modal("toggle");
                    break;
                case "delete":
                    $scope.platformAffectedId = platformId;
                    $scope.platformAffected = platform;
                    $("#deleteModal").modal("toggle");
                    break;
                default :
                    break;
            }
        };

        /**
         * Removes platform
         */
        $scope.remove = function () {
            $("#deleteModal").modal("toggle");
            platformHttp.remove($scope, $scope.platformAffectedId, function (data) {
                $scope.SYNERGY.logger.log("Done", "Platform removed", "INFO", "alert-success");
                $scope.fetch();
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Renames platform
         */
        $scope.rename = function () {

            if ($scope.myForm.$invalid) {
                $scope.SYNERGY.modal.update("Missing required fields", "");
                $scope.SYNERGY.modal.show();
                return;
            }

            platformHttp.edit($scope, new SynergyModels.Platform($scope.newname, $scope.platformAffectedId, $scope.isActive), function (data) {
                $scope.SYNERGY.logger.log("Done", "Platform renamed", "INFO", "alert-success");
                $scope.fetch();
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Creates a new platform
         * @returns {unresolved}
         */
        $scope.create = function () {

            if ($scope.myForm2.$invalid) {
                $scope.SYNERGY.modal.update("Missing required fields", "");
                $scope.SYNERGY.modal.show();
                return;
            }

            platformHttp.create($scope, new SynergyModels.Platform($scope.newname, -1), function (data) {
                $scope.SYNERGY.logger.log("Done", "Platform created", "INFO", "alert-success");
                $scope.fetch();
            }, $scope.generalHttpFactoryError);
        };
        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
    }
    /**
     * @param {UsersFct} usersHttp
     * @param {TribeFct} tribeHttp
     * @param {TribesFct} tribesHttp
     * @returns {undefined} */
    function AdminTribesCtrl($scope, $location, $timeout, usersHttp, tribeHttp, tribesHttp, sanitizerHttp, SynergyUtils, SynergyModels) {

        $scope.tribes = [];
        $scope.tribe = {}; // for new tribe page
        $scope.tribeAffectedId = 0;
        $scope.tribeAffected = "";
        $scope.newname = "";
        $scope.users = [];
        $scope.importTribesUrl = "http://" + window.location.host + "/dashboard/web/a_tribes.php?export=true";

        $scope.fetch = function () {
            if (!$scope.SYNERGY.session.hasAdminRights()) {
                return;
            }
            loadUsers();
            tribesHttp.get($scope, function (data) {
                $scope.tribes = data;
            }, $scope.generalHttpFactoryError);
        };


        $scope.importTribes = function () {
            tribesHttp.importTribes($scope, $scope.importTribesUrl, function (data) {
                $scope.SYNERGY.logger.log("Done", data + " tribes imported", "INFO", "alert-success");
                $scope.fetch();
            }, $scope.generalHttpFactoryError);
        };


        /**
         * Removes tribe
         */
        $scope.deleteTribe = function () {
            $("#deleteModal").modal("toggle");
            tribeHttp.remove($scope, $scope.tribeAffectedId, function (data) {
                $scope.SYNERGY.logger.log("Done", "Tribe removed", "INFO", "alert-success");
                $scope.fetch();
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Redirects to URL based on action value
         * @param {String} action action to be done
         * @param {String} id Tribe ID
         * @param {String} tribe Tribe name
         */
        $scope.perform = function (action, id, tribe) {
            switch (action) {
                case "create":
                    $location.path("administration/tribes/" + action);
                    break;
                default :
                    break;
            }
        };

        /**
         * Shows delete confirmation dialog
         * @param {Number} id tribe ID to be removed
         * @param {String} tribe tribe name
         */
        $scope.deleteModal = function (id, tribe) {
            $scope.tribeAffectedId = id;
            $scope.tribeAffected = tribe;
            $("#deleteModal").modal("toggle");
        };

        function loadUsers() {
            usersHttp.getAll($scope, function (data) {
                if (SynergyUtils.definedNotNull(data) && SynergyUtils.definedNotNull(data.users)) {
                    for (var i = 0, max = data.users.length; i < max; i++) {
                        data.users[i].displayName = data.users[i].firstName + " " + data.users[i].lastName + " (" + data.users[i].username + ")";
                    }
                }

                $scope.users = data.users;
                if (SynergyUtils.definedNotNull(data) && data.users.length > 0) {
                    $scope.tribe.leaderUsername = data.users[0].displayName;
                }
            }, $scope.generalHttpFactoryError);
        }

        $scope.loadPreview = function () {
            var _t = "<h1>" + ($scope.tribe.name || "") + "</h1><h3>Description</h3><div class='well'>" + ($scope.tribe.description || "") + "</div>";
            sanitizerHttp.getSanitizedInput($scope, _t, function (data) {
                $scope.preview = data;
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Creates a new tribe
         */
        $scope.create = function () {

            if ($scope.myForm2.$invalid || $scope.myForm.$invalid) {
                $scope.SYNERGY.modal.update("Missing required fields", "");
                $scope.SYNERGY.modal.show();
                return;
            }
            var tribe = new SynergyModels.Tribe($scope.tribe.name, $scope.tribe.description, ($scope.tribe.leaderUsername), -1);
            tribeHttp.create($scope, tribe, function (data) {
                $scope.SYNERGY.modal.update("Tribe created", "");
                $scope.SYNERGY.modal.show();
                window.history.back();
            }, $scope.generalHttpFactoryError);
        };

        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
    }
    /**
     * @param {RunsFct} runsHttp
     * @param {RunFct} runHttp
     */
    function AdminRunsCtrl($scope, $location, $routeParams, runsHttp, runHttp) {
        $scope.runs = [];
        $scope.page = $routeParams.page || 1;
        $scope.orderProp = "title";
        $scope.next = 0;
        $scope.prev = 0;
        $scope.nextPage = 1;
        $scope.prevPage = 1;
        var currentActionId = -1;
        var currentAction = "";

        $scope.fetch = function () {
            if (!$scope.SYNERGY.session.hasAdminRights()) {
                return;
            }
            runsHttp.get($scope, $scope.page, function (data) {

                data.testRuns.forEach(function (trun) {
                    if (trun.projectName === null || trun.projectName === "") {
                        trun.projectName = $scope.SYNERGY.product;
                    }
                });

                $scope.runs = data;
                $scope.next = (data.nextUrl.length > 1) ? 1 : 0;
                $scope.prev = (data.prevUrl.length > 1) ? 1 : 0;
                $scope.nextPage = parseInt($scope.page, 10) + 1;
                $scope.prevPage = parseInt($scope.page, 10) - 1;
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Unless action is delete, it redirects to action URL, otherwise opens confirmation dialog
         * @param {String} action action
         * @param {Number} id run ID
         * @param {String} run run name
         */
        $scope.perform = function (action, id, run) {
            switch (action) {
                case "delete":
                    $("#deleteModalLabel").text("Delete test run?");
                    $("#deleteModalBody").html("<p>This action will also delete all test assignments for this test run. Do you want to continue?</p>");
                    $("#deleteModal").modal("toggle");
                    currentActionId = id;
                    currentAction = "delete";
                    break;
                case "notify":
                    $("#deleteModalLabel").text("Send notifications?");
                    $("#deleteModalBody").html("<p>Do you really want to send email notifications to testers with incomplete test assignment?</p>");
                    $("#deleteModal").modal("toggle");
                    currentAction = "notify";
                    currentActionId = id;
                    break;
                case "freeze":
                    var _run = $scope.runs.testRuns.filter(function (e) {
                        return e.id === id;
                    })[0];

                    var target = (_run.isActive ? 0 : 1);
                    runHttp.freezeRun($scope, id, target, function (data) {
                        _run = $scope.runs.testRuns.filter(function (e) {
                            return e.id === id;
                        })[0];
                        _run.isActive = target;
                        $scope.SYNERGY.logger.log("Done", "Test run " + (target === 1 ? "unfrozen" : "frozen"), "INFO", "alert-success");
                    }, $scope.generalHttpFactoryError);
                    break;
                default:
                    $location.path("administration/run/" + id + "/" + action);
                    break;
            }
        };

        $scope.performAction = function () {
            switch (currentAction) {
                case "delete":
                    remove();
                    break;
                case "notify":
                    $("#deleteModal").modal("toggle");
                    runHttp.sendNotifications($scope, currentActionId, function (data) {
                        $scope.SYNERGY.logger.log("Done", data, "INFO", "alert-success");
                    }, function (data) {
                        $scope.SYNERGY.logger.log("Action failed", "", "INFO", "alert-error");
                        $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
                    });
                    break;
                default:
                    break;
            }
        };

        /**
         * Removes test run
         */
        function remove() {
            $("#deleteModal").modal("toggle");
            runHttp.remove($scope, currentActionId, function (data) {
                $scope.SYNERGY.logger.log("Done", "Test run removed", "INFO", "alert-success");
                $scope.fetch();
            }, $scope.generalHttpFactoryError);
        }
        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
    }
    /**
     * @param {RunFct} runHttp
     */
    function AdminRunCtrl($scope, $routeParams, runHttp, $location, attachmentHttp, sanitizerHttp, projectsHttp, SynergyModels, SynergyHandlers) {
        $scope.attachmentBase = $scope.SYNERGY.server.buildURL("run_attachment", {});
        $scope.refreshCodemirror = false;
        $scope.projects = [];
        $scope.testRun = new SynergyModels.TestRun("", "", "", "", "");
        $scope.id = $routeParams.id || -1;
        var currentAction = "";
        var currentActionId = -1;

        try {
            $("#start").datetimepicker({dateFormat: "yy-mm-dd", timeFormat: "HH:mm:ss"});
            $("#end").datetimepicker({dateFormat: "yy-mm-dd", timeFormat: "HH:mm:ss"});
        } catch (e) {
        }
        $scope.loadPreview = function () {
            var _t = "<h1>" + ($scope.testRun.title || "") + "</h1><h3>Description</h3><div class='well'>" + ($scope.testRun.desc || "") + "</div>";
            sanitizerHttp.getSanitizedInput($scope, _t, function (data) {
                $scope.preview = data;
            }, $scope.generalHttpFactoryError);
        };
        /**
         * Starts with action on given run attachment
         * Otherwise confirmation dialog is opened
         * @param {String} action action name
         * @param {Number} id attachment ID
         */
        $scope.performAttachment = function (action, id) {
            switch (action) {
                case "delete":
                    $("#deleteModalLabel").text("Delete attachment?");
                    $("#deleteModalBody").html("<p>Do you really want to delete attachment?</p>");
                    $("#deleteModal").modal("toggle");
                    currentAction = "deleteAttachment";
                    currentActionId = id;
                    break;
                default:
                    break;
            }
        };

        $scope.performAction = function () {
            switch (currentAction) {
                case "deleteAttachment":
                    $("#deleteModal").modal("toggle");
                    if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
                        return;
                    }
                    attachmentHttp.removeRunAttachment($scope, currentActionId, function (data) {
                        $scope.SYNERGY.logger.log("Done", "Attachment deleted", "INFO", "alert-success");
                        $scope.fetch();
                    }, function (data) {
                        $scope.SYNERGY.logger.log("Action failed", "", "INFO", "alert-error");
                        $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
                        $scope.fetch();
                    });
                    break;
                default:
                    break;
            }
        };

        $scope.cancel = function () {
            window.history.back();
        };

        /**
         * Creates a new test run
         */
        $scope.create = function () {
            var start = $("#start").val();
            var stop = $("#end").val();
            if ($scope.myForm.$invalid || start.length < 1 || stop.length < 1) {
                $scope.SYNERGY.modal.update("Missing required fields", "");
                $scope.SYNERGY.modal.show();
                return;
            }
            var _run = new SynergyModels.TestRun($scope.testRun.title, $scope.testRun.desc, $scope.getUTCTime(start), $scope.getUTCTime(stop), $scope.id).setNotifications($scope.testRun.notifications);
            _run.projectId = $scope.testRun.projectId;
            runHttp.create($scope, _run, function (data) {
                $scope.SYNERGY.modal.update("Test run created", "");
                $scope.SYNERGY.modal.show();
                $location.path("administration/runs/page/1");
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Modifies test run
         */
        $scope.edit = function () {
            var start = $("#start").val();
            var stop = $("#end").val();
            if ($scope.myForm.$invalid || start.length < 1 || stop.length < 1) {
                $scope.SYNERGY.modal.update("Missing required fields", "");
                $scope.SYNERGY.modal.show();
                return;
            }
            var _run = new SynergyModels.TestRun($scope.testRun.title, $scope.testRun.desc, $scope.getUTCTime(start), $scope.getUTCTime(stop), $scope.id).setNotifications($scope.testRun.notifications);
            _run.projectId = $scope.testRun.projectId;
            runHttp.edit($scope, _run, function (data) {
                $scope.SYNERGY.modal.update("Test run updated", "");
                $scope.SYNERGY.modal.show();
                window.history.back();
            }, $scope.generalHttpFactoryError);
        };

        $scope.uploadFile = function () {
            new SynergyHandlers.FileUploader([], "dropbox", $scope.SYNERGY.uploadFileLimit, $scope.SYNERGY.server.buildURL("run_attachment", {"id": $scope.id}), function (title, msg, level, style, fileName) {
                $scope.SYNERGY.logger.log(title, msg, level, style);
                $scope.fileName = fileName;
                $scope.fetch();
            }, function (title, msg, level, style) {
                $scope.SYNERGY.logger.log(title, msg, level, style);
            }).uploadFileFromFileChooser("fileToUpload");
        };

        $scope.validDates = function () {
            $scope.testRun.start = $("#start").val();
            $scope.testRun.stop = $("#end").val();
            return ($scope.testRun.start.length < 1 || $scope.testRun.stop.length < 1) ? false : true;
        };

        $scope.fetch = function () {
            if (!$scope.SYNERGY.session.hasAdminRights()) {
                return;
            }
            projectsHttp.getAll($scope, function (data) {
                $scope.projects = data;
                if ($scope.testRun.projectName === null && $scope.projects.length > 0) {
                    $scope.testRun.projectId = $scope.projects[0].id;
                }
            }, $scope.generalHttpFactoryError);
            var action = window.location + "";
            action = action.substring(action.lastIndexOf("/") + 1);
            switch (action) {
                case "edit":
                    if ($scope.id < 0) {
                        return;
                    }
                    runHttp.getOverview($scope, false, $scope.id, function (data) {
                        $scope.testRun = data;
                        $scope.testRun.start = $scope.getLocalDateTime($scope.testRun.start);
                        $scope.testRun.end = $scope.getLocalDateTime($scope.testRun.end);
                        if ($scope.projects.length > 0 && data.projectName === null) {
                            $scope.testRun.projectId = $scope.projects[0].id;
                        }
                        $scope.refreshCodemirror = true;
                    }, $scope.generalHttpFactoryError);
                    break;
                default:
                    break;
            }
        };

        new SynergyHandlers.FileUploader([], "dropbox", $scope.SYNERGY.uploadFileLimit, $scope.SYNERGY.server.buildURL("run_attachment", {"id": $scope.id}), function (title, msg, level, style, fileName) {
            $scope.SYNERGY.logger.log(title, msg, level, style);
            $scope.fileName = fileName;
            $scope.fetch();
        }, function (title, msg, level, style) {
            $scope.SYNERGY.logger.log(title, msg, level, style);
        });

        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
    }
    /**
     * 
     * @param {type} $scope
     * @param {type} $routeParams
     * @param {AssignmentsFct} assignmentsHttp
     * @param {UsersFct} usersHttp
     * @param {LabelsFct} labelsHttp
     * @param {PlatformsFct} platformsHttp
     * @param {TribesFct} tribesHttp
     * @param {VersionsFct} versionsHttp
     * @returns {undefined}
     */
    function AdminMatrixAssignmentCtrl($scope, $routeParams, assignmentsHttp, usersHttp, labelsHttp, platformsHttp, tribesHttp, versionsHttp, SynergyUtils) {
        $scope.assignment = {platforms: [], users: [], tribes: [], runId: $routeParams.id};
        $scope.selectedPlatforms = [];
        $scope.platforms = [];
        $scope.platformId = -1;
        $scope.ready = 0; // if < 5, page shows "please wait" message 

        $scope.fetch = function () {
            // load all required data
            loadPlatforms();
            loadLabels();
            loadUsers();
            loadVersions();
            tribesHttp.get($scope, function (data) {
                $scope.tribes = data;
                $scope.ready++;
            }, $scope.generalHttpFactoryError);
        };

        function loadVersions() {
            versionsHttp.get($scope, false, function (data) {
                $scope.versions = data;
                $scope.ready++;
            }, $scope.generalHttpFactoryError);
        }

        function loadPlatforms() {
            platformsHttp.get($scope, function (data) {
                $scope.platforms = data;
                $scope.ready++;
            }, $scope.generalHttpFactoryError, false);
        }

        function loadUsers() {
            usersHttp.getAll($scope, function (data) {
                if (SynergyUtils.definedNotNull(data) && SynergyUtils.definedNotNull(data.users)) {
                    for (var i = 0, max = data.users.length; i < max; i++) {
                        data.users[i].displayName = data.users[i].firstName + " " + data.users[i].lastName + " (" + data.users[i].username + ")";
                    }
                }
                $scope.ready++;
                $scope.users = data.users;
            }, $scope.generalHttpFactoryError);
        }

        function loadLabels() {
            labelsHttp.getAll($scope, function (data) {
                $scope.ready++;
                data.push({"label": "None", "id": "-1"});
                $scope.labels = data;
            }, $scope.generalHttpFactoryError);
        }

        $scope.create = function () {
            if (($scope.assignment.users.length < 1 && $scope.assignment.tribes.length < 1) || $scope.platforms.length < 1) {
                $scope.SYNERGY.modal.update("Missing required fields", "");
                $scope.SYNERGY.modal.show();
                return;
            }
            $scope.showWaitDialog();
            assignmentsHttp.create($scope, $scope.assignment, function (data) {
                $scope.SYNERGY.modal.update("Assignment created", "");
                $scope.SYNERGY.modal.show();
                window.history.back();
            }, function (data, status) {
                $scope.SYNERGY.modal.show();
                $scope.SYNERGY.logger.log("Action failed for users ", data, "INFO", "alert-error");
                $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
            });
        };
        /**
         * Adds platform to selected
         */
        $scope.addSelectedPlatform = function () {
            if (!$scope.platformId || platformAlreadySelected(parseInt($scope.platformId, 10))) { // avoid on load displaying empty 
                return;
            }
            $scope.assignment.platforms.push({id: parseInt($scope.platformId, 10), name: getPlatformName(parseInt($scope.platformId, 10))});
            $scope.platformId = -1;
        };

        $scope.addSelectedTribe = function () {
            if (!$scope.tribeId || tribeAlreadySelected(parseInt($scope.tribeId, 10))) { // avoid on load displaying empty 
                return;
            }
            $scope.assignment.tribes.push({id: parseInt($scope.tribeId, 10), name: getTribeName(parseInt($scope.tribeId, 10))});
            $scope.tribeId = -1;
        };

        $scope.addSelectedUser = function () {
            if (!$scope.username || userAlreadySelected($scope.username)) { // avoid on load displaying empty 
                return;
            }
            $scope.assignment.users.push({username: $scope.username, displayName: getDisplayName($scope.username)});
            $scope.username = "";
        };

        function userAlreadySelected(username) {
            for (var i = 0, max = $scope.assignment.users.length; i < max; i++) {
                if (username === $scope.assignment.users[i].username) {
                    return true;
                }
            }
            return false;
        }

        function tribeAlreadySelected(tribeID) {
            for (var i = 0, max = $scope.assignment.tribes.length; i < max; i++) {
                if (tribeID === $scope.assignment.tribes[i].id) {
                    return true;
                }
            }
            return false;
        }

        function platformAlreadySelected(platformID) {
            for (var i = 0, max = $scope.assignment.platforms.length; i < max; i++) {
                if (platformID === $scope.assignment.platforms[i].id) {
                    return true;
                }
            }
            return false;
        }

        /**
         * removes platform from selected
         * @param {Number} platformID
         */
        $scope.removeSelectedPlatform = function (platformID) {
            for (var i = 0, max = $scope.assignment.platforms.length; i < max; i++) {
                if (platformID === $scope.assignment.platforms[i].id) {
                    $scope.assignment.platforms.splice(i, 1);
                    $scope.platformId = -1;
                    return;
                }
            }
        };

        $scope.removeSelectedTribe = function (tribeId) {
            for (var i = 0, max = $scope.assignment.tribes.length; i < max; i++) {
                if (tribeId === $scope.assignment.tribes[i].id) {
                    $scope.assignment.tribes.splice(i, 1);
                    $scope.tribesId = -1;
                    return;
                }
            }
        };

        $scope.removeSelectedUser = function (username) {
            for (var i = 0, max = $scope.assignment.users.length; i < max; i++) {
                if (username === $scope.assignment.users[i].username) {
                    $scope.assignment.users.splice(i, 1);
                    $scope.username = -1;
                    return;
                }
            }
        };
        function getDisplayName(username) {
            for (var i = 0, max = $scope.users.length; i < max; i++) {
                if ($scope.users[i].username === username) {
                    return $scope.users[i].displayName;
                }
            }
            return "oops";
        }

        function getTribeName(tribeID) {
            for (var i = 0, max = $scope.tribes.length; i < max; i++) {
                if ($scope.tribes[i].id === tribeID) {
                    return $scope.tribes[i].name;
                }
            }
            return "oops";
        }

        function getPlatformName(platformID) {
            for (var i = 0, max = $scope.platforms.length; i < max; i++) {
                if ($scope.platforms[i].id === platformID) {
                    return $scope.platforms[i].name;
                }
            }
            return "oops";
        }

        $scope.cancel = function () {
            window.history.back();
        };

        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
    }
    /**
     * 
     * @param {SpecificationsFct} specificationsHttp
     * @param {AssignmentFct} assignmentHttp
     * @param {UsersFct} usersHttp
     * @param {LabelsFct} labelsHttp
     * @param {PlatformsFct} platformsHttp description
     * @param {TribesFct} tribesHttp description
     * @returns {undefined} */
    function AdminAssignmentCtrl($scope, $timeout, $routeParams, specificationsHttp, assignmentsHttp, usersHttp, labelsHttp, platformsHttp, tribesHttp, versionsHttp, assignmentHttp, runHttp, SynergyUtils, SynergyModels) {
        $scope.testRunId = $routeParams.id;
        $scope.assignments = [];
        $scope.platforms = [];
        $scope.users = [];
        $scope.ready = 0; // if < 4, message is displayed
        $scope.value = {};

        $scope.fetch = function () {
            if (!$scope.SYNERGY.session.hasAdminRights()) {
                return;
            }
            loadPlatforms();
            loadUsers();
            loadLabels();
            loadSpecifications();
        };

        function loadPlatforms() {
            platformsHttp.get($scope, function (data) {
                $scope.platforms = data;
                $scope.ready++;
                if (SynergyUtils.definedNotNull(data) && data.length > 0) {
                    $scope.value.platform = data[0];
                }
            }, $scope.generalHttpFactoryError, false);
        }

        function loadLabels() {
            labelsHttp.getAll($scope, function (data) {
                $scope.ready++;
                data.push({"label": "None", "id": "-1"});
                $scope.labels = data;
                $scope.value.label = data[data.length - 1];
            }, $scope.generalHttpFactoryError);
        }

        function loadUsers() {
            usersHttp.getAll($scope, function (data) {
                if (SynergyUtils.definedNotNull(data) && data.users) {
                    for (var i = 0, max = data.users.length; i < max; i++) {
                        data.users[i].displayName = data.users[i].firstName + " " + data.users[i].lastName + " (" + data.users[i].username + ")";
                    }
                }
                $scope.ready++;
                $scope.users = data.users;
                if (SynergyUtils.definedNotNull(data) && data.users.length > 0) {
                    $scope.value.user = $scope.users[0];
                }
            }, $scope.generalHttpFactoryError);
        }

        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });

        /**
         * Adds assignment to the assignments array that will be sent to server
         */
        $scope.addAssignment = function () {
            if (!$scope.value.specification) {
                $scope.SYNERGY.logger.showMsg("Oops", "No specification selected (maybe tribe does not have any specification?)", "alert-error");
                return;
            }
            var assignment = new SynergyModels.TestAssignment(parseInt($scope.value.platform.id, 10), $scope.value.user.username, parseInt($scope.value.label.id, 10));
            assignment.specificationId = parseInt($scope.value.specification.id, 10);
            assignment.testRunId = parseInt($scope.testRunId, 10);
            assignment.display = {
                user: $scope.value.user.firstName + " " + $scope.value.user.lastName,
                label: $scope.value.label.label,
                platform: $scope.value.platform.name,
                duplicates: false,
                specification: $scope.value.specification.value
            };
            $scope.assignments.push(assignment);
            assignmentHttp.checkExists($scope, assignment).then(function (data) {
                assignment.display.duplicates = true;
            }, function (response) {
                switch (response.status) {
                    case 404:
                        assignment.display.duplicates = false;
                        break;
                    default:
                        $scope.SYNERGY.logger.log("Action failed", "Unable to check for duplicates", "INFO", "alert-error");
                        break;
                }
            });
        };

        $scope.removeAssignment = function (index) {
            $scope.assignments.splice(index, 1);
        };

        function loadSpecifications() {
            runHttp.getSpecifications($scope, $scope.testRunId, function (data) {
                $scope.specifications = [];
                for (var i = 0, max = data.specifications.length; i < max; i += 1) {
                    $scope.specifications[i] = {
                        value: data.specifications[i].title + " (" + ((data.projectName === null || typeof data.projectName === "undefined") ? $scope.SYNERGY.product : data.projectName) + " " + data.specifications[i].version + ")",
                        info: "<a href='#/specification/" + data.specifications[i].id + "'>view</a>",
                        id: data.specifications[i].id,
                        type: "specification",
                        version: data.specifications[i].version
                    };
                }
                $scope.value.specification = $scope.specifications[0];
                $scope.ready++;
            }, $scope.generalHttpFactoryError);
        }

        $scope.cancel = function () {
            window.history.back();
        };

        /**
         * Creates a new test assignment
         * @returns {unresolved}
         */
        $scope.create = function () {

            $scope.showWaitDialog();
            assignmentsHttp.createForUsers($scope, $scope.assignments, function (data) {
                $scope.SYNERGY.modal.update("Assignments created", "");
                $scope.SYNERGY.modal.show();
                window.history.back();
            }, function (data) {
                $scope.SYNERGY.modal.show();
                $scope.SYNERGY.logger.log("Action failed ", data, "INFO", "alert-error");
                $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
            });
        };
    }
    /**
     * @param {UserFct} userHttp
     * @param {UsersFct} usersHttp
     * @returns {undefined} 
     */
    function AdminUsersCtrl($scope, $location, $routeParams, userHttp, usersHttp) {
        $scope.users = [];
        $scope.page = $routeParams.page || 1;
        var currentUser = "";
        $scope.importUsersUrl = "http://" + window.location.host + "/dashboard/web/a_netcatusers.php";
        $scope.fetch = function () {
            if (!$scope.SYNERGY.session.hasAdminRights()) {
                return;
            }
            usersHttp.get($scope, $scope.page, function (data) {
                $scope.users = data.users;
                $scope.next = (data.nextUrl.length > 1) ? 1 : 0;
                $scope.prev = (data.prevUrl.length > 1) ? 1 : 0;
                $scope.nextPage = parseInt($scope.page, 10) + 1;
                $scope.prevPage = parseInt($scope.page, 10) - 1;
            }, $scope.generalHttpFactoryError);
        };

        $scope.importUsers = function () {
            usersHttp.importUsers($scope, $scope.importUsersUrl, function (data) {
                $scope.SYNERGY.logger.log("Done", data + " users imported", "INFO", "alert-success");
                $scope.fetch();
            }, $scope.generalHttpFactoryError);
        };

        $scope.retireUsers = function () {
            usersHttp.retireUsersWithRole($scope, "tester", function () {
                $scope.SYNERGY.logger.log("Done", "Removed users with role 'tester' retired", "INFO", "alert-success");
                $scope.fetch();
            }, $scope.generalHttpFactoryError);
        };

        $scope.perform = function (action, username) {
            if (action !== "delete") {
                $location.path("administration/user/" + username + "/" + action);
            } else {
                switch (action) {
                    case "delete":
                        $("#deleteModal").modal("toggle");
                        currentUser = username;
                        break;
                    default:
                        break;
                }
            }
        };

        $scope.newuser = function () {
            $location.path("administration/user/-1/create");
        };

        /**
         * Deletes user
         */
        $scope.deleteUser = function () {
            $("#deleteModal").modal("toggle");
            userHttp.remove($scope, currentUser, function (data) {
                $scope.SYNERGY.logger.log("Done", "User removed", "INFO", "alert-success");
                $scope.fetch();
            }, $scope.generalHttpFactoryError);
        };

        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
    }
    /**
     * 
     * @param {UserFct} userHttp
     * @returns {undefined} 
     **/
    function AdminUserCtrl($scope, $routeParams, userHttp, SynergyModels) {
        $scope.user = {
        };
        $scope.username = $routeParams.username || "";
        $scope.passwordChangeAllowed = !$scope.SYNERGY.useSSO;
        $scope.updatePassword = false;
        this.oldUsername = ""; // in case admin changes username, need to track of the old one
        var self = this;
        $scope.fetch = function () {
            if (!$scope.SYNERGY.session.hasAdminRights()) {
                return;
            }
            if ($scope.username === "-1") {
                // new user
                return;
            }
            userHttp.get($scope, $scope.username, function (data) {
                self.oldUsername = $scope.username;
                $scope.user = data;
            }, $scope.generalHttpFactoryError);
        };

        $scope.cancel = function () {
            window.history.back();
        };

        /**
         * Creates user
         */
        $scope.save = function () {
            var invalidPassword = typeof $scope.user.password === "undefined" || $scope.user.password === null || $scope.user.password.length < 1;
            if ($scope.myForm.$invalid || ($scope.updatePassword && invalidPassword)) {
                $scope.SYNERGY.modal.update("Missing required fields", "");
                $scope.SYNERGY.modal.show();
                return;
            }
            var u = new SynergyModels.User($scope.user.firstName, $scope.user.lastName, $scope.user.username, $scope.user.role, -1);
            u.email = $scope.user.email;
            if ($scope.updatePassword) {
                u.password = $scope.user.password;
            }
            u.emailNotifications = $scope.user.emailNotifications;
            userHttp.create($scope, u, function (data) {
                $scope.SYNERGY.modal.update("User created", "");
                $scope.SYNERGY.modal.show();
                window.history.back();
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Updates user
         * @returns {unresolved}
         */
        $scope.edit = function () {
            var invalidPassword = typeof $scope.user.password === "undefined" || $scope.user.password === null || $scope.user.password.length < 1;
            if ($scope.myForm.$invalid || ($scope.updatePassword && invalidPassword)) {
                $scope.SYNERGY.modal.update("Missing required fields", "");
                $scope.SYNERGY.modal.show();
                return;
            }
            var u = new SynergyModels.User($scope.user.firstName, $scope.user.lastName, $scope.user.username, $scope.user.role, -1, self.oldUsername);
            u.email = $scope.user.email;
            if ($scope.updatePassword) {
                u.password = $scope.user.password;
            }
            u.emailNotifications = $scope.user.emailNotifications;
            userHttp.edit($scope, u, function (data) {
                $scope.SYNERGY.modal.update("User updated", "");
                $scope.SYNERGY.modal.show();
                window.history.back();
            }, $scope.generalHttpFactoryError);
        };

        var self2 = $scope;
        $scope.init(function () {
            self2.fetch();
        });
    }
    /**
     * 
     * @param {SettingsFct} settingsHttp
     * @returns {undefined} 
     */
    function AdminSettingCtrl($scope, settingsHttp) {
        $scope.settings = [];
        $scope.newValue = "";
        $scope.newDesc = "";
        $scope.newKey = "";

        $scope.fetch = function () {
            if (!$scope.SYNERGY.session.hasAdminRights()) {
                return;
            }
            settingsHttp.get($scope, function (data) {
                $scope.settings = data;
            }, $scope.generalHttpFactoryError);
        };

        $scope.save = function () {
            for (var i = 0, limit = $scope.settings.length; i < limit; i += 1) {
                if ($scope.settings[i].value.length < 1) {
                    $scope.SYNERGY.logger.log("Missing value: ", $scope.settings[i].key, "INFO", "alert-error");
                    return;
                }
            }
            settingsHttp.edit($scope, $scope.settings, function (data) {
                $scope.SYNERGY.logger.log("Done", "Settings updated", "INFO", "alert-success");
                $scope.fetch();
            }, $scope.generalHttpFactoryError);
        };

        $scope.addSetting = function () {
            $scope.settings.push({"value": $scope.newValue, "label": $scope.newDesc, "key": $scope.newKey});
            $scope.newValue = $scope.newDesc = $scope.newKey = "";
        };

        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
    }
    /**
     * 
     * @param {AboutFct} aboutHttp
     * @returns {undefined} 
     */
    function AboutCtrl($scope, aboutHttp) {
        $scope.$emit("updateNavbar", {item: "nav_empty"});
        $scope.statistics = [];

        $scope.fetch = function () {
            aboutHttp.get($scope, function (data) {
                $scope.$emit("updateBreadcrumbs", {link: "about", title: "About"});
                $scope.statistics = data;
            }, $scope.generalHttpFactoryError);
        };
        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
    }
    function TribesCtrl($scope, $location, $timeout, tribesHttp) {
        $scope.$emit("updateNavbar", {item: "nav_empty"});
        $scope.tribes = [];

        $scope.fetch = function () {
            tribesHttp.get($scope, function (data) {
                $scope.tribes = data;
                $scope.$emit("updateBreadcrumbs", {link: "tribes", title: "Tribes"});
            }, $scope.generalHttpFactoryError, true);
        };
        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
    }
    function AdminLogCtrl($scope, logHttp, sessionRenewalHttp) {
        $scope.logContent = "";
        $scope.fetch = function () {
            logHttp.get($scope, function (data) {
                $scope.logContent = data;
            }, $scope.generalHttpFactoryError);
        };

        $scope.deleteLog = function () {
            logHttp.remove($scope, function () {
                $scope.logContent = "";
                $scope.SYNERGY.logger.log("Deleted", "", "INFO", "alert-info");
            }, $scope.generalHttpFactoryError);
        };

        $scope.sso = function () {
            sessionRenewalHttp.test($scope, function (d) {
                window.console.log("Done");
            }, function (d) {
                window.console.log("Opps, not done at all");
            });
        };

        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
    }
    function AdminDatabaseCtrl($scope, databaseHttp) {
        $scope.order = "ASC";
        $scope.tables = [];
        $scope.columns = [];
        $scope.orderBy = "";
        $scope.data = [];
        $scope.limit = 10;
        $scope.selectedTable = "";

        $scope.fetch = function () {
            databaseHttp.getTables($scope, function (data) {
                $scope.tables = data;
            }, $scope.generalHttpFactoryError);
        };

        $scope.loadTable = function () {
            databaseHttp.getColumns($scope, $scope.selectedTable, function (data) {
                $scope.columns = data;
                $scope.orderBy = data[0];
            }, $scope.generalHttpFactoryError);
        };

        $scope.show = function () {
            databaseHttp.listTable($scope, $scope.selectedTable, $scope.limit || 10, $scope.order, $scope.orderBy || "id", function (data) {
                $scope.data = data;
            }, $scope.generalHttpFactoryError);
        };

        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
    }
    function AssignmentVolunteerCtrl($scope, $routeParams, specificationsHttp, assignmentHttp, labelsHttp, platformsHttp, versionsHttp, reviewHttp, runHttp, SynergyUtils, SynergyModels) {
        $scope.suggestions = [];
        $scope.specifications = [];
        $scope.assignment = {
            testRun: $routeParams.id,
            username: "",
            tribe: -1,
            specification: "",
            label: "",
            platform: "",
            specificationId: "",
            labelId: "",
            platformId: ""
        };
        $scope.platforms = [];
        $scope.ready = 0;
        $scope.assignmentType = "test";
        $scope.reviewUrl;
        $scope.availablePages = [];
        $scope.reviewPage = null;
        $scope.showOnlyNotUsedPages = true;
        var deleteModalDisplayed = false;

        $scope.initData = function () {
            if ($scope.assignmentType === "review") {
                $scope.changeAvailablePages();
            }
        };

        $scope.fetch = function () {
            loadPlatforms();
            loadLabels();
            loadVersions();
            loadSpecifications();
        };

        $scope.changeAvailablePages = function () {
            if ($scope.showOnlyNotUsedPages) {
                reviewHttp.listNotStarted($scope, $scope.assignment.testRun, function (d) {
                    $scope.availablePages = d;
                }, $scope.generalHttpFactoryError);
            } else {
                reviewHttp.list($scope, function (d) {
                    $scope.availablePages = d;
                }, $scope.generalHttpFactoryError);
            }
        };

        function loadPlatforms() {
            platformsHttp.get($scope, function (data) {
                $scope.platforms = data;
                $scope.ready++;
                if (SynergyUtils.definedNotNull(data) && data.length > 0) {
                    $scope.assignment.platform = data[0].id;
                }
            }, $scope.generalHttpFactoryError, true);
        }

        function loadVersions() {
            versionsHttp.get($scope, true, function (data) {
                $scope.versions = data;
                $scope.ready++;
                if (SynergyUtils.definedNotNull(data) && data.length > 0) {
                    $scope.selectedVersion = data[0].name;
                    $scope.filter();
                }
            }, $scope.generalHttpFactoryError);
        }

        $scope.filter = function () {
            var matching = [];
            for (var i = 0, max = $scope.specifications.length; i < max; i++) {
                if ($scope.selectedVersion === $scope.specifications[i].version) {
                    matching.push($scope.specifications[i]);
                }
            }
            $scope.suggestions = matching;
        };


        function loadLabels() {
            labelsHttp.getAll($scope, function (data) {
                $scope.ready++;
                data.push({"label": "None", "id": "-1"});
                $scope.labels = data;
                $scope.assignment.label = data[data.length - 1].id;
            }, $scope.generalHttpFactoryError);
        }

        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });

        function loadSpecifications() {
            runHttp.getSpecifications($scope, $routeParams.id, function (data) {
                $scope.specifications = [];
                for (var i = 0, max = data.specifications.length; i < max; i += 1) {
                    $scope.specifications[i] = {
                        value: data.specifications[i].title + " (" + ((data.projectName === null || typeof data.projectName === "undefined") ? $scope.SYNERGY.product : data.projectName) + " " + data.specifications[i].version + ")",
                        info: "<a href='#/specification/" + data.specifications[i].id + "'>view</a>",
                        id: data.specifications[i].id,
                        type: "specification",
                        version: data.specifications[i].version
                    };
                }
                $scope.filter();
                $scope.ready++;
            }, $scope.generalHttpFactoryError);
        }


        $scope.cancel = function () {
            window.history.back();
        };


        function checkAssignmentExists(assignment) {
            assignmentHttp.checkExists($scope, assignment).then(function (data) {
                $("#deleteModal").modal("toggle");
                deleteModalDisplayed = true;
            }, function (response) {
                switch (response.status) {
                    case 404:
                        submitAssignmentData(assignment);
                        break;
                    default:
                        $scope.SYNERGY.logger.log("Action failed", "Unable to check for duplicates", "INFO", "alert-error");
                        break;
                }
            });
        }

        function submitAssignmentData(assignment) {
            $scope.showWaitDialog();
            assignmentHttp.createVolunteer($scope, assignment, function (data) {
                $scope.SYNERGY.modal.update("Assignment created", "");
                $scope.SYNERGY.modal.show();
                window.history.back();
            }, function (data) {
                $scope.SYNERGY.modal.show();
                $scope.SYNERGY.logger.log("Action failed", data, "INFO", "alert-error");
                $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
            });
        }


        function submitReviewAssignment() {
            if ($scope.reviewPage === null) {
                $scope.SYNERGY.modal.update("Missing required fields", "");
                $scope.SYNERGY.modal.show();
                return;
            }
            var reviewAssignment = new SynergyModels.ReviewAssignment($scope.SYNERGY.session.username, $scope.reviewPage.url);
            reviewAssignment.owner = $scope.reviewPage.owner;
            reviewAssignment.title = $scope.reviewPage.title;
            reviewAssignment.testRunId = parseInt($scope.assignment.testRun, 10);

            reviewHttp.createVolunteer($scope, reviewAssignment, function (data) {
                $scope.SYNERGY.modal.update("Assignment created", "");
                $scope.SYNERGY.modal.show();
                window.history.back();
            }, function (data) {
                $scope.SYNERGY.modal.show();
                $scope.SYNERGY.logger.log("Action failed", data, "INFO", "alert-error");
                $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
            });

        }

        /**
         * Creates a new test assignment
         * @returns {unresolved}
         */
        $scope.create = function (skipDuplicateCheck) {

            if ($scope.assignmentType === "review") {
                submitReviewAssignment();
                return;
            }

            var assignment = new SynergyModels.TestAssignment(parseInt($scope.assignment.platform, 10), $scope.SYNERGY.session.username, parseInt($scope.assignment.label, 10));
            assignment.specificationId = parseInt($scope.assignment.specificationId, 10);
            assignment.testRunId = parseInt($scope.assignment.testRun, 10);

            if (!$scope.assignment.specificationId || assignment.platformId < 0) {
                $scope.SYNERGY.modal.update("Missing required fields", "");
                $scope.SYNERGY.modal.show();
                return;
            }

            if (($scope.assignedToUser && assignment.username.length < 1) || ($scope.assignedToTribe && $scope.assignment.tribe.id < 1)) {
                $scope.SYNERGY.modal.update("Missing assignee", "");
                $scope.SYNERGY.modal.show();
                return;
            }

            if (assignment.labelId < 0 && parseInt($scope.assignment.label, 10) > 0) {
                $scope.SYNERGY.modal.update("Label does not exist", "");
                $scope.SYNERGY.modal.show();
                return;
            }

            if (!skipDuplicateCheck) {
                checkAssignmentExists(assignment);
            } else {
                if (deleteModalDisplayed) {
                    $("#deleteModal").modal("toggle");
                }
                submitAssignmentData(assignment);
            }
        };
    }
    /**
     * 
     * @param {type} $scope
     * @param {type} $routeParams
     * @param {RevisionsFct} revisionsHttp
     * @returns {undefined}
     */
    function RevisionCtrl($scope, $routeParams, revisionsHttp) {

        $scope.revisions = [];
        $scope.specificationId = $routeParams.id;

        $scope.fetch = function () {
            revisionsHttp.listRevisions($scope, $scope.specificationId, function (data) {
                $scope.revisions = data;
            }, $scope.generalHttpFactoryError);
        };

        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
        /**
         * Loads 2 revisions from server and calls diffRevisions() to print them
         */
        $scope.compareRevisions = function () {
            revisionsHttp.getRevisions($scope, $scope.choiceA, $scope.choiceB, $scope.specificationId, function (data) {
                diffRevisions(data[0], data[1]);
            }, $scope.generalHttpFactoryError);
        };

        function diffRevisions(rev1, rev2) {
            var base = difflib.stringAsLines(rev1.content);
            var newtxt = difflib.stringAsLines(rev2.content);
            var sm = new difflib.SequenceMatcher(base, newtxt);
            var opcodes = sm.get_opcodes();
            var diffoutputdiv = $("#diffoutput");
            diffoutputdiv.html("");

            diffoutputdiv.append(diffview.buildView({baseTextLines: base,
                newTextLines: newtxt,
                opcodes: opcodes,
                baseTextName: $scope.getLocalTime(rev1.date, true) + "(" + rev1.author + ")",
                newTextName: $scope.getLocalTime(rev2.date, true) + "(" + rev2.author + ")",
                contextSize: null,
                viewType: 1}));
        }
    }
    function CalendarCtrl($scope, calendarHttp) {
        $scope.$emit("updateBreadcrumbs", {link: "calendar", title: "Calendar"});

        function loadCalendar() {
            calendarHttp.getEvents($scope, function (data) {
                var items = [];

                for (var i = 0, max = data.length; i < max; i += 1) {
                    items.push({url: "#/run/" + data[i].id, title: data[i].title, start: new Date(data[i].start.substr(0, 4), parseInt(data[i].start.substr(4, 2), 10) - 1, data[i].start.substr(6, 2)), end: new Date(data[i].end.substr(0, 4), parseInt(data[i].end.substr(4, 2), 10) - 1, data[i].end.substr(6, 2))});
                }

                $("#calendar").fullCalendar({
                    header: {
                        left: "prev,next today",
                        center: "title",
                        right: "month,agendaWeek,agendaDay"
                    },
                    editable: true,
                    events: items,
                    eventMouseover: function (event, jsEvent, view) {
                        if (view.name !== "agendaDay") {
                            $(jsEvent.target).attr("title", event.title);
                        }
                    }
                });
            }, function () {
                window.console.log("Failed to load calendar events");
            });
        }
        loadCalendar();

    }
    function AssignmentTribeCtrl($scope, $routeParams, tribesHttp, labelsHttp, platformsHttp, assignmentsHttp, assignmentHttp, SynergyModels) {

        $scope.tribes = [];
        $scope.platforms = [];
        $scope.labels = [];
        $scope.ready = 0;
        $scope.value = {};
        $scope.assignments = [];
        $scope.runId = $routeParams.id;

        $scope.fetch = function () {
            loadTribes();
            loadLabels();
            loadPlatforms();
        };

        function loadPlatforms() {
            platformsHttp.get($scope, function (data) {
                $scope.platforms = data;
                $scope.value.platform = data[0];
                $scope.ready++;
            }, $scope.generalHttpFactoryError, true);
        }

        function loadLabels() {
            labelsHttp.getAll($scope, function (data) {
                $scope.ready++;
                data.push({"label": "None", "id": "-1"});
                $scope.value.label = data[data.length - 1];
                $scope.labels = data;
            }, $scope.generalHttpFactoryError);
        }
        /**
         * Adds assignment to the array of assignments to be sent to server
         */
        $scope.addAssignment = function () {
            if (!$scope.value.specification) {
                $scope.SYNERGY.logger.showMsg("Oops", "No specification selected (maybe tribe does not have any specification?)", "alert-error");
                return;
            }
            var assignment = new SynergyModels.TestAssignment(parseInt($scope.value.platform.id, 10), $scope.value.user.username, parseInt($scope.value.label.id, 10));
            assignment.specificationId = parseInt($scope.value.specification.id, 10);
            assignment.testRunId = parseInt($scope.runId, 10);
            assignment.tribeId = parseInt($scope.selectedTribe.id, 10);
            assignment.display = {
                tribe: $scope.selectedTribe.name,
                user: $scope.value.user.firstName + " " + $scope.value.user.lastName,
                label: $scope.value.label.label,
                platform: $scope.value.platform.name,
                specification: $scope.value.specification.title + " (" + $scope.SYNERGY.product + " " + $scope.value.specification.version + ")",
                duplicates: false
            };
            $scope.assignments.push(assignment);
            assignmentHttp.checkExists($scope, assignment).then(function (data) {
                assignment.display.duplicates = true;
            }, function (response) {
                switch (response.status) {
                    case 404:
                        assignment.display.duplicates = false;
                        break;
                    default:
                        $scope.SYNERGY.logger.log("Action failed", "Unable to check for duplicates", "INFO", "alert-error");
                        break;
                }
            });
        };
        /**
         * Respons to change of tribe, sets preselected specification and user
         */
        $scope.filterTribe = function () {
            $scope.value.user = $scope.selectedTribe.members[0];
            if ($scope.selectedTribe.ext.specifications) {
                $scope.value.specification = $scope.selectedTribe.ext.specifications[0];
            }
        };

        $scope.removeAssignment = function (index) {
            $scope.assignments.splice(index, 1);
        };

        function setProject(data) {
            for (var t = 0, max = data.length; t < max; t++) {
                if (data[t].hasOwnProperty("ext") && data[t].ext.hasOwnProperty("specifications")) {
                    for (var s = 0, maxs = data[t].ext.specifications.length; s < maxs; s++) {
                        if (data[t].ext.specifications[s].hasOwnProperty("projects") && data[t].ext.specifications[s].projects.length > 0) {
                            data[t].ext.specifications[s]._project = data[t].ext.specifications[s].projects[0].name;
                        } else {
                            data[t].ext.specifications[s]._project = $scope.SYNERGY.product;
                        }
                    }
                }
            }
        }

        function loadTribes() {
            tribesHttp.getTribesForRun($scope, $scope.SYNERGY.session.username, $scope.runId, function (data) {
                setProject(data);
                $scope.tribes = data;
                $scope.selectedTribe = data[0];
                $scope.value.user = data[0].members[0];
                if (data[0].ext.specifications) {
                    $scope.value.specification = data[0].ext.specifications[0];
                }
                $scope.ready++;
            }, $scope.generalHttpFactoryError);
        }

        $scope.cancel = function () {
            window.history.back();
        };
        $scope.create = function () {
            assignmentsHttp.createForTribes($scope, $scope.assignments, function (data) {
                $scope.SYNERGY.modal.update("Assignments created", "");
                $scope.SYNERGY.modal.show();
                window.history.back();
            }, $scope.generalHttpFactoryError);
        };

        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });

    }
    function StatisticsCtrl($scope, statisticsHttp, $routeParams, SynergyUtils, SynergyModels, SynergyHandlers, SynergyIssue) {
        $scope.id = $routeParams.id;
        $scope.timeView = "all";
        $scope.inArchive = window.location.href.indexOf("archive") > 0 ? true : false;
        var archivedData = {};
        /**
         * Order object for Tribes table
         */
        $scope.orderProp = {
            prop: "time",
            descending: true
        };

        try {
            $("#start").datetimepicker({dateFormat: "yy-mm-dd", timeFormat: "HH:mm:ss"});
            $("#end").datetimepicker({dateFormat: "yy-mm-dd", timeFormat: "HH:mm:ss"});
        } catch (e) {

        }
        $scope.P1Issues = [];
        $scope.P2Issues = [];
        $scope.P3Issues = [];
        $scope.unresolvedIssues = [];
        $scope.unknownIssues = [];
        $scope.allIssues = [];
        $scope.reviewTotal = {};

        /**
         * Order object for testers table
         */
        $scope.orderPropU = {
            prop: "time",
            descending: true
        };
        $scope.orderPropR = {
            prop: "time",
            descending: true
        };

        var cache = {};
        $scope.tribes = [];
        var tribesSpecs = [];
        $scope.data = {};
        $scope.computed = {};
        var totalCounts = {};
        var fallback = false;

        $scope.fetch = function () {
            if ($scope.inArchive) {
                statisticsHttp.getArchive($scope, $scope.id, fallback, function (data) {
                    $scope.data = data;
                    cache = data;
                    tribesSpecs = data.tribes;
                    evaluateComputedData(data);
                    if (data.reviews) {
                        evaluateReviews(data.reviews);
                    }
                }, function (data, status) {
                    if (status === 404 && !fallback) {
                        $scope.SYNERGY.logger.log("Opps", "Statistics not found, trying alternative way", "INFO", "alert-warning");
                        fallback = true;
                        self.fetch();
                        return;
                    }
                    $scope.SYNERGY.logger.log("Action failed", data, "INFO", "alert-error");
                    $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
                });
            } else {
                statisticsHttp.get($scope, $scope.id, function (data) {
                    cache = data;
                    $scope.data = data;
                    tribesSpecs = data.tribes;
                    evaluateComputedData(data);
                    if (data.reviews) {
                        evaluateReviews(data.reviews);
                    }
                }, $scope.generalHttpFactoryError);
            }
        };

        $scope.getAllTimeData = function () {
            if ($scope.timeView === "all") {
                $scope.data = cache;
                tribesSpecs = cache.tribes;
                evaluateComputedData(cache);
                if ($scope.data.reviews) {
                    evaluateReviews($scope.data.reviews);
                }
            } else {
                $("#start").val("");
                $("#end").val("");
            }
        };

        function evaluateReviews(reviews) {
            var total = {
                total: reviews.length,
                completed: 0,
                time: 0,
                comments: 0,
                reviewers: 0
            };
            var collector = new SynergyModels.UserReviewStats();
            for (var i = 0, max = reviews.length; i < max; i++) {
                if (reviews[i].isFinished) {
                    total.completed++;
                }
                total.time += reviews[i].timeTaken;
                total.comments += reviews[i].numberOfComments;
                collector.addReview(reviews[i]);
            }
            total.time = Math.floor(total.time / 60) + " hours and " + (total.time % 60) + " minutes";
            $scope.reviews = collector.finish();
            total.reviewers = $scope.reviews.length;
            total.finishRate = (Math.round(1000 * total.completed / total.total) / 10) || 0;
            $scope.reviewTotal = total;
        }

        /**
         * Loads statistics for time period
         */
        $scope.filterStatistics = function () {
            var start = $("#start").val();
            var stop = $("#end").val();

            if (start.length < 1 || stop.length < 1) {
                $scope.SYNERGY.logger.log("Missing data", "Please specify both 'from' and 'to' time periods", "INFO", "alert-error");
                return;
            }

            if ($scope.inArchive) {
                filterArchivedData(start, stop);
            } else {
                statisticsHttp.getPeriod($scope, $scope.id, {"from": $scope.getUTCTime(start), "to": $scope.getUTCTime(stop)}, function (data) {
                    $scope.data = data;
                    tribesSpecs = data.tribes;
                    evaluateComputedData(data);
                    if ($scope.data.reviews) {
                        evaluateReviews($scope.data.reviews);
                    }
                }, $scope.generalHttpFactoryError);
            }
        };


        function filterArchivedData(start, stop) {
            start = SynergyUtils.localToUTCTimestamp(start);
            stop = SynergyUtils.localToUTCTimestamp(stop);
            $scope.data = new SynergyHandlers.ArchiveDataFilter(SynergyUtils.shallowClone(cache)).getData(start, stop);
            tribesSpecs = $scope.data.tribes;
            evaluateComputedData($scope.data);
            if ($scope.data.reviews) {
                evaluateReviews($scope.data.reviews);
            }
        }

        $scope.sortTribes = function (prop) {
            if ($scope.orderProp.prop === prop) {
                $scope.orderProp.descending = !$scope.orderProp.descending;
            } else {
                $scope.orderProp = {
                    prop: prop,
                    descending: true
                };
            }
        };

        $scope.sortUsers = function (prop) {
            if ($scope.orderPropU.prop === prop) {
                $scope.orderPropU.descending = !$scope.orderPropU.descending;
            } else {
                $scope.orderPropU = {
                    prop: prop,
                    descending: true
                };
            }
        };
        $scope.sortReviewers = function (prop) {
            if ($scope.orderPropR.prop === prop) {
                $scope.orderPropR.descending = !$scope.orderPropR.descending;
            } else {
                $scope.orderPropR = {
                    prop: prop,
                    descending: true
                };
            }
        };

        /**
         * Goes through loaded data and count overall statistics, draw charts etc.
         */
        function evaluateComputedData(data) {
            var computed = {};
            computed.testers = getTesters(data);
            computed.testers = computed.testers.filter(function (t) {
                return t.completedCases > 0;
            });
            computed.testersCount = computed.testers.length;
            computed.time = Math.floor(totalCounts.timeToComplete / 60) + " hours and " + (totalCounts.timeToComplete % 60) + " minutes";
            computed.completedRelative = (Math.round(1000 * data.testRun.completed / data.testRun.total) / 10) || 0;
            computed.passRate = (Math.round(1000 * totalCounts.passed / data.testRun.completed) / 10) || 0;
            $scope.tribes = getTribesData(data);
            $scope.issuesTotal = data.issues.length;
            $scope.issuesUrl = $scope.SYNERGY.issues.viewLink(data.testRun.projectName, data.issues);
            $scope.issueColor = getIssueColor(data.issues);
            var issueCollector = new SynergyIssue.RunIssuesCollector();
            issueCollector.addIssues(data.issues);
            $scope.unresolvedIssues = issueCollector.issuesStats.unresolvedIssues;
            $scope.P1Issues = issueCollector.issuesStats.P1Issues;
            $scope.unknownIssues = issueCollector.issuesStats.unknownIssues;
            $scope.P2Issues = issueCollector.issuesStats.P2Issues;
            $scope.P3Issues = issueCollector.issuesStats.P3Issues;
            $scope.computed = computed;
            SynergyUtils.ProgressChart([issueCollector.issuesStats.opened / (issueCollector.issuesStats.total / 100), 100 - (issueCollector.issuesStats.opened / (issueCollector.issuesStats.total / 100))], ["#ccc", "#62c462"], ["Unresolved", "Resolved"], "issuesResolution");
            SynergyUtils.ProgressChart([issueCollector.issuesStats.P1 / (issueCollector.issuesStats.total / 100), issueCollector.issuesStats.P2 / (issueCollector.issuesStats.total / 100), issueCollector.issuesStats.P3 / (issueCollector.issuesStats.total / 100), issueCollector.issuesStats.P4 / (issueCollector.issuesStats.total / 100)], ["#ee5f5b", "#f89406", "#fbeed5", "#ccc"], ["P1 (" + issueCollector.issuesStats.P1 + ")", "P2 (" + issueCollector.issuesStats.P2 + ")", "P3 (" + issueCollector.issuesStats.P3 + ")", "P4 (" + issueCollector.issuesStats.P4 + ")"], "issuesPriority");
            printCharts();
        }

        function printCharts() {
            SynergyUtils.ProgressChart([$scope.computed.completedRelative, 100 - $scope.computed.completedRelative], ["#08c", "#ccc"], ["Executed cases", "Pending cases"], "canvas1");
            SynergyUtils.ProgressChart([$scope.computed.passRate, 100 - $scope.computed.passRate], ["#62c462", "#ccc"], ["Passed cases", "Other cases"], "canvas2");
        }

        /**
         * Based on number and priority of bugs, colors the Issues div
         */
        function getIssueColor(issues) {
            var sums = {"P1": 0, "P2": 0, "P3": 0, "P4": 0};
            for (var i = 0, max = issues.length; i < max; i++) {
                sums[issues[i].priority]++;
            }
            if (sums.P1 > 0) {
                return "bugs-error";
            }

            if (sums.P2 === 1) {
                return "bugs-warning";
            }

            if (sums.P2 > 1) {
                return "bugs-orange";
            }

            return "bugs-success";
        }

        function getTesters(data) {
            totalCounts.passed = 0;
            totalCounts.timeToComplete = 0;
            if (!data.assigneesOverview) {
                return [];
            }
            var testers = [];
            var _hours = 0;
            for (var i in data.assigneesOverview) {
                if (data.assigneesOverview.hasOwnProperty(i)) {
                    _hours = 0;
                    var t = {
                        "completedCases": 0,
                        "username": i,
                        "name": data.assigneesOverview[i].name,
                        "time": 0,
                        "prettyTime": ""
                    };
                    for (var k = 0, max3 = data.assigneesOverview[i].assignments.length; k < max3; k++) {
                        totalCounts.timeToComplete += data.assigneesOverview[i].assignments[k].totalTime;
                        t.time += data.assigneesOverview[i].assignments[k].totalTime;
                        totalCounts.passed += parseInt(data.assigneesOverview[i].assignments[k].passedCases, 10);
                        t.completedCases += parseInt(data.assigneesOverview[i].assignments[k].completedCases, 10);
                    }
                    _hours = Math.floor(t.time / 60);
                    t.prettyTime = (_hours > 0 ? (_hours + " hours and ") : "") + (t.time % 60) + " minutes";
                    testers.push(t);
                }
            }
            return testers;
        }

        /**
         * Iterates over all assignments to create statistics for tribes based on assignee and specification
         */
        function getTribesData(data) {
            if (!data.assigneesOverview) {
                return;
            }
            var assignments = data.assigneesOverview;
            var tribes = {};
            var _t;
            for (var i in assignments) {
                if (assignments.hasOwnProperty(i)) {
                    for (var j = 0, max2 = assignments[i].tribes.length; j < max2; j++) { // for each user's tribe
                        _t = assignments[i].tribes[j];
                        if (!tribes[_t.name]) {
                            tribes[_t.name] = new SynergyModels.TribeRunStats(_t.name, _t.id, tribesSpecs);
                        }
                        tribes[_t.name].addAssignment(assignments[i].assignments); // all assignments for given user
                    }
                }
            }
            return $scope.SYNERGY.util.toIndexedArray(tribes);
        }

        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
    }
    function AssignmentCommentsCtrl($scope, $routeParams, assignmentHttp, SynergyUtils) {
        $scope.id = $routeParams.id || -1;
        $scope.filters = {};
        $scope.isLoading = false;
        $scope.pageSize = $scope.SYNERGY.commentsPage;
        $scope.filter = {
            "case": "All",
            "specification": "All",
            "reporter": "All",
            "resolver": "All",
            "comment": "All",
            "status": "All"
        };
        $scope.duplicates = [];
        var duplicatesFull = [];
        var commentToBeResolved = {};
        $scope.fetch = function () {
            if ($scope.id < 0) {
                return;
            }
            assignmentHttp.getComments($scope, $scope.id, function (data) {
                $scope.data = data;
                collectFilters(data);
            }, $scope.generalHttpFactoryError);
        };

        $scope.nextPage = function () {
            $scope.isLoading = true;
            $scope.pageSize += $scope.SYNERGY.commentsPage;
            $scope.isLoading = false;
        };

        /**
         * Collects all distinct cases, specifications, reporters etc. to populate folter combo boxes
         */
        function collectFilters(data) {

            var cases = [];
            var specifications = [];
            var reporters = [];
            var comments = [];
            var status = [];
            var resolvers = [];

            for (var i = 0, max = data.comments.length; i < max; i++) {
                if (cases.indexOf(data.comments[i].caseTitle) < 0) {
                    cases.push(data.comments[i].caseTitle);
                }
                if (specifications.indexOf(data.comments[i].specificationTitle) < 0) {
                    specifications.push(data.comments[i].specificationTitle);
                }
                if (reporters.indexOf(data.comments[i].authorDisplayName) < 0) {
                    reporters.push(data.comments[i].authorDisplayName);
                }
                if (status.indexOf(data.comments[i].resolution) < 0) {
                    status.push(data.comments[i].resolution);
                }
                if (SynergyUtils.definedNotNull(data.comments[i].resolverDisplayName) && resolvers.indexOf(data.comments[i].resolverDisplayName) < 0) {
                    resolvers.push(data.comments[i].resolverDisplayName);
                }
                if (comments.indexOf(data.comments[i].commentText) < 0) {
                    comments.push(data.comments[i].commentText);
                }
            }

            var sortFnc = function (a, b) {
                a = a.toLowerCase();
                b = b.toLowerCase();
                if (a === b) {
                    return 0;
                }
                return a > b ? 1 : -1;
            };

            cases.sort(sortFnc);
            cases.push("All");
            specifications.sort(sortFnc);
            specifications.push("All");
            reporters.sort(sortFnc);
            reporters.push("All");
            comments.sort(sortFnc);
            comments.push("All");
            status.sort(sortFnc);
            status.push("All");
            resolvers.sort(sortFnc);
            resolvers.push("All");
            $scope.filters = {
                "cases": cases,
                "specifications": specifications,
                "reporters": reporters,
                "comments": comments,
                "status": status,
                "resolvers": resolvers
            };
        }

        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });

        $scope.commentFilter = function (comment) {
            if ($scope.filter["case"] && $scope.filter["case"] !== "All" && comment.caseTitle !== $scope.filter["case"]) {
                return false;
            }

            if ($scope.filter.specification && $scope.filter.specification !== "All" && comment.specificationTitle !== $scope.filter.specification) {
                return false;
            }
            if ($scope.filter.reporter && $scope.filter.reporter !== "All" && comment.authorDisplayName !== $scope.filter.reporter) {
                return false;
            }
            if ($scope.filter.comment && $scope.filter.comment !== "All" && comment.commentText !== $scope.filter.comment) {
                return false;
            }
            if ($scope.filter.status && $scope.filter.status !== "All" && comment.resolution !== $scope.filter.status) {
                return false;
            }
            if ($scope.filter.resolver && $scope.filter.resolver !== "All" && comment.resolverDisplayName !== $scope.filter.resolver) {
                return false;
            }
            return true;
        };

        /**
         * Resolves particular comment, if there are duplicate comments (same case id, suite id and comment type), offers to resolve them as well
         */
        $scope.resolveComment = function (comment) {
            commentToBeResolved = comment;
            $scope.duplicates = findSimilarComments(comment);
            duplicatesFull.push(comment);
            if ($scope.duplicates.length === 0) {
                $scope.resolveContinue(false);
            } else {
                $("#resolveModal").modal("toggle");
            }
        };

        /**
         * Returns similar comments for given comment (same case id, suite id and comment type)
         */
        function findSimilarComments(comment) {
            var similars = [];
            duplicatesFull = [];
            for (var i = 0, max = $scope.data.comments.length; i < max; i++) { // compare case_id, suite_id and comment type
                if ($scope.data.comments[i].caseId === comment.caseId &&
                        $scope.data.comments[i].id !== comment.id &&
                        $scope.data.comments[i].suiteId === comment.suiteId &&
                        $scope.data.comments[i].commentText === comment.commentText &&
                        $scope.data.comments[i].resolution === "new") {
                    duplicatesFull.push($scope.data.comments[i]);
                    similars.push({"id": $scope.data.comments[i].id});
                }
            }
            return similars;
        }

        $scope.resolveContinue = function (resolveDuplicates) {
            if (resolveDuplicates) {
                $scope.duplicates.push({"id": commentToBeResolved.id});
            }
            $scope.showWaitDialog();
            assignmentHttp.resolveComments($scope, (resolveDuplicates ? $scope.duplicates : [{"id": commentToBeResolved.id}]), function (data) {
                $scope.showWaitDialog();
                if (resolveDuplicates) {
                    for (var i = 0, max = duplicatesFull.length; i < max; i++) {
                        duplicatesFull[i].resolution = "resolved";
                        duplicatesFull[i].resolverUsername = $scope.SYNERGY.session.username;
                        duplicatesFull[i].resolverDisplayName = $scope.SYNERGY.session.firstName + " " + $scope.SYNERGY.session.lastName;
                    }
                    $("#resolveModal").modal("toggle");
                } else {
                    duplicatesFull[duplicatesFull.length - 1].resolution = "resolved";
                    duplicatesFull[duplicatesFull.length - 1].resolverUsername = $scope.SYNERGY.session.username;
                    duplicatesFull[duplicatesFull.length - 1].resolverDisplayName = $scope.SYNERGY.session.firstName + " " + $scope.SYNERGY.session.lastName;
                }

                $scope.SYNERGY.logger.log("Done", "Comments resolved", "INFO", "alert-success");
            }, function (data) {
                $scope.SYNERGY.modal.show();
                $scope.SYNERGY.logger.log("Action failed", data, "INFO", "alert-error");
                $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
            });

        };
    }
    function ReviewCtrl($scope, $routeParams, reviewHttp, SynergyUtils, SynergyModels) {
        function addListener() {
            if (window.addEventListener) {
                window.addEventListener("message", handleIframeSelection);
            } else {
                window.attachEvent("onmessage", handleIframeSelection);
            }
        }

        var iframe = window.document.getElementById("tutorialFrame");
        var action = $routeParams.action || "view";
        $scope.id = parseInt($routeParams.id, 10) || -1;
        $scope.review = {};
        $scope.currentComment = new SynergyModels.ReviewComment($scope.tutorialUrl);
        addListener();
        $scope.frameSrc = "";
        $scope.isEditing = false;
        $scope.commentAction = "Add";
        var oldCopy = null;
        var editingIndex = -1;
        var cachedData = null;
        var started = 0;
        var sentAs = -1;
        var pageLength = -1;

        function fetch() {
            if (cachedDataExists()) {
                $scope.sendCached(true);
            } else {
                reviewHttp.get($scope, $scope.id, action, function (data) {
                    $scope.review = data;
                    loadPage();
                }, $scope.generalHttpFactoryError);
            }
        }


        function removeListener() {
            if (window.addEventListener) {
                window.removeEventListener("message", handleIframeSelection);
            } else {
                window.detachEvent("onmessage", handleIframeSelection);
            }
        }

        function handleIframeSelection(msg) {
            var _data = msg.data;
            if (typeof _data === "string") {
                _data = JSON.parse(msg.data);
            }

            switch (_data.action) {
                case "added":
                    $scope.currentComment.addElement(_data.xpath, _data.element);
                    break;
                case "removed":
                    $scope.currentComment.removeElement(_data.xpath);
                    break;
                default: // wordCount
                    pageLength = parseInt(_data.words, 10);
                    break;
            }
            try {
                if (!$scope.$$phase) {
                    $scope.$apply();
                }
            } catch (e) {
            }
        }

        function loadPage() {
            $scope.frameSrc = "../../server/api/review.php?url=" + encodeURIComponent($scope.review.reviewUrl);
            window.document.getElementById("frameContainer").innerHTML = "<iframe data-ng-hide=\"frameSrc.length < 1\" id=\"tutorialFrame\" style=\"width: 100%; height: 1000px\" src=\"" + $scope.frameSrc + "\"></iframe>";
            started = new Date().getTime();
            iframe = window.document.getElementById("tutorialFrame");
        }

        function cachedDataExists() {
            cachedData = $scope.SYNERGY.cache.get("review_progress_" + $scope.id);
            return cachedData && cachedData.date && cachedData.progress ? true : false;
        }

        $scope.cancelEditing = function () {
            $scope.currentComment = new SynergyModels.ReviewComment($scope.review.reviewUrl);
            $scope.review.comments[editingIndex] = oldCopy;
            iframe.contentWindow.postMessage("{\"action\" : \"clear\"}", "*");
            $scope.isEditing = false;
            $scope.commentAction = "Add";
        };

        $scope.sendCached = function (send) {
            if (send) {
                $("#attemptModal").modal("toggle");
                $scope.review.comments = cachedData.progress;
                $scope.review.timeTaken = cachedData.timeTaken;
                $scope.review.weight = cachedData.weight;
                $scope.submit(sentAs);
            } else {
                $scope.SYNERGY.cache.clear("review_progress_" + $scope.id);
                fetch();
            }
        };

        /**
         * @param {Number} mode 1 means finished, 0 means paused
         */
        $scope.submit = function (mode) {
            if (sentAs === -1) { // assign only for the first time
                sentAs = mode;
            }
            mode = sentAs; // fallback to the first value prior any failures
            var assignment = new SynergyModels.ReviewAssignment($scope.SYNERGY.session.username, $scope.review.reviewUrl);
            assignment.comments = $scope.review.comments;
            assignment.id = $scope.id;
            assignment.testRunId = $scope.review.testRunId;
            assignment.isFinished = parseInt(mode, 10) === 1 ? true : false;
            assignment.timeTaken = new Date().getTime() - started;
            assignment.weight = pageLength;
            $scope.showWaitDialog();
            $scope.SYNERGY.cache.put("review_progress_" + $scope.id, {"date": new Date().toString(), "timeTaken": assignment.timeTaken, "weight": pageLength, "progress": assignment.comments});
            reviewHttp.submitResults($scope, $scope.id, assignment, function () {
                $scope.SYNERGY.modal.update("Results submitted", "Thank you for review");
                $scope.SYNERGY.modal.show();
                $scope.SYNERGY.cache.clear("review_progress_" + $scope.id);
                removeListener();
                $scope.review = {};
                $scope.failedAttempt = false;
                $scope.currentComment = new SynergyModels.ReviewComment($scope.tutorialUrl);
                window.history.back();
            }, function (data) {
                $scope.SYNERGY.modal.show();// hide wait dialog
                if (!$scope.failedAttempt) {
                    $scope.failedAttempt = true;
                    $("#deleteModal").modal("toggle");
                }
                $scope.SYNERGY.logger.log("Action failed", data, "INFO", "alert-error");
                $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
            });
        };

        $scope.addComment = function () {
            if (!$scope.isEditing) {
                $scope.review.comments.push($scope.currentComment);
            }
            $scope.isEditing = false;
            $scope.commentAction = "Add";
            $scope.SYNERGY.cache.put("review_progress_" + $scope.id, {"date": new Date().toString(), "timeTaken": new Date().getTime() - started, "weight": pageLength, "progress": $scope.review.comments});
            $scope.currentComment = new SynergyModels.ReviewComment($scope.review.reviewUrl);
            iframe.contentWindow.postMessage("{\"action\" : \"clear\"}", "*");
        };

        $scope.editComment = function (index) {
            oldCopy = SynergyUtils.shallowClone($scope.review.comments[index]);
            editingIndex = index;
            $scope.currentComment = $scope.review.comments[index];
            $scope.isEditing = true;
            $scope.commentAction = "Edit";
            iframe.contentWindow.postMessage(JSON.stringify({"action": "highlight", "elements": $scope.currentComment.elements}), "*");
        };

        $scope.viewComment = function (index) {
            iframe.contentWindow.postMessage(JSON.stringify({"action": "highlight", "elements": $scope.review.comments[index].elements}), "*");
        };

        $scope.removeComment = function (index) {
            $scope.review.comments.splice(index, 1);
            iframe.contentWindow.postMessage("{\"action\" : \"clear\"}", "*");
        };

        $scope.init(function () {
            fetch();
        });

    }
    function AdminReviewsCtrl($scope, reviewHttp) {

        $scope.existingReviewPages = [];
        $scope.importUrl = "http://wiki.netbeans.org/NetBeansTutorialsForCommunityReview";
        $scope.newReview = {
            title: "",
            owner: "",
            url: ""
        };


        function fetch() {
            if (!$scope.SYNERGY.session.hasAdminRights()) {
                return;
            }
            reviewHttp.list($scope, function (data) {
                $scope.existingReviewPages = data;
            }, $scope.generalHttpFactoryError);
        }

        $scope.importData = function () {
            reviewHttp.importFromUrl($scope, $scope.importUrl, function () {
                $scope.SYNERGY.logger.log("Done", "Data imported", "INFO", "alert-success");
                fetch();
            }, $scope.generalHttpFactoryError);
        };


        $scope.create = function () {
            reviewHttp.create($scope, $scope.newReview, function () {
                $scope.SYNERGY.logger.log("Done", "Review page created", "INFO", "alert-success");
                fetch();
            }, $scope.generalHttpFactoryError);
        };

        $scope.perform = function (action) {
            switch (action) {
                case "create":
                    $scope.newReview = {
                        title: "",
                        owner: "",
                        url: ""
                    };
                    $("#createTutorialModal").modal("toggle");
                    break;
                default :
                    break;
            }
        };


        $scope.init(function () {
            fetch();
        });
    }
    function AdminProjectsCtrl($scope, projectsHttp, projectHttp, $location, SynergyModels) {

        $scope.projects = [];
        var projectAffectedId = 0; // when editing version, modal dialog is opened, this is to show version name when typing new name
        $scope.newname = "";

        $scope.fetch = function () {
            if (!$scope.SYNERGY.session.hasAdminRights()) {
                return;
            }
            projectsHttp.getAll($scope, function (data) {
                $scope.projects = data;
            }, $scope.generalHttpFactoryError);
        };

        $scope.perform = function (action, projectId, projectName) {
            switch (action) {
                case "edit":
                    $location.path("administration/project/" + projectId + "/edit");
                    break;
                case "create":
                    $scope.newname = "";
                    $("#createVersionModal").modal("toggle");
                    break;
                case "delete":
                    projectAffectedId = projectId;
                    $("#deleteModal").modal("toggle");
                    break;
                default :
                    break;
            }
        };

        /**
         * Deletes version
         */
        $scope.remove = function () {
            $("#deleteModal").modal("toggle");
            projectHttp.remove($scope, projectAffectedId, function () {
                $scope.SYNERGY.logger.log("Done", "Project removed", "INFO", "alert-success");
                $scope.fetch();
            }, $scope.generalHttpFactoryError);
        };

        /**
         * Creates a new version
         */
        $scope.create = function () {
            projectHttp.create($scope, new SynergyModels.Version($scope.newname, -1, false), function (data) {
                $scope.SYNERGY.logger.log("Done", "Project created", "INFO", "alert-success");
                $scope.fetch();
            }, $scope.generalHttpFactoryError);
        };
        var self = $scope;
        $scope.init(function () {
            self.fetch();
        });
    }
    function RegisterCtrl($scope, registerHttp, SynergyModels) {

        $scope.username = "";
        $scope.email = "";
        $scope.firstname = "";
        $scope.lastname = "";
        $scope.password = "";
        $scope.password2 = "";

        $scope.register = function () {
            if (!$scope.registerForm.$valid || $scope.password !== $scope.password2) {
                $scope.SYNERGY.logger.log("Oops", "Seems like form is not properly filled", "INFO", "alert-danger");
                return;
            }

            var reg = new SynergyModels.Registration($scope.username, $scope.email, $scope.firstname, $scope.lastname);
            reg.password = $scope.password;
            registerHttp.doRegister($scope, reg, function () {
                window.history.back();
            }, $scope.generalHttpFactoryError);
        };

        $scope.init(function () {
        });
    }
    /**
     * 
     * @param {type} $scope
     * @param {SessionFct} sessionHttp
     * @returns {undefined}
     */
    function LoginCtrl($scope, sessionHttp, $location) {
        $scope.username = "";
        $scope.password = "";

        $scope.login = function () {
            if (!$scope.loginForm.$valid) {
                $scope.SYNERGY.logger.log("Oops", "Seems like form is not properly filled", "INFO", "alert-danger");
                return;
            }
            sessionHttp.login($scope, {username: $scope.username, password: $scope.password}, function () {
                window.history.back();
            }, function (data, status, headers, config) {
                $scope.SYNERGY.logger.log("Oops", "Wrong username or password", "INFO", "alert-danger");
            });
        };

        $scope.init(function () {
        });

    }

    function RecoverCtrl($scope, sessionHttp) {
        $scope.username = "";
        $scope.reset = function () {
            if (!$scope.resetForm.$valid) {
                $scope.SYNERGY.logger.log("Oops", "Seems like form is not properly filled", "INFO", "alert-danger");
                return;
            }
            sessionHttp.resetPassword($scope, $scope.username, function () {
                $scope.SYNERGY.logger.log("Done", "New password has been sent to you, check your email.", "INFO", "alert-success");
            }, function (data, status, headers, config) {
                $scope.SYNERGY.logger.log("Oops", data, "INFO", "alert-danger");
            });
        };

        $scope.init(function () {
        });
    }

    function AdminProjectCtrl($scope, projectHttp, $routeParams, SynergyModels) {
        $scope.project = {
            id: $routeParams.id
        };
        $scope.refreshCodemirror = false;

        function fetch() {
            projectHttp.get($scope, $scope.project.id, function (data) {
                $scope.project = data;
                $scope.refreshCodemirror = true;
            }, $scope.generalHttpFactoryError);
        }

        $scope.init(function () {
            fetch();
        });

        $scope.save = function () {
            var pr = new SynergyModels.Project($scope.project.id, $scope.project.name);
            pr.reportLink = $scope.project.reportLink;
            pr.viewLink = $scope.project.viewLink;
            pr.bugTrackingSystem = $scope.project.bugTrackingSystem;
            pr.multiViewLink = $scope.project.multiViewLink;
            projectHttp.edit($scope, pr, function () {
                $scope.SYNERGY.logger.log("Done", "Project updated", "INFO", "alert-success");
                fetch();
            }, $scope.generalHttpFactoryError);
        };

        $scope.cancel = function () {
            window.history.back();
        };

    }

    function RunCoverageCtrl($scope, $routeParams, versionsHttp, specificationsHttp, testRunCoverageHandler, versionHandler, runHttp, specificationLengthHttp) {

        $scope.allVersions = [];
        $scope.platforms = [];
        $scope.versionToCover = null;
        $scope.selectedVersions = [];
        $scope.specifications = [];
        $scope.syncCounter = 0;
        $scope.syncTarget = 3;
        $scope.testRunId = parseInt($routeParams.id, 10);
        $scope.testRunTitle = null;
        $scope.coverageTotals = null;
        $scope.totals = {
            finishedCases: 0,
            totalCases: 0
        };
        var allSpecifications = [];
        var run = null;
        var specificationSizeCache = {};

        $scope.addVersion = function () {
            var versionToCover = parseInt($scope.versionToCover, 10);
            for (var i = 0, max = $scope.selectedVersions.length; i < max; i++) {
                if ($scope.selectedVersions[i].id === $scope.allVersions[versionToCover].id) {
                    return;
                }
            }
            $scope.selectedVersions.push($scope.allVersions[versionToCover]);
            sortVersions();
            $scope.allVersions[versionToCover].isVisible = false;
        };

        $scope.getPercentage = function (finished, total) {
            if (total === 0 || typeof total === "undefined") {
                return new Number(0).toFixed(2);
            } else {
                return (finished / (total / 100)).toFixed(2);
            }
        };

        $scope.removeCoveredVersion = function ($index) {
            var removedVersion = $scope.selectedVersions.splice($index, 1)[0];
            $scope.allVersions[removedVersion.index].isVisible = true;
            sortVersions();
        };

        $scope.getProgressClass = function (finished, total) {
            if (finished === total && total > 0) { // 1/1
                return "finished";
            } else if (finished === total) { // 0/0
                return "warning";
            } else if (finished > 0) { // 2/4
                return "unfinished";
            } else {
                return "warning"; // 0/4
            }
        };

        $scope.loadCoverage = function () {
            var filteredSpecifications = testRunCoverageHandler.collectSpecificationsTotalsPerPlatform(filterSpecifications(), run.assignments, $scope.platforms, $scope.selectedVersions);
            loadSpecsWithoutAssignment(filteredSpecifications, function () {
                $scope.specifications = filteredSpecifications;
                $scope.coverageTotals = testRunCoverageHandler.getTotalRow($scope.specifications, $scope.platforms);
                $scope.specificationTotals = testRunCoverageHandler.getSpecificationTotalAcrossPlatforms($scope.specifications, $scope.platforms);
                $scope.totals = testRunCoverageHandler.getTotals($scope.specifications, $scope.platforms);
            });
        };

        $scope.isVisible = function (version) {
            return version.isVisible;
        };

        $scope.toggle = function () {
            $scope.coverageTotals = testRunCoverageHandler.getTotalRow($scope.specifications, $scope.platforms);
            $scope.specificationTotals = testRunCoverageHandler.getSpecificationTotalAcrossPlatforms($scope.specifications, $scope.platforms);
            $scope.totals = testRunCoverageHandler.getTotals($scope.specifications, $scope.platforms);
        };

        function loadSpecsWithoutAssignment(specifications, callback) {
            var query = testRunCoverageHandler.getEmptySpecifications(specifications, specificationSizeCache);
            if (query.ids.length > 0) {
                specificationLengthHttp.get($scope, query, function (data) {
                    for (var id in data) {
                        if (data.hasOwnProperty(id) && !specificationSizeCache.hasOwnProperty(id)) {
                            specificationSizeCache[id] = data[id];
                        }
                    }
                    testRunCoverageHandler.addCaseCounts(specifications, specificationSizeCache);
                    callback();
                }, $scope.generalHttpFactoryError);
            } else {
                testRunCoverageHandler.addCaseCounts(specifications, specificationSizeCache);
                callback();
            }
        }

        function sortVersions() {
            $scope.selectedVersions.sort(function (a, b) {
                if (a.floatValue > b.floatValue) {
                    return -1;
                }
                if (a.floatValue < b.floatValue) {
                    return 1;
                }
                return 0;
            });
        }

        function fetch() {
            loadVersions();
            loadSpecifications();
            loadRun(parsePlatforms);
        }

        function filterSpecifications() {
            var filteredSpecifications = [];
            var latestMatchingSpec;
            for (var i = 0, max = allSpecifications.length; i < max; i++) {
                latestMatchingSpec = testRunCoverageHandler.getLatestMatchingSpecification(allSpecifications[i], $scope.selectedVersions, $scope.platforms);
                if (latestMatchingSpec) {
                    filteredSpecifications.push(latestMatchingSpec);
                }
            }
            return filteredSpecifications;
        }


        function loadSpecifications() {
            specificationsHttp.get($scope, "all", function (data) {
                allSpecifications = data;
                $scope.syncCounter++;
            }, $scope.generalHttpFactoryError);
        }

        function loadRun(callback) {
            runHttp.get($scope, $scope.testRunId, function (data) {
                run = data;
                $scope.testRunTitle = run.title;
                $scope.$emit("updateBreadcrumbs", {link: "run/" + $scope.testRunId + "/coverage", title: run.title + " Coverage"});
                callback();
                $scope.syncCounter++;
            }, $scope.generalHttpFactoryError);
        }
        /**
         * Need to get platforms from assignments in case platform has been renamed
         * @returns {undefined}
         */
        function parsePlatforms() {
            var platformsObj = {};
            for (var i = 0, max = run.assignments.length; i < max; i++) {
                if (!platformsObj.hasOwnProperty(run.assignments[i].platform)) {
                    platformsObj[run.assignments[i].platform] = {
                        name: run.assignments[i].platform,
                        isVisible: true
                    };
                }
            }
            var platforms = [];
            var _p;
            var index = 0;
            for (var p in platformsObj) {
                _p = platformsObj[p];
                _p.index = index;
                index++;
                platforms.push(_p);
            }
            $scope.platforms = platforms;
        }

        function loadVersions() {
            versionsHttp.get($scope, true, function (data) {
                for (var i = 0, max = data.length; i < max; i++) {
                    data[i].isVisible = true;
                    data[i].index = i;
                    data[i].floatValue = versionHandler.getVersionFloatValue(data[i].name);
                }
                $scope.allVersions = data;
                $scope.syncCounter++;
            }, $scope.generalHttpFactoryError);
        }

        $scope.init(function () {
            fetch();
        });
    }

    angular.module("synergy.controllers", ["synergy.models", "synergy.utils", "synergy.models", "synergy.core"])
            .controller("SynergyCtrl", ["$scope", "$location", "sessionHttp", "$cookieStore", "$timeout", "ngProgress", "$templateCache", "searchHttp", "projectsHttp", "sessionService", "SynergyApp", "specificationCache", "SynergyUtils", "SynergyCore", SynergyCtrl])
            .controller("SearchCtrl", ["$scope", "$routeParams", "searchHttp", SearchCtrl])
            .controller("SpecPoolCtrl", ["$scope", "$routeParams", "specificationsHttp", "versionsHttp", SpecPoolCtrl])
            .controller("HomeCtrl", ["$scope", "specificationsHttp", "runsHttp", "calendarHttp", HomeCtrl])
            .controller("RunCtrl", ["$scope", "utils", "$location", "$routeParams", "runHttp", "assignmentHttp", "attachmentHttp", "reviewHttp", "SynergyUtils", "SynergyHandlers", "SynergyIssue", RunCtrl])
            .controller("RunCtrlCase", ["$scope", "utils", "$location", "$routeParams", "runHttp", "SynergyUtils", "SynergyHandlers", "SynergyIssue", RunCtrlCase])
            .controller("RunCtrlUser", ["$scope", "$location", "$routeParams", "runHttp", "assignmentHttp", "attachmentHttp", "reviewHttp", "SynergyUtils", "SynergyHandlers", "SynergyIssue", RunCtrlUser])
            .controller("SpecificationCtrl", ["$scope", "utils", "$location", "$routeParams", "versionsHttp", "specificationHttp", "suiteHttp", "attachmentHttp", "usersHttp", "jobHttp", "userHttp", "sanitizerHttp", "labelsHttp", "projectsHttp", "specificationCache", "SynergyUtils", "SynergyModels", "SynergyHandlers", SpecificationCtrl])
            .controller("SuiteCtrl", ["$scope", "utils", "$location", "$routeParams", "$timeout", "specificationHttp", "suiteHttp", "casesHttp", "productsHttp", "sanitizerHttp", "specificationCache", "SynergyModels", SuiteCtrl])
            .controller("CaseCtrl", ["$scope", "utils", "$location", "$routeParams", "suiteHttp", "caseHttp", "imageHttp", "issueHttp", "labelHttp", "sanitizerHttp", "specificationCache", "SynergyModels", "SynergyHandlers", CaseCtrl])
            .controller("ProfileCtrl", ["$scope", "$routeParams", "userHttp", "$location", "SynergyModels", "SynergyHandlers", ProfileCtrl])
            .controller("LabelFilterCtrl", ["$scope", "$routeParams", "labelHttp", LabelFilterCtrl])
            .controller("TribeCtrl", ["$scope", "$location", "$routeParams", "$timeout", "usersHttp", "tribeHttp", "sanitizerHttp", "specificationsHttp", "SynergyUtils", "SynergyModels", TribeCtrl])
            .controller("RunsCtrl", ["$scope", "$routeParams", "runsHttp", RunsCtrl])
            .controller("AssignmentCtrl", ["$scope", "$routeParams", "assignmentHttp", AssignmentCtrl])
            .controller("AdminHomeCtrl", ["$scope", AdminHomeCtrl])
            .controller("AdminVersionCtrl", ["$scope", "versionsHttp", "versionHttp", "SynergyModels", AdminVersionCtrl])
            .controller("AdminPlatformsCtrl", ["$scope", "platformsHttp", "platformHttp", "SynergyModels", AdminPlatformsCtrl])
            .controller("AdminTribesCtrl", ["$scope", "$location", "$timeout", "usersHttp", "tribeHttp", "tribesHttp", "sanitizerHttp", "SynergyUtils", "SynergyModels", AdminTribesCtrl])
            .controller("AdminRunsCtrl", ["$scope", "$location", "$routeParams", "runsHttp", "runHttp", AdminRunsCtrl])
            .controller("AdminRunCtrl", ["$scope", "$routeParams", "runHttp", "$location", "attachmentHttp", "sanitizerHttp", "projectsHttp", "SynergyModels", "SynergyHandlers", AdminRunCtrl])
            .controller("AdminMatrixAssignmentCtrl", ["$scope", "$routeParams", "assignmentsHttp", "usersHttp", "labelsHttp", "platformsHttp", "tribesHttp", "versionsHttp", "SynergyUtils", AdminMatrixAssignmentCtrl])
            .controller("AdminAssignmentCtrl", ["$scope", "$timeout", "$routeParams", "specificationsHttp", "assignmentsHttp", "usersHttp", "labelsHttp", "platformsHttp", "tribesHttp", "versionsHttp", "assignmentHttp", "runHttp", "SynergyUtils", "SynergyModels", AdminAssignmentCtrl])
            .controller("AdminUsersCtrl", ["$scope", "$location", "$routeParams", "userHttp", "usersHttp", AdminUsersCtrl])
            .controller("AdminUserCtrl", ["$scope", "$routeParams", "userHttp", "SynergyModels", AdminUserCtrl])
            .controller("AdminSettingCtrl", ["$scope", "settingsHttp", AdminSettingCtrl])
            .controller("AboutCtrl", ["$scope", "aboutHttp", AboutCtrl])
            .controller("TribesCtrl", ["$scope", "$location", "$timeout", "tribesHttp", TribesCtrl])
            .controller("AdminLogCtrl", ["$scope", "logHttp", "sessionRenewalHttp", AdminLogCtrl])
            .controller("AdminDatabaseCtrl", ["$scope", "databaseHttp", AdminDatabaseCtrl])
            .controller("AssignmentVolunteerCtrl", ["$scope", "$routeParams", "specificationsHttp", "assignmentHttp", "labelsHttp", "platformsHttp", "versionsHttp", "reviewHttp", "runHttp", "SynergyUtils", "SynergyModels", AssignmentVolunteerCtrl])
            .controller("RevisionCtrl", ["$scope", "$routeParams", "revisionsHttp", RevisionCtrl])
            .controller("CalendarCtrl", ["$scope", "calendarHttp", CalendarCtrl])
            .controller("AssignmentTribeCtrl", ["$scope", "$routeParams", "tribesHttp", "labelsHttp", "platformsHttp", "assignmentsHttp", "assignmentHttp", "SynergyModels", AssignmentTribeCtrl])
            .controller("StatisticsCtrl", ["$scope", "statisticsHttp", "$routeParams", "SynergyUtils", "SynergyModels", "SynergyHandlers", "SynergyIssue", StatisticsCtrl])
            .controller("AssignmentCommentsCtrl", ["$scope", "$routeParams", "assignmentHttp", "SynergyUtils", AssignmentCommentsCtrl])
            .controller("ReviewCtrl", ["$scope", "$routeParams", "reviewHttp", "SynergyUtils", "SynergyModels", ReviewCtrl])
            .controller("AdminReviewsCtrl", ["$scope", "reviewHttp", AdminReviewsCtrl])
            .controller("AdminProjectsCtrl", ["$scope", "projectsHttp", "projectHttp", "$location", "SynergyModels", AdminProjectsCtrl])
            .controller("RegisterCtrl", ["$scope", "registerHttp", "SynergyModels", RegisterCtrl])
            .controller("LoginCtrl", ["$scope", "sessionHttp", "$location", LoginCtrl])
            .controller("RecoverCtrl", ["$scope", "sessionHttp", RecoverCtrl])
            .controller("RunCoverageCtrl", ["$scope", "$routeParams", "versionsHttp", "specificationsHttp", "TestRunCoverageHandler", "VersionHandler", "runHttp", "specificationLengthHttp", RunCoverageCtrl])
            .controller("AdminProjectCtrl", ["$scope", "projectHttp", "$routeParams", "SynergyModels", AdminProjectCtrl]);
})();