/**
* Provides a service class for the Task domain class.
*
*/
class TaskService {

    boolean transactional = false

    def authService
    def assignedGroupService
    def assignedPersonService

    /**
    * Determines and returns a possible parent list for a task.
    * @todo Create and use another method that limits the results to say the latest 20 or 100 tasks?
    * @param taskInstance The task to use when determining the possible parent list.
    * @returns A list of the possible parents.
    */
    def possibleParentList(taskInstance) {
        def criteria = taskInstance.createCriteria()
        def possibleParentList = criteria {
            and {
                notEqual('trash', true)
                notEqual('id', taskInstance.id)
                taskInstance.subTasks.each() { notEqual('id', it.id) }
                }
        }
    }

    /**
    * Creates a new task with the given params.
    * @param params The params to use when creating the new task.
    * @returns A map containing result.error=true (if any error) and result.taskInstance.
    */
    def save(params) {
        Task.withTransaction { status ->
            def result = [:]
            // Default status to "not started" if not supplied.
            params.taskStatus = params.taskStatus ?: TaskStatus.get(1)

            // Set budgetStatus.
            if(params.taskType?.id?.toLong() == 1) // Unscheduled Breakin.
                params.taskBudgetStatus = params.taskBudgetStatus ?: TaskBudgetStatus.get(1) // Unplanned.
            else
                params.taskBudgetStatus = params.taskBudgetStatus ?: TaskBudgetStatus.get(2) // Planned.

            def taskInstance = new Task(params)
            result.taskInstance = taskInstance

            if(result.taskInstance.parentTask?.trash) {
                status.setRollbackOnly()
                result.taskInstance.errors.rejectValue("parentTask", "task.operationNotPermittedOnTaskInTrash")
                result.error = true
                return result
            }

            if(taskInstance.save()) {
                def taskModification = new TaskModification(person: authService.currentUser,
                                                        taskModificationType: TaskModificationType.get(1),
                                                        task: taskInstance)

                if(!taskModification.save()) {
                    status.setRollbackOnly()
                    taskInstance.errors.rejectValue("taskModifications", "task.modifications.failedToSave")
                    result.error = true
                    return result
                }

                //Add the assignedGroups, provided by a new ArrayList(task.assignedGroups)
                if(params.assignedGroups) {
                    def assignedGroupsResult
                    def assignedGroupParams = [:]
                    params.assignedGroups.each() {

                        assignedGroupParams = [personGroup: it.personGroup,
                                                                    task: taskInstance,
                                                                    estimatedHour: it.estimatedHour,
                                                                    estimatedMinute: it.estimatedMinute]

                        assignedGroupsResult = assignedGroupService.save(assignedGroupParams)

                        if(assignedGroupsResult.error) {
                            status.setRollbackOnly()
                            taskInstance.errors.rejectValue("assignedGroups", "task.assignedGroups.failedToSave")
                            result.error = true
                            return result
                        }

                    }
                }

                //Add the assignedPersons, provided by a new ArrayList(task.assignedPersons)
                if(params.assignedPersons) {
                    def assignedPersonsResult
                    def assignedPersonsParams = [:]
                    params.assignedPersons.each() {

                        assignedPersonsParams = [person: it.person,
                                                                    task: taskInstance,
                                                                    estimatedHour: it.estimatedHour,
                                                                    estimatedMinute: it.estimatedMinute]

                        assignedPersonsResult = assignedPersonService.save(assignedPersonsParams)

                        if(assignedPersonsResult.error) {
                            status.setRollbackOnly()
                            taskInstance.errors.rejectValue("assignedPersons", "task.assignedPersons.failedToSave")
                            result.error = true
                            return result
                        }

                    }
                }

                // Success.
                return result
            }
            else {
                result.error = true
                return result
            }

        } //end withTransaction
    } // end save()

