(function()
{
    var moduleName = 'claromentis.project';

    angular.module(moduleName)
        .service('tasksService', tasksService);

    tasksService.$inject = ['$rootScope', '$http', 'growl.service', 'projectPreloadFactory', 'TaskSection', 'Task'];
    function tasksService($rootScope, $http, growlService, preload, TaskSection, Task) {
        var service = this;

        // Constant to mean 'unsorted' at the end of the list
        const DISPLAY_ORDER_NONE = 1000000;

        const STATUS_ID_TODO = -1;
        const STATUS_ID_COMPLETED = -2;

        const TASK_ENDPOINT = '/api/projects/v3/task/{task_id}';
        const TASK_ADD_ENDPOINT = '/api/projects/v3/task/add';
        const STATUS_ENDPOINT = '/api/projects/v3/{project_id}/statuses';
        const LIST_ENDPOINT = '/api/projects/v3/task/section';

        service.tasksUpdating = false;

        service.getTaskEndpoint = function(task_id)
        {
            return TASK_ENDPOINT.replace(/({task_id})/g, task_id);
        }

        service.getTaskAddEndpoint = function()
        {
            return TASK_ADD_ENDPOINT;
        }

        service.getStatusEndpoint = function(project_id)
        {
            return STATUS_ENDPOINT.replace(/({project_id})/g, project_id);
        }

        service.getListEndpoint = function()
        {
            return LIST_ENDPOINT;
        }

        service.perms = preload.perms;
        service.project_id = preload.id;
        service.lists = [];
        for (var section_index = 0; section_index < preload.lists.length; section_index++)
        {
            var section = new TaskSection();
            section.loadFromArray(preload.lists[section_index]);
            service.lists.push(section);
        }

        service.tasks = [];
        for (var task_index=0; task_index < preload.tasks.length; task_index++)
        {
            var task = new Task();
            task.loadFromArray(preload.tasks[task_index]);

            task.project_id = project_id;
            service.tasks.push(task);
        }

        service.statuses = [];
        for (var statusIndex = 0; statusIndex < preload.statuses.length; statusIndex++)
        {
            service.statuses.push(preload.statuses[statusIndex]);
        }

        // ID to use for new statuses, decrements each time one is added
        service.nextFreeStatusId = -10;

        service.canEditStatuses = function()
        {
            return service.perms.can_edit_project;
        };

        service.canEditTasks = function()
        {
            return service.perms.can_edit_any_task;
        };

        service.collapseList = function(listId)
        {
            for (var sectionIndex = 0; sectionIndex < service.lists.length; sectionIndex++)
            {
                if (service.lists[sectionIndex].id === listId)
                    service.lists[sectionIndex].expanded = false;
            }
        };

        service.expandList = function(listId)
        {
            for (var sectionIndex = 0; sectionIndex < service.lists.length; sectionIndex++)
            {
                if (service.lists[sectionIndex].id === listId)
                    service.lists[sectionIndex].expanded = true;
            }
        };

        service.saveStatus = function(status)
        {
            var data = {
                name: status.localized_name,
                sort_order: status.sort_order
            };

            var promise = null;
            var url = '';
            if (status.id < STATUS_ID_COMPLETED)
            {
                url = service.getStatusEndpoint(service.project_id);
                promise = $http.post(url, data);
            } else
            {
                url = service.getStatusEndpoint(service.project_id) + '/' + status.id;
                promise = $http.put(url, data);
            }

            service.tasksUpdating = true;
            promise.then(function(){
                service.tasksUpdating = false;
            }, function(){
                service.tasksUpdating = false;
            });
            return promise;
        };

        service.renameStatus = function(statusId, newName)
        {
            var oldName = '';
            var status = {};
            for (var statusIndex = 0; statusIndex < service.statuses.length; statusIndex++)
            {
                if (service.statuses[statusIndex].id === statusId)
                {
                    oldName = service.statuses[statusIndex].localized_name;
                    service.statuses[statusIndex].localized_name = newName;
                    status = service.statuses[statusIndex];
                }
            }

            // Don't do anything more if there was no change made
            if (newName === oldName)
                return;

            service.saveStatus(status).then(
                function(){
                    growlService.showSuccess(lmsg('projects.task.status.updated', newName));
                    $rootScope.$broadcast('task_list_data.updated');
                },
                function(data){
                    status.localized_name = oldName
                    growlService.showError(lmsg('common.general.error'), data.statusText);
                }
            );
        };

        service.addStatusBefore = function(sortOrder)
        {
            var name = lmsg('projects.task.status.default_name');
            var newStatus = {
                id: service.nextFreeStatusId,
                localized_name: name,
                name: name,
                sort_order: sortOrder,
                isNew: true
            };

            service.saveStatus(newStatus).then(
                function(data) {
                    service.statuses.length = 0;
                    for (var i = 0; i < data.data.statuses.length; i++)
                    {
                        var status = data.data.statuses[i];
                        if (status.id === data.data.status.id)
                            status.isNew = true;

                        service.statuses.push(status);
                    }

                    service.nextFreeStatusId--;
                    growlService.showSuccess(lmsg('projects.task.status.added', name));
                    $rootScope.$broadcast('task_list_data.updated');
                },
                function(data){
                    growlService.showError(lmsg('common.general.error'), data.statusText);
                }
            );

            $rootScope.$broadcast('task_list_data.updated');
        }

        service.addStatusAfter = function(sortOrder)
        {
            var name = lmsg('projects.task.status.default_name');
            var newStatus = {
                id: service.nextFreeStatusId,
                localized_name: name,
                name: name,
                sort_order: sortOrder + 1,
                isNew: true
            };

            service.saveStatus(newStatus).then(
                function(data){
                    service.statuses.length = 0;
                    for (var i = 0; i < data.data.statuses.length; i++)
                    {
                        var status = data.data.statuses[i];
                        if (status.id === data.data.status.id)
                            status.isNew = true;

                        service.statuses.push(status);
                    }

                    service.nextFreeStatusId--;
                    growlService.showSuccess(lmsg('projects.task.status.added', name));
                    $rootScope.$broadcast('task_list_data.updated');
                },
                function(data){
                    growlService.showError(lmsg('common.general.error'), data.statusText);
                }
            );
        };

        service.deleteStatus = function(status)
        {
            var url = service.getStatusEndpoint(service.project_id) + '/' + status.id;
            service.tasksUpdating = true;
            $http.delete(url).then(
                function(data) {
                    service.statuses.length = 0;
                    for (var i = 0; i < data.data.statuses.length; i++)
                    {
                        service.statuses.push(data.data.statuses[i]);
                    }
                    service.tasksUpdating = false;
                    growlService.showSuccess(lmsg('projects.task.status.deleted', status.localized_name));
                    $rootScope.$broadcast('task_list_data.updated');
                },
                function(data) {
                    service.tasksUpdating = false;
                    growlService.showError(lmsg('common.general.error'), data.statusText);
                }
            );
        };

        service.updateStatusOrder = function(statusId, newOrder)
        {
            var status = {
                id: 0,
                localized_name: '',
                sort_order: newOrder
            };
            for (var statusIndex = 0; statusIndex < service.statuses.length; statusIndex++)
            {
                if (service.statuses[statusIndex].id === statusId)
                {
                    status.id = service.statuses[statusIndex].id;
                    status.localized_name = service.statuses[statusIndex].localized_name;
                }
            }

            service.saveStatus(status).then(
                function(data) {
                    service.statuses.length = 0;
                    for (var i = 0; i < data.data.statuses.length; i++)
                    {
                        service.statuses.push(data.data.statuses[i]);
                    }

                    $rootScope.$broadcast('task_list_data.updated');
                },
                function(data) {
                    // Irrelevant change to force watches to fire and rebuild local data using the original
                    // data, undoing the drag+drop
                    service.statuses[0].sort_order += 0.001;

                    $rootScope.$broadcast('task_list_data.updated');
                    growlService.showError(lmsg('common.general.error'), data.statusText);
                }
            );
        };

        service.saveList = function(list)
        {
            var data = {
                title: list.title,
                display_order: list.display_order,
                colour: list.colour,
                project_id: list.project_id
            };

            var promise = null;
            var url = '';
            if (list.id <= 0)
            {
                url = service.getListEndpoint(service.project_id) + '/add';
                promise = $http.post(url, data);
            } else
            {
                url = service.getListEndpoint(service.project_id) + '/' + list.id;
                promise = $http.put(url, data);
            }

            service.tasksUpdating = true;
            promise.then(function(){
                service.tasksUpdating = false;
            }, function(){
                service.tasksUpdating = false;
            });
            return promise;
        };

        service.editList = function(data)
        {
            return new Promise(function(resolve, reject) {
                var oldName = '';
                var oldColour = '';
                var list = {};

                if (data.id === 0)
                {
                    list = {
                        id: 0,
                        title: data.name,
                        colour: data.colour,
                        project_id: service.project_id,
                        display_order: service.lists.length
                    };
                } else
                {
                    for (var listIndex = 0; listIndex < service.lists.length; listIndex++)
                    {
                        if (service.lists[listIndex].id == data.id)
                        {
                            oldName = service.lists[listIndex].title;
                            oldColour = service.lists[listIndex].colour;
                            service.lists[listIndex].title = data.name;
                            service.lists[listIndex].colour = data.colour;
                            list = service.lists[listIndex];
                        }
                    }
                }

                // Don't do anything more if there was no change made
                if ((data.name === oldName) &&
                    (data.colour === oldColour))
                {
                    resolve();
                    return null;
                }

                var newName = data.name;

                service.saveList(list).then(
                    function(data){
                        growlService.showSuccess(lmsg('projects.task.list.updated', newName));

                        if (list.id === 0)
                        {
                            list = new TaskSection();
                            list.id = data.data.id;
                            list.colour = data.data.colour;
                            list.project_id = data.data.project_id;
                            list.display_order = data.data.display_order;
                            list.title = data.data.title;

                            service.lists.push(list);
                        }

                        $rootScope.$broadcast('task_list_data.updated');
                        resolve();
                    },
                    function(data){
                        list.title = oldName
                        list.colour = oldColour;
                        growlService.showError(lmsg('common.general.error'), data.data.error);

                        reject();
                    }
                );
            });
        };

        service.deleteList = function(list)
        {
            service.tasksUpdating = true;
            var url = service.getListEndpoint(service.project_id) + '/' + list.listId;
            $http.delete(url).then(
                function(data) {
                    var oldSections = service.lists;
                    service.lists.length = 0;
                    for (var key in data.data.sections)
                    {
                        if (data.data.sections.hasOwnProperty(key))
                        {
                            var sectionData = data.data.sections[key];
                            var section = null;

                            // Copy from existing data where possible to retain temporary state (collapsed, etc)
                            for (var j = 0; j < oldSections.length; j++)
                            {
                                if (sectionData.id === oldSections[j].id)
                                    section = oldSections[j];
                            }
                            if (section === null)
                                section = new TaskSection();

                            section.loadFromArray(sectionData);
                            service.lists.push(section);
                        }
                    }

                    service.tasksUpdating = false;
                    growlService.showSuccess(lmsg('projects.task.status.deleted', list.listName));
                    $rootScope.$broadcast('task_list_data.updated');

                    // trigger event about deleted section
                    angular.element('#headerButtonsApp').trigger('onDeleteList', {});
                },
                function(data) {
                    service.tasksUpdating = false;
                    growlService.showError(lmsg('common.general.error'), data.statusText);
                }
            );
        };

        service.updateListOrder = function(listId, newIndex)
        {
            // Find the right list that got moved
            var foundList = null;
            var foundIndex = null;
            for (var listIndex = 0; listIndex < service.lists.length; listIndex++)
            {
                if (service.lists[listIndex].id == listId)
                {
                    foundList = service.lists[listIndex];
                    foundIndex = listIndex;
                }
            }

            if (foundList !== null)
            {
                // Remove old list and reinsert it at the right place
                service.lists.splice(foundIndex, 1);
                service.lists.splice(newIndex, 0, foundList);
            }

            // Get new ID order
            var newOrder = []
            for (var listIndex = 0; listIndex < service.lists.length; listIndex++)
            {
                newOrder.push(service.lists[listIndex].id);
            }

            service.saveListOrder(newOrder).then(
                function(data) {
                    // Make a copy first to re-use old data from
                    var oldSections = [];
                    for (var listIndex = 0; listIndex < service.lists.length; listIndex++)
                    {
                        oldSections.push(service.lists[listIndex]);
                    }

                    service.lists.length = 0;
                    for (var key in data.data.sections)
                    {
                        if (data.data.sections.hasOwnProperty(key))
                        {
                            var sectionData = data.data.sections[key];
                            var section = null;

                            // Copy from existing data where possible to retain temporary state (collapsed, etc)
                            for (var oldListIndex = 0; oldListIndex < oldSections.length; oldListIndex++)
                            {
                                if (sectionData.id == oldSections[oldListIndex].id)
                                    section = oldSections[oldListIndex];
                            }
                            if (section === null)
                                section = new TaskSection();

                            section.loadFromArray(sectionData);
                            service.lists.push(section);
                        }
                    }

                    $rootScope.$broadcast('task_list_data.updated');
                },
                function(data) {
                    // Irrelevant change to force watches to fire and rebuild local data using the original
                    // data, undoing the drag+drop
                    service.statuses[0].sort_order += 0.001;

                    $rootScope.$broadcast('task_list_data.updated');
                    growlService.showError(lmsg('common.general.error'), data.statusText);
                }
            );
        };

        service.saveListOrder = function(listOrder)
        {
            var data = {
                project_id: service.project_id,
                newOrder: listOrder
            };

            var url = service.getListEndpoint() + '/order';
            var promise = $http.post(url, data);

            service.tasksUpdating = true;
            promise.then(function(){
                service.tasksUpdating = false;
            }, function(){
                service.tasksUpdating = false;
            });
            return promise;
        };

        service.saveTask = function(task)
        {
            var data = {
                title: task.title,
                description: task.description,
                orders: task.orders,
                project_id: task.project_id,
                assignees: task.assignees,
                due_date: task.due_date,
                priority: task.priority,
                duration: task.duration,
                is_private: task.is_private,
                section_id: task.list_id,
                status_id: task.status_id
            };

            var promise = null;
            var url = '';
            if (task.id <= 0)
            {
                url = service.getTaskAddEndpoint();
                promise = $http.post(url, data);
            } else
            {
                url = service.getTaskEndpoint(task.id);
                promise = $http.put(url, data);
            }

            service.tasksUpdating = true;
            promise.then(function(){
                service.tasksUpdating = false;
            }, function(){
                service.tasksUpdating = false;
            });
            return promise;
        };

        service.editTask = function(data, listType)
        {
            if (listType === undefined)
                listType = 'list';

            return new Promise(function(resolve, reject) {
                var oldTitle = '';
                var oldDescription = '';
                var oldAssignees = [];
                var oldDueDate = '';
                var oldPriority = '';
                var oldDuration = '';
                var oldIsPrivate = false;
                var oldListId = 0;
                var oldOrders;

                var newTask = {};

                if (data.id === 0)
                {
                    newTask = {
                        id: 0,
                        project_id: data.project_id,
                        title: data.name,
                        description: data.description,
                        assignees: data.assignees,
                        due_date: data.due_date,
                        priority: data.priority,
                        duration: data.duration,
                        is_private: data.is_private,
                        list_id: data.list_id,
                        orders: {
                            list: DISPLAY_ORDER_NONE,
                            board: DISPLAY_ORDER_NONE,
                            board_no_list: DISPLAY_ORDER_NONE,
                            my_tasks: DISPLAY_ORDER_NONE
                        }
                    };
                    newTask.orders[listType] = service.tasks.length;
                } else
                {
                    for (var taskIndex = 0; taskIndex < service.tasks.length; taskIndex++)
                    {
                        if (service.tasks[taskIndex].id == data.id)
                        {
                            oldTitle = service.tasks[taskIndex].title;
                            oldDescription = service.tasks[taskIndex].description;
                            oldAssignees = service.tasks[taskIndex].assignees;
                            oldDueDate = service.tasks[taskIndex].due_date;
                            oldPriority = service.tasks[taskIndex].priority;
                            oldDuration = service.tasks[taskIndex].duration;
                            oldIsPrivate = service.tasks[taskIndex].is_private;
                            oldListId = service.tasks[taskIndex].list_id;
                            oldOrders = service.tasks[taskIndex].orders;

                            service.tasks[taskIndex].title = data.name;
                            service.tasks[taskIndex].description = data.description;
                            service.tasks[taskIndex].assignees = data.assignees;
                            service.tasks[taskIndex].due_date = data.due_date;
                            service.tasks[taskIndex].priority = data.priority;
                            service.tasks[taskIndex].duration = data.duration;
                            service.tasks[taskIndex].is_private = data.is_private;
                            service.tasks[taskIndex].list_id = data.list_id;
                            service.tasks[taskIndex].orders = data.orders;

                            newTask = service.tasks[taskIndex];
                        }
                    }
                }

                // Don't do anything more if there was no change made
                if ((data.name === oldTitle) &&
                    (data.description === oldDescription) &&
                    (data.assignees === oldAssignees) &&
                    (data.due_date === oldDueDate) &&
                    (data.priority === oldPriority) &&
                    (data.duration === oldDuration) &&
                    (data.is_private === oldIsPrivate) &&
                    (data.list_id === oldListId) &&
                    (data.orders === oldOrders))
                {
                    resolve();
                    return null;
                }

                var newName = data.name;

                service.saveTask(newTask).then(
                    function(data){
                        growlService.showSuccess(lmsg('projects.task.task_updated', newName));

                        /**
                         * This is specified case when user removed all task list and API will create a default list for
                         * a new created task and here we have to update the front end.
                         */
                        if (isNaN(newTask.list_id))
                        {
                            list = new TaskSection();
                            list.id = data.data.section_id;
                            list.colour = '#ccddee';
                            list.project_id = service.project_id;
                            list.display_order = 0;
                            list.title = data.data.section_name;
                            service.lists.push(list);

                            $rootScope.$broadcast('task_list_data.updated');
                        }

                        if (newTask.id === 0)
                        {
                            task = new Task();
                            task.loadFromArray(data.data);

                            service.tasks.push(task);
                            $rootScope.$broadcast('task_list_data.updated');
                        }

                        resolve();
                    },
                    function(data){
                        if (newTask.id > 0)
                        {
                            task.title = oldTitle;
                            task.description = oldDescription;
                            task.assignees = oldAssignees;
                            task.due_date = oldDueDate;
                            task.priority = oldPriority;
                            task.duration = oldDuration;
                            task.is_private = oldIsPrivate;
                            task.list_id = oldListId;
                            task.orders = oldOrders;
                        }

                        growlService.showError(lmsg('common.general.error'), data.data.error);

                        reject();
                    }
                );
            });
        };

        service.updateTaskOrder = function(taskId, data)
        {
            service.saveTaskOrder(taskId, data).then(
                function(data)
                {
                    service.tasks.length = 0;
                    for (var i = 0; i < data.data.tasks.length; i++)
                    {
                        var task = new Task();
                        task.loadFromArray(data.data.tasks[i]);
                        service.tasks.push(task);
                    }

                    $rootScope.$broadcast('task_list_data.updated');
                }, function(data)
                {
                    growlService.showError(lmsg('common.general.error'), data.statusText);
                }
            );
        };

        service.saveTaskOrder = function(taskId, data)
        {
            data.projectId = service.project_id;

            var url = service.getTaskEndpoint(taskId) + '/order';
            var promise = $http.post(url, data);

            service.tasksUpdating = true;
            promise.then(function(){
                service.tasksUpdating = false;
            }, function(){
                service.tasksUpdating = false;
            });
            return promise;
        };

        service.deleteTask = function(taskId)
        {
            // The task has already been deleted by the edit task modal so just remove it locally
            var oldTasks = [];
            for (var taskIndex = 0; taskIndex < service.tasks.length; taskIndex++)
            {
                oldTasks.push(service.tasks[taskIndex]);
            }

            service.tasks.length = 0;
            for (taskIndex = 0; taskIndex < oldTasks.length; taskIndex++)
            {
                if (oldTasks[taskIndex].id !== taskId)
                    service.tasks.push(oldTasks[taskIndex]);
            }

            $rootScope.$broadcast('task_list_data.updated');
        };

        service.toggleCompleted = function(taskId)
        {
            for (var taskIndex = 0; taskIndex < service.tasks.length; taskIndex++)
            {
                if (service.tasks[taskIndex].id === taskId)
                {
                    var statusId = service.tasks[taskIndex].status_id;

                    if (statusId === STATUS_ID_COMPLETED)
                        service.tasks[taskIndex].status_id = STATUS_ID_TODO;
                    else
                        service.tasks[taskIndex].status_id = STATUS_ID_COMPLETED;

                    service.saveTask(service.tasks[taskIndex]).then(function() {
                        $rootScope.$broadcast('task_list_data.updated');
                    }, function(data) {
                        growlService.showError(lmsg('common.general.error'), data.statusText);
                    });
                }
            }
        };

        angular.element('#headerButtonsApp').on('onAddList', function(event, data)
        {
            service.editList({
                id: 0,
                name: data.title,
                colour: data.colour
            }).then(function(){
                data.success();
            }, function(){
            });
        });

        angular.element('#headerButtonsApp').on('onAddTask', function(event, data)
        {
            service.editTask({
                id: 0,
                project_id: service.project_id,
                name: data.name,
                description: data.description,
                assignees: data.assignees,
                due_date: data.due_date,
                priority: data.priority,
                duration: data.duration,
                is_private: data.is_private,
                list_id: data.list_id,
                status_id: data.status_id
            }).then(function(){
                data.success();
            }, function(){
            });
        });
    }
}());
