/**
* Provides a service class for the TaskProcedure domain class.
*/
class TaskProcedureService {

    boolean transactional = false

    def authService
    def dateUtilService

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

            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.taskProcedureInstance && m.field)
                    result.taskProcedureInstance.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["TaskProcedure", params.id] ]
                // Fetch to prevent lazy initialization error.
                result.taskProcedureInstance.revisions.each {it.createdBy.toString()}
                return result
            }

            result.taskProcedureInstance = TaskProcedure.get(params.id)

            if(!result.taskProcedureInstance)
                return fail(code:"default.not.found")

            // Optimistic locking check.
            if(params.version) {
                if(result.taskProcedureInstance.version > params.version.toLong())
                    return fail(field:"version", code:"default.optimistic.locking.failure")
            }

            result.taskProcedureInstance.properties = params

            // Gaps in the html index's can be created by adding 2 items and removing the first one.
            // This creates a gap at the missing index where LazyList will return a null.
            def nullMaintenanceActions = result.taskProcedureInstance.maintenanceActions.findAll {!it}
            if (nullMaintenanceActions) {
                result.taskProcedureInstance.maintenanceActions.removeAll(nullMaintenanceActions)
            }
            def nullDocumentReferences = result.taskProcedureInstance.documentReferences.findAll {!it}
            if (nullDocumentReferences) {
                result.taskProcedureInstance.documentReferences.removeAll(nullDocumentReferences)
            }

            // Save for restoration if validation fails.
            def savedMaintenanceActions = new ArrayList(result.taskProcedureInstance.maintenanceActions)
            def savedDocumentReferences = new ArrayList(result.taskProcedureInstance.documentReferences)

            // Remove toBeDeleted before validation.
            def ma_toBeDeleted = result.taskProcedureInstance.maintenanceActions.findAll {it.toBeDeleted}
            if (ma_toBeDeleted) {
                result.taskProcedureInstance.maintenanceActions.removeAll(ma_toBeDeleted)
            }
            def docRef_toBeDeleted = result.taskProcedureInstance.documentReferences.findAll {it.toBeDeleted}
            if (docRef_toBeDeleted) {
                result.taskProcedureInstance.documentReferences.removeAll(docRef_toBeDeleted)
            }

            if(result.taskProcedureInstance.hasErrors() || !result.taskProcedureInstance.save()) {
                // Restore the saved items, some of which contain toBeDeleted flags but
                // have not been deleted yet since validation failed.
                // The toBeDeleted items are hidden in the view.
                result.taskProcedureInstance.maintenanceActions = savedMaintenanceActions
                result.taskProcedureInstance.documentReferences = savedDocumentReferences
                // Populate collection errors for display.
                result.taskProcedureInstance.maintenanceActions.each { it.validate() }
                result.taskProcedureInstance.documentReferences.each { it.validate() }
                return fail(code:"default.update.failure")
            }

            // Sort MaintenanceActions.
            result.taskProcedureInstance.maintenanceActions.sort { p1, p2 -> p1.procedureStepNumber <=> p2.procedureStepNumber }

            def r = createRevision(result.taskProcedureInstance)
            if(r.error)
                return fail(field:'id', code:"default.create.revision.failure")

            // Also sets: taskInstance.taskProcedureRevision = taskProcedureRevision
            r.taskProcedureRevision.addToTasks(result.taskProcedureInstance.linkedTask)

            // Update tasks that are using previousRevision.
            // Only those that are not started and due to be started today or in the future.
            def previousRevision = result.taskProcedureInstance.getRevision(r.taskProcedureRevision.revision - 1)
            if(previousRevision) {
                Task.withCriteria {
                    eq("taskProcedureRevision", previousRevision)
                    ge("targetStartDate", dateUtilService.today)
                    taskStatus {
                        idEq(1L) // Not Started.
                    }
                    maxResults(10000)
                }.each {
                    it.taskProcedureRevision = r.taskProcedureRevision
                }
            }

            // Success.
            return result

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

    /**
    * Creates a new taskProcedure with the given params.
    * @param params The params to use when creating the new taskProcedure.
    * @returns A map containing result.error (if any error) and result.taskProcedure.
    */
    def save(params) {
        def result = [:]
        TaskProcedure.withTransaction { status ->
            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.taskProcedureInstance && m.field)
                    result.taskProcedureInstance.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["TaskProcedure", params.id] ]
                // Fetch to prevent lazy initialization error.
                result.taskProcedureInstance.linkedTask.primaryAsset
                return result
            }

            result.taskProcedureInstance = new TaskProcedure(params)

            // Optimistic locking check on linkedTask.
            if(result.taskProcedureInstance.linkedTask.taskProcedureRevision)
                    return fail(field:"version", code:"default.optimistic.locking.failure")

            // Gaps in the html index's can be created by adding 2 items and removing the first one.
            // This creates a gap at the missing index where LazyList will return a null.
            def nullMaintenanceActions = result.taskProcedureInstance.maintenanceActions.findAll {!it}
            if (nullMaintenanceActions) {
                result.taskProcedureInstance.maintenanceActions.removeAll(nullMaintenanceActions)
            }
            def nullDocumentReferences = result.taskProcedureInstance.documentReferences.findAll {!it}
            if (nullDocumentReferences) {
                result.taskProcedureInstance.documentReferences.removeAll(nullDocumentReferences)
            }

            if(result.taskProcedureInstance.hasErrors() || !result.taskProcedureInstance.save()) {
                // Populate collection errors for display.
                result.taskProcedureInstance.maintenanceActions.each { it.validate() }
                result.taskProcedureInstance.documentReferences.each { it.validate() }
                return fail(code:"default.create.failure")
            }

            // Sort MaintenanceActions.
            result.taskProcedureInstance.maintenanceActions.sort { p1, p2 -> p1.procedureStepNumber <=> p2.procedureStepNumber }

            def r = createRevision(result.taskProcedureInstance)
            if(r.error)
                return fail(field:'id', code:"default.create.revision.failure")

            // Also sets: taskInstance.taskProcedureRevision = taskProcedureRevision
            r.taskProcedureRevision.addToTasks(result.taskProcedureInstance.linkedTask)

        } // end withTransaction

        // Success.
        return result
    }  // end save()

    /**
    * Creates a new taskProcedureRevision.
    * @param taskProcedureInstance The taskProcedure to create the revision for.
    * @returns A map containing result.error (if any error) and result.taskProcedureRevision.
    */
    def createRevision(taskProcedureInstance) {
        def result = [:]
        TaskProcedureRevision.withTransaction { status ->
            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.taskProcedureRevision && m.field)
                    result.taskProcedureRevision.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["TaskProcedureRevision"] ]
                return result
            }

            result.taskProcedureRevision = new TaskProcedureRevision()
            taskProcedureInstance.addToRevisions(result.taskProcedureRevision)

            result.taskProcedureRevision.taskProcedure = taskProcedureInstance
            result.taskProcedureRevision.linkedTask = taskProcedureInstance.linkedTask
            result.taskProcedureRevision.createdBy =  authService.currentUser
            result.taskProcedureRevision.revision = 0 // init to prevent DataIntegrityViolationException
            result.taskProcedureRevision.revision = taskProcedureInstance.revision
            taskProcedureInstance.maintenanceActions.each {
                result.taskProcedureRevision.addToMaintenanceActions(new MaintenanceAction(it.properties))
            }
            taskProcedureInstance.documentReferences.each {
                result.taskProcedureRevision.addToDocumentReferences(new DocumentReference(it.properties))
            }

            if(result.taskProcedureRevision.hasErrors() || !result.taskProcedureRevision.save()) {
                log.error result.taskProcedureRevision.errors
                return fail(code:"default.create.failure")
            }

        } // end withTransaction

        // Success.
        return result
    } // end createRevision()

} // end class