    /**
    * Creates a subTask copying sane attributes from the parentTask unless otherwise specified in params.
    * The taskProcedure is only assigned to the sub task if supplied in params.
    * The assignedPersons and assignedGroups are only added to the sub task if supplied in params.
    * Collections in params must be supplied as new ArrayList's.
    * This method is not intended to be a copyTask method.
    * There should be no reason to copy tasks, try to find a better solution.
    * @param parentTask The parent task to get attributes from, also set as the parent.
    * @param params Overrides the parent task values if specified.
    * @returns A map containing result.error=true (if any error) and result.taskInstance.
    */
    def createSubTask(parentTask, params = [:]) {

        def result = [:]

        //Make our new Task a subTask and set the required properites.
        def p = [:]
        p.parentTask = parentTask
        p.description = params.description ?: parentTask.description
        p.comment = params.comment ?: parentTask.comment
        p.targetStartDate = params.targetStartDate ?: parentTask.targetStartDate
        p.targetCompletionDate = params.targetCompletionDate ?: parentTask.targetCompletionDate

        p.taskGroup = params.taskGroup ?: parentTask.taskGroup
        p.taskStatus = TaskStatus.get(1) // A new subTask must always be "Not Started".
        p.taskPriority = parentTask.taskPriority
        p.taskType = params.taskType ?: parentTask.taskType
        p.leadPerson = params.leadPerson ?: parentTask.leadPerson
        p.primaryAsset = params.primaryAsset ?: parentTask.primaryAsset
        p.associatedAssets = params.associatedAssets ?: new ArrayList(parentTask.associatedAssets) // Collection.

        // Only if supplied, otherwise this would be copying.
        if(params.scheduled) p.scheduled = params.scheduled
        if(params.approved) p.approved = params.approved

        // Supplied by recurring tasks.
        if(params.taskProcedure) p.taskProcedure = params.taskProcedure
        if(params.assignedGroups) p.assignedGroups = params.assignedGroups // Collection.
        if(params.assignedPersons) p.assignedPersons = params.assignedPersons // Collection.

        // trash: A new subTask must always have trash=false, which is already the domain class default.

        // These would be considered copying, hence not done.
        // taskRecurringSchedule, entries, taskModifications, subTasks, inventoryMovements.

        // Create the sub task and return the result.
        result = save(p)

    } // end createSubTask()

