AJS.toInit(function($) {
    $(".tasklist-container").each(function() {
        var tasklistContainer = this;
        var tasklistId = tasklistContainer.id.substr("tasklist-".length);
        $(".task-item", tasklistContainer).each(function () {
           var taskItem = $(this);
 
           dtl.onListLoad(tasklistId, function(list) {
                    new Task(
                        list,
                        $(".task-id", taskItem).text(),
                        $(".task-name", taskItem).text(),
                        $(".task-priority", taskItem).text(),
                        $(".task-createdDate", taskItem).text(),
                        $(".task-completed", taskItem).text() === "true",
                        $(".task-completedDate", taskItem).text(),
                        $(".task-locked", taskItem).text() === "true",
                        $(".task-assginee", taskItem).text()
                    );
                });
        });
        dtl.registerTaskListEx(tasklistId);
        dtl.lists[tasklistId].updateMarkAllIncompleteButton();
    });
});

TaskList = Class.create();
TaskList.prototype = {

    initialize: function(listId, readonly, lockingEnabled, sortBy, sortAscending, entityId) {
        this.id = listId;
        this.readonly = readonly;
        this.lockingEnabled = lockingEnabled;
        this.sortBy = sortBy;
        this.sortAscending = sortAscending;
        this.tasks = [];
        this.entityId = entityId;

        if (!readonly) {
            Event.observe(this._getAddNewTaskButton(), "click", function(event) {
                this.addTask();
                Event.stop(event);
            }.bind(this));
            
            Event.observe(this._getNewTaskField(), "keypress", function(event) {
                this.addTaskKeypress(event);
            }.bind(this));
            
            Event.observe(this._getSortSelector(), "change", function(event) {
                this.updateSortBy();
            }.bind(this));
            
            if (this._getSortDirectionButton()) {
                // if the list isn"t sorted, there will be no sortDirection element
                Event.observe(this._getSortDirectionButton(), "click", function(event) {
                    this.updateSort(this.sortBy, !this.sortAscending);
                }.bind(this));
            }
            
            if (this._getMarkAllIncompleteButton()) {
                // if no tasks are complete, the button won"t exist
                Event.observe(this._getMarkAllIncompleteButton(), "click", function(event) {
                    this.markAllIncomplete();
                }.bind(this));
            }

            this.makeListSortable();
        }
    },
        
    addTaskKeypress: function(event) {
        event = event || window.event;

        if (dtl.isKey(Event.KEY_RETURN, event)) {
            this.addTask();
            Event.stop(event);
        }
    },

    addTask: function() {
        var newTask = this._getNewTaskField();
        var taskName = dtl.trim(newTask.value);
        if (taskName.length == 0) {
            dtl.onError(listId, "Task name cannot be blank", null);
        }

        // TASK-137
        this.disableTaskAdding();

        var uri = dtl.getUri({
            action: "addTask",
            listId: this.id,
            entityId: this.entityId,
            newTask: taskName
        });
        new Ajax.Request(uri, {
            method: "post",
            onSuccess: function(data) {
                var taskContainer = this._getTaskListTaskContainer();
                var childElements = taskContainer.getElementsByTagName("div"); 
                var taskId = data.responseXML.getElementsByTagName("id")[0].firstChild.nodeValue;
                var name = data.responseXML.getElementsByTagName("name")[0].firstChild.nodeValue;
                var priority = data.responseXML.getElementsByTagName("priority")[0].firstChild.nodeValue;
                var createdDate = data.responseXML.getElementsByTagName("createdDate")[0].firstChild.nodeValue;
                var completed = data.responseXML.getElementsByTagName("completed")[0].firstChild.nodeValue == "true";
                var completedDate = data.responseXML.getElementsByTagName("completedDate")[0].firstChild.nodeValue;
                var locked = data.responseXML.getElementsByTagName("locked")[0].firstChild.nodeValue == "true";
                
                var assignee = data.responseXML.getElementsByTagName("assignee")[0].firstChild
                        ? data.responseXML.getElementsByTagName("assignee")[0].firstChild.nodeValue : '';


                var tmpElement = document.createElement("div");
                tmpElement.innerHTML = data.responseXML.getElementsByTagName("representation")[0].firstChild.nodeValue;
                taskContainer.appendChild(dtl.firstDescendant(tmpElement));

                var task = new Task(this, taskId, name, priority, createdDate, completed, completedDate, locked, assignee);

                this._sortList();
                task.highlight();

                newTask.value = "";

                this.enableTaskAdding(); // TASK-137
                Field.activate(newTask);
                this.updateProgressBar();
                this.makeListSortable();
            }.bind(this),
            onFailure: function(data) {
                this.enableTaskAdding(); // TASK-137
                dtl.onError(this.id, null, data);
            }
        });
    },

    markAllIncomplete: function() {
        var uri = dtl.getUri({
            action: "markAllIncomplete",
            listId: this.id,
            entityId: this.entityId
        });
        new Ajax.Request(uri, {
            method: "post",
            onSuccess: function(data) {
                $H(this.tasks).each(function(pair) {
                    pair.value.markIncomplete();
                }.bind(this));
                this.hideMarkAllIncompleteButton();
                this.updateProgressBar();
            }.bind(this),
            onFailure: function(data) {
                dtl.onError(this.id, null, data);
            }
        });
    },

    updateSortBy: function() {
        var selectedSort = this._getSelectedSort();
        var ascending = selectedSort == "priority" ? false : true;
        this.updateSort(selectedSort, ascending);
    },

    updateSort: function(sortBy, ascending) {
        if (this.sortBy == sortBy && this.sortAscending == ascending) {
            return;
        }
        this.sortBy = sortBy;
        this.sortAscending = ascending;
        this._sort();
    },

    _sort: function() {
        var action;
        if (this.sortBy) {
            action = "sortBy" + this.sortBy.substring(0, 1).toUpperCase() + this.sortBy.substring(1, this.sortBy.length);
        } else {
            action = "sortByNone";
        }
            
        var uri = dtl.getUri({
            action: action,
            listId: this.id,
            entityId: this.entityId
        });
        new Ajax.Request(uri, {
            method: "post",
            onSuccess: function(data) {
                if (this.sortBy) {
                    this._sortList();
                }
                this._updateSortDirectionButton();
            }.bind(this),
            onFailure: function(data) {
                dtl.onError(this.id, null, data);
            }
        });
    },
    
    _sortList: function() {
        if (!this.sortBy) {
            return;
        }
        var sorter = function(lhs, rhs) {
            return (this.sortAscending ? 1 : -1) * dtl["task" + this.sortBy.substring(0, 1).toUpperCase() + this.sortBy.substring(1, this.sortBy.length) + "Comparator"](lhs, rhs);
        }.bind(this);
        var tasks = $H(this.tasks).values();
        tasks.sort(sorter);
        tasks.each(function(task) {
            var hook = task._getTaskHook();
            var parent = hook.parentNode;
            Element.remove(hook);
            parent.appendChild(hook);
        });
    },
    
    _updateSortDirectionButton: function() {
        var button = this._getSortDirectionButton();
        if (this.sortBy) {
            if (this.sortAscending) {
                dtl.removeClassName(button, "desc");        
                dtl.addClassName(button, "asc");
            } else {
                dtl.removeClassName(button, "asc");
                dtl.addClassName(button, "desc");
            }
        } else {
            dtl.removeClassName(button, "desc");        
            dtl.removeClassName(button, "asc");
        }
    },

    copyTasks: function(taskId, fromListId, fromEntityId, index, dstEntityId) {
        var uri = dtl.getUri({
            action: "copyTasks",
            listId: fromListId,
            taskId: taskId,
            toListId: this.id,
            index: index,
            entityId: fromEntityId,
            destinationEntityId: dstEntityId
        });
        new Ajax.Request(uri, {
            method: "post",
            onSuccess: function(data) {
                /* Only allow copy from list to another list */
                if (this.id != fromListId) {
                    var taskContainer = dtl.lists[fromListId]._getTaskListTaskContainer();
                    var childElements = taskContainer.getElementsByTagName("div");
                    var idOfTaskAfterSourceTask = data.responseXML.getElementsByTagName("idOfTaskAfterSourceTask")[0].firstChild
                            ? data.responseXML.getElementsByTagName("idOfTaskAfterSourceTask")[0].firstChild.nodeValue
                            : null;
                    var newTaskId = data.responseXML.getElementsByTagName("newTaskId")[0].firstChild.nodeValue;
                    var taskId = data.responseXML.getElementsByTagName("id")[0].firstChild.nodeValue;
                    var name = data.responseXML.getElementsByTagName("name")[0].firstChild.nodeValue;
                    var priority = data.responseXML.getElementsByTagName("priority")[0].firstChild.nodeValue;
                    var createdDate = data.responseXML.getElementsByTagName("createdDate")[0].firstChild.nodeValue;
                    var completed = data.responseXML.getElementsByTagName("completed")[0].firstChild.nodeValue == "true";
                    var completedDate = data.responseXML.getElementsByTagName("completedDate")[0].firstChild.nodeValue;
                    var locked = data.responseXML.getElementsByTagName("locked")[0].firstChild.nodeValue == "true";

                    var assignee = data.responseXML.getElementsByTagName("assignee")[0].firstChild
                            ? data.responseXML.getElementsByTagName("assignee")[0].firstChild.nodeValue : '';

                    var tmpElement = document.createElement("div");
                    
                    tmpElement.innerHTML = data.responseXML.getElementsByTagName("representation")[0].firstChild.nodeValue;

                    var fromList = dtl.lists[fromListId];
                    var task = fromList.tasks[taskId];

                    // Move old task to new list
                    task.updateTaskId(newTaskId, this);
                    this.updateProgressBar();
                    this.updateMarkAllIncompleteButton();

                    // Delay the execution of makeListSortable because it will cause problems if executed in here.
                    window.setTimeout(this.makeListSortable.bind(this), 500);
                    
                    // Recreate old task
                    if (!idOfTaskAfterSourceTask) {
                        taskContainer.appendChild(dtl.firstDescendant(tmpElement));
                    } else {
                        taskContainer.insertBefore(dtl.firstDescendant(tmpElement), $(idOfTaskAfterSourceTask + "_hook"));
                    }

                    new Task(fromList, taskId, name, priority, createdDate, completed, completedDate, locked, assignee);
                    fromList.updateProgressBar();
                    fromList.makeListSortable();
                }
            }.bind(this),
            onFailure: function(data) {
                dtl.onError(fromListId, null, data);
            }
        });
    },
    
    reorderTasks: function(taskId, fromListId, fromEntityId, index, dstEntityId) {
        var uri = dtl.getUri({
            action: "reorderTasks",
            listId: fromListId,
            taskId: taskId,
            toListId: this.id,
            index: index,
            entityId: fromEntityId,
            destinationEntityId: dstEntityId
        });
        new Ajax.Request(uri, {
            method: "post",
            onSuccess: function(data) {
                if (this.id != fromListId) {
                    var fromList = dtl.lists[fromListId];
                    var task = fromList.tasks[taskId];

                    // update task id
                    var newTaskId = data.responseXML.getElementsByTagName("id")[0].firstChild.nodeValue;
                    task.updateTaskId(newTaskId, this);

                    this.updateProgressBar();
                    fromList.updateProgressBar();
                    this.updateMarkAllIncompleteButton();
                    fromList.updateMarkAllIncompleteButton();
                }
            }.bind(this),
            onFailure: function(data) {
                dtl.onError(fromListId, null, data);
                // should put the moved task back where it was, but how?
            }
        });
    },

    onListReorder: function(list) {
        if (list.ttlChangedTaskId) {
            if (!dtl.ctrlHeld)
                this.reorderTasks(list.ttlChangedTaskId, list.ttlFromListId, list.ttlFromEntityId, list.ttlTaskIndex, list.entityId);
            else
                this.copyTasks(list.ttlChangedTaskId, list.ttlFromListId, list.ttlFromEntityId, list.ttlTaskIndex, list.entityId);
            
            list.ttlChangedTaskId = undefined;
            list.ttlFromListId = undefined;
            list.ttlTaskIndex = undefined;
        }
        return false;
    },

    onTaskOrderChange: function(task) {
        var toList = task.parentNode;

        if (toList) {
            var child = toList.firstChild;
            var index = 0;
            
            while (child) {
                if (child == task) {
                    var changedTaskId = task.id.substr(0, task.id.indexOf("_hook"));
                    var changedListId = dtl.getListId(changedTaskId);
                    var changedList = dtl.lists[changedListId];

                    toList.ttlChangedTaskId = changedTaskId;
                    toList.ttlFromListId = dtl.getListId(toList.ttlChangedTaskId);
                    toList.ttlFromEntityId = changedList.entityId;
                    toList.ttlTaskIndex = index;

                    // if the list we're moving to is different than the one the task came from,
                    // we need to make sure the information from previous onTaskOrderChange calls
                    // on the originating task list are wiped out, or else an extra update call 
                    // could be made to the server
                    if (dtl.getListIdForContainer(toList) != toList.ttlFromListId) {
                        var fromList = dtl.lists[toList.ttlFromListId]._getTaskListTaskContainer();
                        fromList.ttlChangedTaskId = undefined;
                        fromList.ttlFromListId = undefined;
                        fromList.ttlFromEntityId = undefined;
                        fromList.ttlTaskIndex = undefined;
                    }
                    break;
                }
                if (child.tagName.toLowerCase() == "li") {
                    index++;
                }
                child = child.nextSibling;
            }
        }
    },

    toggleConfig: function(configMode) {
        if (configMode) {
            return this._doListAction("configure", listId);
        } else {
            // @todo
            alert("Not Yet Implemented");
        }
    },

    unlockAll: function() {
        // @todo
        alert("Not Yet Implemented");
        return false;
    },

    saveConfig: function() {
        // @todo
        alert("Not Yet Implemented");
        return false;
    },

    cancelConfig: function() {
        // @todo
        alert("Not Yet Implemented");
        return false;
    },
    
    showMarkAllIncompleteButton: function() {
        var hook = this._getListHook();
        if (Element.hasClassName(hook, "none-complete")) {
            dtl.removeClassName(hook, "none-complete");
        }
    },
    
    hideMarkAllIncompleteButton: function() {
        var hook = this._getListHook();
        if (!Element.hasClassName(hook, "none-complete")) {
            dtl.addClassName(hook, "none-complete");
        }
    },
    
    updateMarkAllIncompleteButton: function() {
        var atLeastOneComplete = false;
        var hasLockedTask = false;

        $H(this.tasks).each(function(pair) {
            atLeastOneComplete = atLeastOneComplete || pair.value.completed;
            hasLockedTask = hasLockedTask || pair.value.locked;
        }.bind(this));
        if (atLeastOneComplete && (!this.lockingEnabled || !hasLockedTask)) {
            this.showMarkAllIncompleteButton();
        } else {
            this.hideMarkAllIncompleteButton();
        }
    },
    
    // TASK-137
    enableTaskAdding: function() {
        var buttons = document.getElementsByTagName("button");

		for(var i = 0; i < buttons.length; i++) {
            if(Element.hasClassName(buttons[i], "add-button")) {
                buttons[i].disabled = false;
            }
        }
        
        var taskname_text = document.getElementsByTagName("input");

        for (var j = 0; j < taskname_text.length; j++) {
            if(Element.hasClassName(taskname_text[j], "taskname-text")) {
                taskname_text[j].disabled = false;
            }
        }
    },
    
    // TASK-137
    disableTaskAdding: function() {
        var buttons = document.getElementsByTagName("button");

        for(var i = 0; i < buttons.length; i++) {
            if(Element.hasClassName(buttons[i], "add-button")) {
                buttons[i].disabled = true;
            }
        }

        var taskname_text = document.getElementsByTagName("input");

        for (var j = 0; j < taskname_text.length; j++) {
            if(Element.hasClassName(taskname_text[j], "taskname-text")) {
                taskname_text[j].disabled = true;
            }
        }
    },

    updateProgressBar: function() {
        var percentComplete = this._getPercentComplete();
        var completeElement = this._getPercentCompleteElement();
        
        completeElement.style.width = percentComplete + "%";
    },

    makeListSortable: function() {
        var taskListContainers = dtl._getAllTaskListContainers();
        var opts = {
            tag: "li",
            handle: "handle",
            containment: taskListContainers,
            constraint:false,
            ghosting: false,
            dropOnEmpty: true,
            scroll: window,
            onUpdate: this.onListReorder.bind(this),
            onChange: this.onTaskOrderChange.bind(this)
        };
    
        Sortable.create(this._getTaskListTaskContainer(), opts);
    },
    
    taskStatusChanged: function(task) {
        this.updateProgressBar();
        this.updateMarkAllIncompleteButton();
        if (this.sortBy == "completed") {
            this._sortList();
        }
    },
    
    taskNameChanged: function(task) {
        if (this.sortBy == "name") {
            this._sortList();
        }
    },
    
    taskPriorityChanged: function(task) {
        if (this.sortBy == "priority") {
            this._sortList();
        }
    },
    
    taskAssigneeChanged: function(task) {
        if (this.sortBy == "assignee") {
            this._sortList();
        }
    },

    // getters for list dom elements
    _getListHook: function() {
        if (dtl.isUndefined(this.listHook)) {
            this.listHook = $(this.id + "_hook");
        }
        return this.listHook;
    },
    
    _getNewTaskField: function() {
        if (dtl.isUndefined(this.newTaskField)) {
            var form = dtl.getFirstElementWithClassName(this._getListHook(), "quick-add", "form");
            this.newTaskField = form.taskname;
        }
        return this.newTaskField;
    },

    _getAddNewTaskButton: function() {
        if (dtl.isUndefined(this.addNewTaskButton)) {
            var form = dtl.getFirstElementWithClassName(this._getListHook(), "quick-add", "form");
            this.addNewTaskButton = form.getElementsByTagName("button")[0];
        }
        return this.addNewTaskButton;
    },
    
    _getTaskListTaskContainer: function() {
        if (dtl.isUndefined(this.taskContainer)) {
            this.taskContainer = this._getListHook().getElementsByTagName("ol")[0];
            this.taskContainer.entityId = this.entityId;
        }
        return this.taskContainer;
    },
    
    _getMarkAllIncompleteButton: function() {
        if (dtl.isUndefined(this.markAllIncompleteButton)) {
            this.markAllIncompleteButton = dtl.getFirstElementWithClassName(this._getListHook(), "uncheck-all", "button");
        }
        return this.markAllIncompleteButton;
    },
    
    _getSortSelector: function() {
        if (dtl.isUndefined(this.sortSelector)) {
            this.sortSelector = dtl.getFirstElementWithClassName(this._getListHook(), "sort-select", "select");
        }
        return this.sortSelector;
    },
    
    _getSelectedSort: function() {
        return Form.Element.getValue(this._getSortSelector());
    },
    
    _getSortDirectionButton: function() {
        if (dtl.isUndefined(this.sortDirectionButton)) {
            this.sortDirectionButton = dtl.getFirstElementWithClassName(this._getListHook(), "sort-order", "button");
        }
        return this.sortDirectionButton;
    },

    _getSortDirectionButtonImg: function() {
        return dtl.firstDescendant(this._getSortDirectionButton());
    },
    
    _getPercentComplete: function() {
        var numComplete = 0;
        var numTasks = 0;
        $H(this.tasks).each(function(pair) {
            numTasks++;
            if (pair.value.completed) {
                numComplete++;
            }
        });
        if (numTasks == 0) {
            return 0;
        }
        return numComplete / numTasks * 100;
    },
    
    _getPercentCompleteElement: function() {
        if (dtl.isUndefined(this.percentCompleteElement)) {
            this.percentCompleteElement = dtl.firstDescendant(dtl.getFirstElementWithClassName(this._getListHook(), "progress", "div"));
        }
        return this.percentCompleteElement;
    }    
};

Task = Class.create();
Task.prototype = {
    
    initialize: function(list, taskId, name, priority, createdDate, completed, completedDate, locked, assignee) {
        this.list = list;
        this.id = taskId;
        this.name = name;
        this.priority = priority;
        this.createdDate = createdDate;
        this.completed = completed;
        this.completedDate = completedDate;
        this.locked = locked;
        this.assignee = assignee;

        list.tasks[taskId] = this;

        this._connectTasklineEventHandlers();
        this._connectTaskInfoEventHandlers();
    },

    reassignTaskKeypress: function(event) {
        event = event || window.event;

        if (dtl.isKey(Event.KEY_RETURN, event)) {
            this.reassignTask(true);
            Event.stop(event);
        }
    },

    reassignTask: function(async) {
        var assignee = this._getTaskAssigneeField().value;
        if (this.assignee) {
            if (dtl.trim(this.assignee) == dtl.trim(assignee)) {
                return;
            }            
        }
        
        var uri = dtl.getUri({
            action: "reassignTask",
            listId: this.list.id,
            taskId: this.id,
            assignee: dtl.trim(assignee),
            entityId: this.list.entityId
        });
        var request = new Ajax.Request(uri, {
            method: "post",
            onSuccess: function(data) {
                // what to do, what to do?
                this.assignee = assignee;
                this.list.taskAssigneeChanged(this);
            }.bind(this),
            onFailure: function(data) {
                this._getTaskAssigneeField().value = this.assignee;
                dtl.onError(this.list.id, null, data);
            }.bind(this),
            asynchronous: async
        });
        if (!async) {
            request.onStateChange();
        }
    },

    // called by popup window when user finishes a user/group search.
    // calls reassignTask with the async option set to false because we can't allow the window to close while making the
    // request.
    setAssigneeForTask: function(assignee) {
        var element = this._getTaskAssigneeField();
        if (assignee) {
            if (!element.value) {
                element.value = assignee;
            } else {
                element.value = element.value + ", " + assignee;
            }
            this.reassignTask(false);
        }
    },

    removeTask: function(promptOnDelete) {
        var remove = true;

        if (promptOnDelete) {
            remove = confirm("Delete '" + this.name + "'?");
        }

        if (remove) {
            var uri = dtl.getUri({
                action: "removeTask",
                listId: this.list.id,
                taskId: this.id,
                entityId: this.list.entityId
            });
            new Ajax.Request(uri, {
                method: "post",
                onSuccess: function(data) {
                    var taskHook = this._getTaskHook();
                    Effect.DropOut(taskHook, {
                        afterFinish: function() {
                            Element.remove(taskHook);
                            delete this.list.tasks[this.id];
                            this.list.updateProgressBar();
                            this.list.updateMarkAllIncompleteButton();
                        }.bind(this)
                    });
                }.bind(this),
                onFailure: function(data) {
                    dtl.onError(this.list.id, null, data);
                }
            });
        }

        return false;
    },

    toggleTaskStatus: function() {
        var uri = dtl.getUri({
            action: "toggleTaskStatus",
            listId: this.list.id,
            taskId: this.id,
            entityId : this.list.entityId
        });
        var checkbox = this._getTaskCompleteCheckbox();
        new Ajax.Request(uri, {
            method: "post",
            onSuccess: function(data) {
                this.completed = !this.completed;
                this.completedDate = data.responseXML.getElementsByTagName("completedDate")[0].firstChild.nodeValue;
                this.renderedCompletedDate = data.responseXML.getElementsByTagName("renderedCompletedDate")[0].firstChild.nodeValue;

                this._getTaskAssigneeField().value = data.responseXML.getElementsByTagName("assignee")[0].firstChild.nodeValue

                this._updateTaskStatusDisplay();
                this.list.taskStatusChanged(this);
            }.bind(this),
            onFailure: function(data) {
                checkbox.checked = !checkbox.checked;
                dtl.onError(this.list.id, null, data);
            }.bind(this)
        });
    },
    
    markIncomplete: function() {
        this._getTaskCompleteCheckbox().checked = false;
        this.completed = false;
        this._updateTaskStatusDisplay();
    },
    
    toggleTaskInfo: function() {
    	var taskHook = this._getTaskHook();
    	if (Element.hasClassName(taskHook, "opened")) {
    		dtl.removeClassName(taskHook, "opened");
    		dtl.addClassName(taskHook, "closed");
    	} else {
            dtl.removeClassName(taskHook, "closed");
            dtl.addClassName(taskHook, "opened");
    	}
    },

    toggleTaskLock: function() {
        var uri = dtl.getUri({
            action: "toggleTaskLock",
            listId: this.list.id,
            taskId: this.id,
            entityId: this.list.entityId
        });
        new Ajax.Request(uri, {
            method: "post",
            onSuccess: function(data) {
                var wasLocked = this.locked;
                this.locked = !this.locked;
                var hook = this._getTaskHook();
                if (wasLocked) {
                    dtl.removeClassName(hook, "locked");
                    dtl.addClassName(hook, "unlocked");
                } else {
                    dtl.removeClassName(hook, "unlocked");
                    dtl.addClassName(hook, "locked");
                }

                this.list.updateMarkAllIncompleteButton();
            }.bind(this),
            onFailure: function(data) {
                dtl.onError(this.list.id, null, data);
            }.bind(this)
        });
    },

    changeTaskPriority: function(priority) {
        var uri = dtl.getUri({
            action: "changeTaskPriority",
            listId: this.list.id,
            taskId: this.id,
            priority: priority,
            entityId: this.list.entityId
        });
        new Ajax.Request(uri, {
            method: "post",
            onSuccess: function(data) {
            	var oldPriority = this.priority;
                this.priority = priority;
                dtl.removeClassName(this._getTaskHook(), oldPriority.toLowerCase());
                dtl.addClassName(this._getTaskHook(), priority.toLowerCase());
                this.list.taskPriorityChanged(this);
                
                // IE has some weird issue where the right radio button isn't selected so we need to do it manually
                if (priority == 'HIGH') {
                    this._getTaskHighPriorityButton().checked = true;
                } else if (priority == 'MEDIUM') {
                    this._getTaskMediumPriorityButton().checked = true;
                } else {
                    this._getTaskLowPriorityButton().checked = true;
                }
            }.bind(this),
            onFailure: function(data) {
                dtl.onError(this.list.id, null, data);
            }.bind(this)
        });
    },

    _editingTask: null,

    editTask: function() {
        if (!this._editingTask)
        {
            this._openTaskEditor();

            var editBox = this._getEditTaskField();
            var saveButton = this._getSaveTaskButton();
            
            var thisTask = this;
            var handlers = {
                _stopObserving: function() {
                    Event.stopObserving(editBox, "blur", handlers.save);
                    Event.stopObserving(editBox, "keypress", handlers.keypress);
                    Event.stopObserving(saveButton, "click", handlers.click);
                },
                
                save: function(event) {
                    handlers._stopObserving();
                    this._updateTask();
                    Event.stop(event);
                }.bind(thisTask),
                
                keypress: function(event) {
                    if (dtl.isKey(Event.KEY_RETURN, event)) {
                        handlers._stopObserving();
                        this._updateTask();
                        Event.stop(event);
                    } else if (dtl.isKey(Event.KEY_ESC, event)) {
                        handlers._stopObserving();
                        this._cancelEditing();
                        Event.stop(event);
                    }
                }.bind(thisTask),
                
                click: function(event) {
                    handlers._stopObserving();
                    this._updateTask();
                    Event.stop(event);
                }
            };

            Event.observe(editBox, "keypress", handlers.keypress);
            Event.observe(editBox, "blur", handlers.save);
            Event.observe(saveButton, "click", handlers.click);
            
            editBox.focus();
            editBox.select();
        }
    },

    _actions: [ "edit", "delete" ],
    
    showTaskActions: function() {
        dtl.addClassName(dtl.firstDescendant(this._getTaskHook()), "hover");
    },
    
    hideTaskActions: function(taskId) {
        dtl.removeClassName(dtl.firstDescendant(this._getTaskHook()), "hover");
    },
    
    highlight: function() {
    	new Effect.Highlight(this._getTaskHook(), { duration: 5 });
    },
    
    // Event handler hookups
    _connectTasklineEventHandlers: function() {
        Event.observe(this._getToggleTaskInfoButton(), "click", function(event) {
            this.toggleTaskInfo();
            Event.stop(event);
        }.bind(this));
        
        if (!this.list.readonly) {
            var taskContainer = this._getTaskHook();
            Event.observe(taskContainer, "mouseover", function(event) {
                if (!this.locked) {
                    this.showTaskActions();
                }
            }.bind(this));
            
            Event.observe(taskContainer, "mouseout", function(event) {
                if (!event) {
                    return;
                }
                this.hideTaskActions();
            }.bind(this));

            var checkbox = this._getTaskCompleteCheckbox();
            if (!checkbox.readonly) {
                Event.observe(checkbox, "click", function(event) {
                    if (this.locked) {
                        Event.stop(event);
                    } else {
                        this.toggleTaskStatus();
                    }
                }.bind(this));
            }
            
            Event.observe(this._getEditTaskButton(), "click", function(event) {
                this.editTask();
                Event.stop(event);
            }.bind(this));
            
            Event.observe(this._getDeleteTaskButton(), "click", function(event) {
                this.removeTask(true);
                Event.stop(event);
            }.bind(this));

            if (this.list.lockingEnabled) {
                var lockTaskButton = this._getLockTaskButton();
                if (lockTaskButton) {
                    Event.observe(lockTaskButton, "click", function(event) {
                        this.toggleTaskLock();
                        Event.stop(event);
                    }.bind(this));
                }
            }
        }
    },
    
    _connectTaskInfoEventHandlers: function() {
        if (!this.list.readonly) {
            Event.observe(this._getTaskHighPriorityButton(), "click", function(event) {
                this.changeTaskPriority('HIGH');
            }.bind(this));
            
            Event.observe(this._getTaskMediumPriorityButton(), "click", function(event) {
                this.changeTaskPriority('MEDIUM');
            }.bind(this));
    
            Event.observe(this._getTaskLowPriorityButton(), "click", function(event) {
                this.changeTaskPriority('LOW');
            }.bind(this));

            this._connectTaskAssigneeEventHandlers();
        }
    },
    
    _connectTaskAssigneeEventHandlers: function() {
        var assigneeField = this._getTaskAssigneeField();
        if (!assigneeField) {
        	return;
        }
        
        Event.observe(assigneeField, "click", function(event) {
            assigneeField.select();
        }.bind(this));
        
        Event.observe(assigneeField, "blur", function(event) {
            this.reassignTask(true);
        }.bind(this));
        
        Event.observe(assigneeField, "keypress", function(event) {
            this.reassignTaskKeypress(event);            
        }.bind(this));
        
        
        // setup function that the user and group picker use as callback
        var assignFunctionName = "set" + this.id.replace(":", "_") + "Assignee";
        if (!self[assignFunctionName]) {
            self[assignFunctionName] = function(entityNames) {
                this.setAssigneeForTask(entityNames);
            }.bind(this);
        }

        Event.observe(this._getTaskUserSearchButton(), "click", function(event) {
            var searchAction = "openuserpicker.action";
            var url = dtl.baseUri + "/spaces/" + searchAction + "?startIndex=0&existingUsers=" + this.assignee + "&onPopupSubmit=" + assignFunctionName;
            var windowName = "UserPicker";
            var windowProperties = "status=yes,resizable=yes,top=100,left=200,width=580,height=550,scrollbars=yes";
            var picker = window.open(url, windowName, windowProperties);
            picker.focus(); 
            Event.stop(event);
        }.bind(this));
        
        Event.observe(this._getTaskGroupSearchButton(), "click", function(event) {
            var searchAction = "opengrouppicker.action";
            var actionName = "dosearchgroups.action";
            var url = dtl.baseUri + "/spaces/" + searchAction + "?startIndex=0&actionName=" + actionName + "&existingGroups=" + this.assignee + "&onPopupSubmit=" + assignFunctionName;
            var windowName = "GroupPicker";
            var windowProperties = "status=yes,resizable=yes,top=100,left=200,width=580,height=550,scrollbars=yes";
            var picker = window.open(url, windowName, windowProperties);
            picker.focus(); 
            Event.stop(event);
        }.bind(this));
    },

    // editing helper methods
    _closeTaskEditor: function() {
        this._getTaskEditor().style.display = "none";
        this._getRenderedTaskNameContainer().style.display = "block";
        dtl.removeClassName(this._getTaskHook(), "editing");
        this._editingTask = false;
    },
    
    _openTaskEditor: function() {
        this._getRenderedTaskNameContainer().style.display = "none";
        this._getTaskEditor().style.display = "block";
        dtl.addClassName(this._getTaskHook(), "editing");
        this._editingTask = true;
    },

    _cancelEditing: function(originalValue) {
        this._getEditTaskField().value = this.name;
        this._closeTaskEditor();
    },
    
    _updateTask: function() {
        var newName = dtl.trim(this._getEditTaskField().value);

        if (!newName || newName.length == 0 || newName == this.name) {
            this._cancelEditing();
            return;
        }

        var uri = dtl.getUri({
            action: "editTask",
            listId: this.list.id,
            taskId: this.id,
            newTask: newName,
            entityId: this.list.entityId
        });
        new Ajax.Request(uri, {
            method: "post",
            onSuccess: function(data) {
                // get the rendered text of the task name and show it
                var renderedTaskName = data.responseXML.getElementsByTagName("renderedName")[0].firstChild.nodeValue;
                this._getRenderedTaskNameContainer().innerHTML = renderedTaskName;
                this._closeTaskEditor();

                // get the tasks new id and update element ids
                var newTaskId = data.responseXML.getElementsByTagName("id")[0].firstChild.nodeValue;
                this.updateTaskId(newTaskId);
                this.name = newName;
                
                this.list.taskNameChanged(this);
            }.bind(this),
            onFailure: function(data) {
                this._cancelEditing();
                dtl.onError(this.list.id, null, data);
            }.bind(this)
        });
    },
    
    _updateTaskStatusDisplay: function() {
        var hook = this._getTaskHook();
        var completedLabel = dtl.getFirstElementWithClassName(hook, "task-completed-label", "dt");
        var completedDate = dtl.getFirstElementWithClassName(hook, "task-completed-date", "dd"); 

        if (!this.completed) {
            completedLabel.style.display = 'none';
            completedDate.style.display = 'none';
            dtl.removeClassName(hook, "completed");
        } else {
            completedLabel.style.display = '';

            completedDate.innerHTML = this.renderedCompletedDate;
            completedDate.style.display = '';
            dtl.addClassName(hook, "completed");
        }
    },
    
    updateTaskId: function(newTaskId, newList) {
        if (!newList) {
            newList = this.list;
        }
        
        var hook = this._getTaskHook();
        var oldTaskId = this.id;
        
        delete this.list.tasks[oldTaskId];
        newList.tasks[newTaskId] = this;
        
        this.id = newTaskId;
        this.list = newList;
        hook.id = newTaskId + "_hook";
    },
    
    // getters for task dom elements
    _getTaskHook: function() {
        if (dtl.isUndefined(this.taskHook)) {
            this.taskHook = $(this.id + "_hook");
        }
        return this.taskHook;
    },
    
    _getToggleTaskInfoButton: function() {
        if (dtl.isUndefined(this.toggleTaskInfoButton)) {
            this.toggleTaskInfoButton = dtl.getFirstElementWithClassName(this._getTaskHook(), "trigger", "button");
        }
        return this.toggleTaskInfoButton;
    },

    _getTaskCompleteCheckbox: function() {
        if (dtl.isUndefined(this.taskCompleteCheckbox)) {
            this.taskCompleteCheckbox = dtl.getFirstElementWithClassName(this._getTaskHook(), "complete", "input");
        }
        return this.taskCompleteCheckbox;
    },
    
    _getRenderedTaskNameContainer: function() {
        if (dtl.isUndefined(this.renderedTaskNameContainer)) {
            this.renderedTaskNameContainer =  dtl.getFirstElementWithClassName(this._getTaskHook(), "rendered", "p");
        }
        return this.renderedTaskNameContainer;
    },
    
    _getTaskEditor: function() {
        if (dtl.isUndefined(this.taskEditor)) {
            this.taskEditor = dtl.getFirstElementWithClassName(this._getTaskHook(), "editable", "p");
        }
        return this.taskEditor;
    },
    
    _getEditTaskField: function() {
        return dtl.firstDescendant(this._getTaskEditor());
    },
    
    _getTaskActionButton: function(action) {
        if (dtl.isUndefined(this.actionButtons)) {
            this.actionButtons = [];
        }
        if (dtl.isUndefined(this.actionButtons[action])) {
            this.actionButtons[action] = dtl.getFirstElementWithClassName(this._getTaskHook(), action, "button");
        }
        return this.actionButtons[action];
    },
    
    _getDeleteTaskButton: function() {
        return this._getTaskActionButton("delete");
    },
    
    _getEditTaskButton: function() {
        return this._getTaskActionButton("edit");
    },

    _getSaveTaskButton: function() {
        return this._getTaskActionButton("save");
    },

    _getTaskInfo: function() {
        if (dtl.isUndefined(this.taskInfo)) {
            this.taskInfo = dtl.getFirstElementWithClassName(this._getTaskHook(), "additional-info", "dl");
        }
        return this.taskInfo;
    },
    
    _getTaskHighPriorityButton: function() {
        if (dtl.isUndefined(this.taskHighPriorityButton)) {
            this.taskHighPriorityButton = dtl.getFirstElementWithClassName(this._getTaskHook(), "high-priority", "input");
        }
        return this.taskHighPriorityButton;
    },
    
    _getTaskMediumPriorityButton: function() {
        if (dtl.isUndefined(this.taskMediumPriorityButton)) {
            this.taskMediumPriorityButton = dtl.getFirstElementWithClassName(this._getTaskHook(), "medium-priority", "input");
        }
        return this.taskMediumPriorityButton;
    },
    
    _getTaskLowPriorityButton: function() {
        if (dtl.isUndefined(this.taskLowPriorityButton)) {
            this.taskLowPriorityButton = dtl.getFirstElementWithClassName(this._getTaskHook(), "low-priority", "input");
        }
        return this.taskLowPriorityButton;
    },
    
    _getTaskAssigneeField: function() {
        if (dtl.isUndefined(this.taskAssigneeEditBox)) {
            this.taskAssigneeEditBox = dtl.getFirstElementWithClassName(this._getTaskHook(), "assignee", "input");
        }
        return this.taskAssigneeEditBox;
    },
    
    _getTaskUserSearchButton: function() {
        if (dtl.isUndefined(this.userSearchButton)) {
            this.userSearchButton = dtl.getFirstElementWithClassName(this._getTaskHook(), "user-search", "button");
        }
        return this.userSearchButton;
    },
    
    _getTaskGroupSearchButton: function() {
        if (dtl.isUndefined(this.groupSearchButton)) {
            this.groupSearchButton = dtl.getFirstElementWithClassName(this._getTaskHook(), "group-search", "button");
        }
        return this.groupSearchButton;
    },
    
    _getLockTaskButton: function() {
        if (dtl.isUndefined(this.lockTaskButton)) {
            this.lockTaskButton = dtl.getFirstElementWithClassName(this._getTaskHook(), "locker", "button");
        }
        return this.lockTaskButton;
    }
};