    /**
    * Creates a new task entry.
    * @param params The params to use when creating the new entry.
    * @returns A map containing result.error=true (if any error), result.entryInstance and result.taskId.
    */
    def saveEntry(params) {
        Task.withTransaction { status ->
            def result = [:]

            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.taskInstance && m.field)
                    result.taskInstance.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["Entry", params.id] ]
                return result
            }

            result.entryInstance = new Entry(params)
            result.entryInstance.enteredBy = authService.currentUser

            def taskInstance
            if(result.entryInstance.task.id) {
                result.taskId = result.entryInstance.task.id
                taskInstance = Task.lock(result.entryInstance.task.id)
            }

            if(!taskInstance)
                return fail(field:"task", code:"task.notFound")

            if(result.entryInstance.hasErrors() || !result.entryInstance.save())
                return fail(code:"default.create.failure")

            if(taskInstance.taskStatus.id == 3)
                return fail(field:"task", code:"task.operationNotPermittedOnCompleteTask")

            // If task status is "Not Started" and entry type is "Work Done" then we create the started modification and set the status.
            if(taskInstance.taskStatus.id == 1 && result.entryInstance.entryType.id == 2) {

                // Create the "Started" task modification, this provides the "Actual Started Date".
                def taskModification = new TaskModification(person: authService.currentUser,
                                                        taskModificationType: TaskModificationType.get(2),
                                                        task: taskInstance)

                if(taskModification.hasErrors() || !taskModification.save())
                    return fail(field:"task", code:"task.modifications.failedToSave")

                // Set task status to "In Progress".
                taskInstance.taskStatus = TaskStatus.get(2)

                if(taskInstance.hasErrors() || !taskInstance.save())
                    return fail(field:"task", code:"task.failedToSave")
            }

            if(result.entryInstance.hasErrors() || !result.entryInstance.save())
                return fail(field:"task", code:"default.create.failure")

            // Success.
            return result

        } //end withTransaction
    } // end saveEntry()

    /**
    * Updates an existing task.
    * @param params The params to update for task with id of params.id.
    * @returns A map containing result.error=true (if any error) and result.taskInstance (if available).
    */
    def update(params) {
        Task.withTransaction { status ->
            def result = [:]

            def fail = { Object[] args ->
                status.setRollbackOnly()
                if(args.size() == 2) result.taskInstance.errors.rejectValue(args[0], args[1])
                result.error = true
                return result
            }

            result.taskInstance = Task.get(params.id)

            if(!result.taskInstance)
                return fail('id', "task.notFound")

            // Optimistic locking check.
            if(params.version) {
                def version = params.version.toLong()
                if(result.taskInstance.version > version)
                    return fail("version", "default.optimistic.locking.failure")
            }

            result.taskInstance.properties = params

            if(result.taskInstance.hasErrors() || !result.taskInstance.save())
                return fail()

            def taskModification = new TaskModification(person:authService.currentUser,
                                                    taskModificationType: TaskModificationType.get(3),
                                                    task: result.taskInstance)

            if(!taskModification.save())
                return fail("taskModifications", "task.modifications.failedToSave")

            // If we get here all went well.
            return result

        } //end withTransaction
    }  // end update()

    /**
    * Completes an existing task.
    * @param params The params for task with id of params.id.
    * @returns A map containing result.error=true (if any error) and result.taskInstance.
    */
    def complete(params) {
        Task.withTransaction { status ->
            def result = [:]
            result.taskInstance = Task.get(params.id)
            if(result.taskInstance) {

                // Optimistic locking check.
                if(params.version) {
                    def version = params.version.toLong()
                    if(result.taskInstance.version > version) {
                        status.setRollbackOnly()
                        result.taskInstance.errors.rejectValue("version", "task.optimistic.locking.failure", "Another user has updated this Task while you were editing.")
                        result.error = true
                        return result
                    }
                }

                result.taskInstance.taskStatus = TaskStatus.get(3)
                result.taskInstance.taskRecurringSchedule?.enabled = false

                if(result.taskInstance.save()) {
                    def taskModification = new TaskModification(person:authService.currentUser,
                                                            taskModificationType: TaskModificationType.get(4),
                                                            task: result.taskInstance)

                    if(taskModification.save()) {
                        // All went well.
                        return result
                    }
                    else {
                        status.setRollbackOnly()
                        result.taskInstance.errors.rejectValue("taskModifications", "task.modifications.failedToSave")
                        result.error = true
                        return result
                    }
                }
            }
            // Something failed.
            status.setRollbackOnly()
            result.error = true
            return result

        } //end withTransaction
    }  // end complete()

    /**
    * Reopens an existing task.
    * @param params The params for task with id of params.id.
    * @returns A map containing result.error=true (if any error) and result.taskInstance.
    */
    def reopen(params) {
        Task.withTransaction { status ->
            def result = [:]
            result.taskInstance = Task.get(params.id)
            if(result.taskInstance) {

                // Optimistic locking check.
                if(params.version) {
                    def version = params.version.toLong()
                    if(result.taskInstance.version > version) {
                        status.setRollbackOnly()
                        result.taskInstance.errors.rejectValue("version", "task.optimistic.locking.failure", "Another user has updated this Task while you were editing.")
                        result.error = true
                        return result
                    }
                }

                result.taskInstance.taskStatus = TaskStatus.get(2)

                if(result.taskInstance.save()) {
                    def taskModification = new TaskModification(person:authService.currentUser,
                                                            taskModificationType: TaskModificationType.get(5),
                                                            task: result.taskInstance)
                    if(taskModification.save()) {
                        // All went well.
                        return result
                    }
                    else {
                        status.setRollbackOnly()
                        result.taskInstance.errors.rejectValue("taskModifications", "task.modifications.failedToSave")
                        result.error = true
                        return result
                    }
                }
            }
            // Something failed.
            status.setRollbackOnly()
            result.error = true
            return result

        } //end withTransaction
    }  // end reopen()

    /**
    * Move a task to the trash.
    * @param params The params for task with id of params.id.
    * @returns A map containing result.error=true (if any error) and result.taskInstance.
    */
    def trash(params) {
        Task.withTransaction { status ->
            def result = [:]
            result.taskInstance = Task.get(params.id)
            if(result.taskInstance) {

                // Optimistic locking check.
                if(params.version) {
                    def version = params.version.toLong()
                    if(result.taskInstance.version > version) {
                        status.setRollbackOnly()
                        result.taskInstance.errors.rejectValue("version", "task.optimistic.locking.failure", "Another user has updated this Task while you were editing.")
                        result.error = true
                        return result
                    }
                }

                result.taskInstance.trash = true
                result.taskInstance.taskRecurringSchedule?.enabled = false

                if(result.taskInstance.save()) {
                    def taskModification = new TaskModification(person:authService.currentUser,
                                                            taskModificationType: TaskModificationType.get(6),
                                                            task: result.taskInstance)
                    if(taskModification.save()) {
                        // All went well.
                        return result
                    }
                    else {
                        status.setRollbackOnly()
                        result.taskInstance.errors.rejectValue("taskModifications", "task.modifications.failedToSave")
                        result.error = true
                        return result
                    }
                }
            }
            // Something failed.
            status.setRollbackOnly()
            result.error = true
            return result

        } //end withTransaction
    }  // end trash()

    /**
    * Restore a task from the trash.
    * @param params The params for task with id of params.id.
    * @returns A map containing result.error=true (if any error) and result.taskInstance.
    */
    def restore(params) {
        Task.withTransaction { status ->
            def result = [:]
            result.taskInstance = Task.get(params.id)
            if(result.taskInstance) {

                // Optimistic locking check.
                if(params.version) {
                    def version = params.version.toLong()
                    if(result.taskInstance.version > version) {
                        status.setRollbackOnly()
                        result.taskInstance.errors.rejectValue("version", "task.optimistic.locking.failure", "Another user has updated this Task while you were editing.")
                        result.error = true
                        return result
                    }
                }

                result.taskInstance.trash = false

                if(result.taskInstance.save()) {
                    def taskModification = new TaskModification(person:authService.currentUser,
                                                            taskModificationType: TaskModificationType.get(7),
                                                            task: result.taskInstance)
                    if(taskModification.save()) {
                        // All went well.
                        return result
                    }
                    else {
                        status.setRollbackOnly()
                        result.taskInstance.errors.rejectValue("taskModifications", "task.modifications.failedToSave")
                        result.error = true
                        return result
                    }
                }
            }
            // Something failed.
            status.setRollbackOnly()
            result.error = true
            return result

        } //end withTransaction
    }  // end restore()

    /**
    * Approve a task.
    * @param params The params for task with id of params.id.
    * @returns A map containing result.error=true (if any error) and result.taskInstance.
    */
    def approve(params) {
        Task.withTransaction { status ->
            def result = [:]
            result.taskInstance = Task.get(params.id)
            if(result.taskInstance) {

                // Optimistic locking check.
                if(params.version) {
                    def version = params.version.toLong()
                    if(result.taskInstance.version > version) {
                        status.setRollbackOnly()
                        result.taskInstance.errors.rejectValue("version", "task.optimistic.locking.failure", "Another user has updated this Task while you were editing.")
                        result.error = true
                        return result
                    }
                }

                result.taskInstance.approved = true

                if(result.taskInstance.save()) {
                    def taskModification = new TaskModification(person:authService.currentUser,
                                                            taskModificationType: TaskModificationType.get(8),
                                                            task: result.taskInstance)
                    if(taskModification.save()) {
                        // All went well.
                        return result
                    }
                    else {
                        status.setRollbackOnly()
                        result.taskInstance.errors.rejectValue("taskModifications", "task.modifications.failedToSave")
                        result.error = true
                        return result
                    }
                }
            }
            // Something failed.
            status.setRollbackOnly()
            result.error = true
            return result

        } //end withTransaction
    }  // end approve()

    /**
    * Remove a previously given approval from a task.
    * @param params The params for task with id of params.id.
    * @returns A map containing result.error=true (if any error) and result.taskInstance.
    */
    def renegeApproval(params) {
        Task.withTransaction { status ->
            def result = [:]
            result.taskInstance = Task.get(params.id)
            if(result.taskInstance) {

                // Optimistic locking check.
                if(params.version) {
                    def version = params.version.toLong()
                    if(result.taskInstance.version > version) {
                        status.setRollbackOnly()
                        result.taskInstance.errors.rejectValue("version", "task.optimistic.locking.failure", "Another user has updated this Task while you were editing.")
                        result.error = true
                        return result
                    }
                }

                result.taskInstance.approved = false

                if(result.taskInstance.save()) {
                    def taskModification = new TaskModification(person:authService.currentUser,
                                                            taskModificationType: TaskModificationType.get(9),
                                                            task: result.taskInstance)
                    if(taskModification.save()) {
                        // All went well.
                        return result
                    }
                    else {
                        status.setRollbackOnly()
                        result.taskInstance.errors.rejectValue("taskModifications", "task.modifications.failedToSave")
                        result.error = true
                        return result
                    }
                }
            }
            // Something failed.
            status.setRollbackOnly()
            result.error = true
            return result

        } //end withTransaction
    }  // end renegeApproval()

    /**
    * Creates a new breakin task with the given params.
    * @param params The params to use when creating the new task.
    * @returns A map containing result.error=true (if any error) and result.taskInstance.
    */
    def saveBreakin(params) {
        Task.withTransaction { status ->
            def result = [:]

            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.taskInstance && m.field)
                    result.taskInstance.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["Task", params.id] ]
                return result
            }

            // If not supplied.
            if(!params.taskStatus)
                params.taskStatus = TaskStatus.get(1) // Not Started.

            result.taskInstance = new Task(params)

            // Always for a breakin.
            result.taskInstance.taskType = TaskType.get(1) // Unscheduled Breakin.
            result.taskInstance.taskBudgetStatus = TaskBudgetStatus.get(1) // Unplanned.
            result.taskInstance.taskPriority = TaskPriority.get(4) // Immediate.
            result.taskInstance.taskGroup = TaskGroup.get(1) // Engineering Activites.
            result.taskInstance.approved = true
            result.taskInstance.leadPerson = authService.currentUser

            if(result.taskInstance.hasErrors() || !result.taskInstance.save())
                fail(code:"default.create.failure")

            if(!result.error) {
                def taskModification = new TaskModification(person: authService.currentUser,
                                                                taskModificationType: TaskModificationType.get(1), // Created.
                                                                task: result.taskInstance)

                if(taskModification.hasErrors() || !taskModification.save())
                    fail(field:"taskModifications", code:"task.modifications.failedToSave")
            }

            def faultParams = [task: result.taskInstance,
                                            entryType: EntryType.get(1),
                                            comment: params.entryFault.comment,
                                            durationHour: params.entryFault.durationHour,
                                            durationMinute: params.entryFault.durationMinute]
            def faultResult = saveEntry(faultParams)
            result.entryFaultInstance = faultResult.entryInstance

            def workDoneParams = [task: result.taskInstance,
                                                    entryType: EntryType.get(2),
                                                    comment: params.entryWorkDone.comment,
                                                    durationHour: params.entryWorkDone.durationHour,
                                                    durationMinute: params.entryWorkDone.durationMinute]
            def workDoneResult = saveEntry(workDoneParams)
            result.entryWorkDoneInstance = workDoneResult.entryInstance

            if(result.error)
                return result

            if(faultResult.error)
                return fail(code: "default.create.failure")

            if(workDoneResult.error)
                return fail(code: "default.create.failure")

            // Success.
            return result

        } //end withTransaction
    } // end saveBreakin()

} // end TaskService