var dtl = {

    log: true,
    baseUri: null,
    lists: [],
    loaders: [],
    ctrlHeld: false,
    ctrlListenersAdded: false,
	
    // 17 is the control key. Unfortunately, we don't have a symbol for it. See
    // http://www.prototypejs.org/api/event
    KEY_CONTROL: 17,
    
    ensurePositionFix: function() {
        if (!Position.ieFixApplied) {
            // Fix a script.aculo.us problem under IE
            Position.cumulativeOffset = function(element) {
                var valueT = 0, valueL = 0;

                do {
                    valueT += element.offsetTop || 0;
                    valueL += element.offsetLeft || 0;

                    if (typeof element.offsetParent == "undefined" || typeof element.offsetParent == "unknown") {
                        break;
                    }

                    element = element.offsetParent;
                } while (element);

                return [valueL, valueT];
            };

            Position.ieFixApplied = true;
        }
    },
    
    registerList: function(list) {
        this.lists[list.id] = list;
        this._notifyListLoaded(list);
    },
    
    onListLoad: function(listId, loader) {
        if (!this.loaders[listId]) {
            this.loaders[listId] = [];
        } 
        var loaders = this.loaders[listId];
        loaders[loaders.length] = loader;
    },
    
    _notifyListLoaded: function(list) {
        if (this.loaders[list.id]) {
            this.loaders[list.id].each(function(loader) {
                loader(list);
            });
        }
    },

    getListId: function(taskId) {
        return taskId.split("_")[0];
    },

    _getAllTaskListContainers: function() {
        if (!this.taskListContainers) {
            this.taskListContainers = new Array();
            var candidates = document.getElementsByTagName("ol");
    
            for (var i = 0; i < candidates.length; i++) {
                if (Element.hasClassName(candidates[i].parentNode, "task-list")) {
                    this.taskListContainers.push(candidates[i]);
                }
            }
        }
        return this.taskListContainers;
    },
    
    getListIdForContainer: function(container) {
        return container.parentNode.id.substring(0, container.parentNode.id.indexOf("_hook"));
    },

    // basic helper methods
    log: function(msg) {
        if (typeof console != "undefined" && typeof console.log != "undefined" && console && console.log && dtl.log) {
            console.log("LOG: " + msg);
        }
    },

    isKey: function(key, event) {
        return ((event.which && event.which == key) || (event.keyCode && event.keyCode == key));
    },

    trim: function(string) {
        return string.replace(/^\s+|\s+$/g, "");
    },
    
    getUri: function(params) {
        var h = $H(params);
        return this.baseUri + "/plugins/servlet/tasklist?" + h.toQueryString();
    },
    
    taskNameComparator: function(lhs, rhs) {
        var numberRe = /^(\d+).*/;
        
        var lname = dtl._stripBasicMarkup(lhs.name.toLowerCase());
        var rname = dtl._stripBasicMarkup(rhs.name.toLowerCase());
        
        if (numberRe.test(lname) && numberRe.test(rname)) {
            var lnum = parseInt(numberRe.exec(lname)[1]);
            var rnum = parseInt(numberRe.exec(rname)[1]);
            if (lnum < rnum) {
                return -1;
            } else if (lnum > rnum) {
                return 1;
            }
        }
        
        if (lname < rname) {
            return -1;
        } else if (lname > rname) {
            return 1;
        } else {
            return 0;
        }
    },
    
    _stripBasicMarkup: function(content) {
        content = content.replace(/h[0-9]\./, " ", "g"); // headings
        content = content.replace(/\[.*\/\/\/.*\]/, "", "g"); // system links
        content = content.replace(/[\[\]\*_\^\-\~\+]/, "", "g"); // basic formatting
        content = content.replace(/\|/, " ", "g"); // table breaks
        content = content.replace(/\{([^:\}\{]+)(?::([^\}\{]*))?\}(?!\})/, " ", "g"); // macros
        content = content.replace(/\n/, " ", "g");
        content = content.replace(/\r/, " ", "g");
        content = content.replace(/bq\./, " ", "g");
        content = content.replace("  ", " ", "g");
        return content;
    },

    taskDateComparator: function(lhs, rhs) {
        var rval = lhs.createdDate - rhs.createdDate;
        return rval == 0 ? dtl.taskNameComparator(lhs, rhs) : rval;
    },
    
    taskCompletedComparator: function(lhs, rhs) {
        var rval = lhs.completed == rhs.completed ? 0 : (lhs.completed ? 1 : -1);
        if (rval != 0) {
            return rval;
        }
        return lhs.completedDate - rhs.completedDate;
    },

    taskAssigneeComparator: function(lhs, rhs) {
        var lassignee = lhs.assignee.toLowerCase();
        var rassignee = rhs.assignee.toLowerCase();
        if (lassignee < rassignee) {
            return -1;
        } else if (lassignee > rassignee) {
            return 1;
        } else {
            return dtl.taskNameComparator(lhs, rhs);
        }
    },

    taskPriorityComparator: function(lhs, rhs) {
        var lpriority = lhs.priority == 'HIGH' ? 1 : (lhs.priority == 'MEDIUM' ? 0 : -1);
        var rpriority = rhs.priority == 'HIGH' ? 1 : (rhs.priority == 'MEDIUM' ? 0 : -1);
        var rval = lpriority - rpriority;
        return rval == 0 ? dtl.taskNameComparator(lhs, rhs) : rval;
    },
    
    // our generic error handler
    onError: function(listId, ex, data) {
        var errorMessage = "An unexpected error occurred performing the operation.  Please retry and if you continue to get this message contact your administrator.";
        if (data) {
            var root = data.responseXML.firstChild;
            if ((root.tagName == "div" || root.tagName == "DIV") && root.getAttribute("class") == "dtl-error") {
                var errorDivs = root.getElementsByTagName("div");
                if (errorDivs.length == 1) {
                    errorMessage = errorDivs[0].firstChild.nodeValue;
                } else {
                    errorMessage = "The following errors occurred:\n"
                    for (var i = 0; i < errorDivs.length; i++) {
                        errorMessage += "  " + errorDivs[i].firstChild.nodeValue + "\n";
                    }
                }
            }
        }
        else if (ex) {
            errorMessage = "An error was encountered: " + ex;
        }
        alert(errorMessage);
    },
    
    // some prototype 1.5+ methods that make dom traversal easier
    isUndefined: function(obj) {
        return typeof obj == "undefined";
    },
    
    getFirstElementWithClassName: function(element, className, tag) {
        if (dtl.isUndefined(tag)) {
            tag = '*';
        }
        var children = $(element).getElementsByTagName(tag);
        return $A(children).find(function(child) {
            return Element.hasClassName(child, className)
        });
    },

    getElementsWithClassName: function(element, className) {
        var children = $(element).getElementsByTagName('*');
        return $A(children).select(function(child) {
            return Element.hasClassName(child, className)
        });
    },
    
    firstDescendant: function(element) {
        element = $(element).firstChild;
        while (element && element.nodeType != 1) {
            element = element.nextSibling;
        }
        return $(element);
    },
    
    nextElementSibling: function(node) {
      while (node = node.nextSibling) {
          if (node.nodeType == 1) {
              return node;
          }
      }
      return null;
    },
    
    removeClassName: function(element, classNameToRemove) {
    	var classNames = new Element.ClassNames(element).select(function(className) {
    		return className != classNameToRemove;
    	});
    	element.className = classNames.toArray().join(' ');
    },
    
    addClassName: function(element, className) {
    	var classNames = new Element.ClassNames(element);
    	element.className = classNames.toArray().concat(className).join(' ');
    },

    registerTaskListEx: function(taskListId)
    {
        var taskListReadOnly;
        var taskListEnableLocking;
        var taskListSort;
        var taskListSortAscending;
        var entityId = $(taskListId + '_entityId').value;

        this.baseUri = $(taskListId + '_baseUri').value;

        if (entityId == 0)
            return;

        taskListReadOnly = $(taskListId + '_readOnly').value;
        taskListEnableLocking = $(taskListId + '_enableLocking').value;
        taskListSort = $(taskListId + '_sort').value;
        taskListSortAscending = $(taskListId + '_sortAscending').value;

        this.registerList(new TaskList(taskListId, taskListReadOnly, taskListEnableLocking, taskListSort, taskListSortAscending, entityId));
        this.ensurePositionFix();

        if (!this.ctrlListenersAdded) {
            Event.observe(document, 'keydown', function(event) {
                if (dtl.isKey(dtl.KEY_CONTROL, event)) {
                    dtl.ctrlHeld = true;
                }
            });

            Event.observe(document, 'keyup', function(event) {
                if (dtl.isKey(dtl.KEY_CONTROL, event)) {
                    dtl.ctrlHeld = false;
                }
            });
            
            this.ctrlListenersAdded = true;
        }
		
        // TASK-127 - http://developer.atlassian.com/jira/browse/TASK-127
        // Due to the javascript in Confluence on page load, it'll search the whole page and focus the first text field that is visible and not disabled.
        // That will make the page to scroll down to the tasklist if the page is long, a hack is needed to avoid the text field being focus on page load.
        // First, setting the form with a unique Id and name the form with 'inlinecommentform' will make the script skip that form.
        // Next, the function below will overwrite the page's onload method and executed.
        // Lastly, unsetting the form name so that a page will not contain 2 elements with 'inlinecommentform' name.
        var newPlaceFocusFunction = function() {
            var currentOnloadFunction = window.onload;

            if (typeof window.onload == 'function') {
                currentOnloadFunction();
            }

            var form_Id = $(taskListId + '_addform');
            form_Id.name = '';
        }

        window.onload = newPlaceFocusFunction();
    }
};
