class TaskRecurringScheduleService {

    boolean transactional = false

    // Can hold state since the service is a singleton.
    boolean baseDataWarnLogged = false

    def taskService
    def dateUtilService
    def appConfigService

    /**
    * Generate all enabled recurring tasks.
    */
    def generateAll() {

        // Prevent errors if base data has not yet been created.
        if(!appConfigService.exists("baseDataCreated")) {
            if(!baseDataWarnLogged) {
                log.warn "Base data has not been created, can't generate all."
                baseDataWarnLogged = true
            }
            return
        }

        def p = [:]
        def targetCompletionDate
        def nextTargetCompletionDate
        def tomorrow = dateUtilService.tomorrow

        def taskRecurringScheduleList = TaskRecurringSchedule.findAllByEnabled(true)

        // Stop generation if max reached.
        def maxSubTasksReached = {
            if( (it.maxSubTasks > 0) && (it.subTasksGenerated >= it.maxSubTasks) ) {
                it.enabled = false
                return true
            }
            return false
        }

        // Stop generation by targetCompletionDate.
        def targetCompletionDateReached = {
            if( it.useTargetCompletionDate) {
                targetCompletionDate = dateUtilService.getMidnight(it.task.targetCompletionDate)
                nextTargetCompletionDate = dateUtilService.getMidnight(it.nextTargetCompletionDate)
                if(nextTargetCompletionDate > targetCompletionDate) {
                    it.enabled = false
                    return true
                }
            }
            return false
        }

        // Main loop.
        for(it in taskRecurringScheduleList) {

            if(maxSubTasksReached(it))
                continue

            if(targetCompletionDateReached(it))
                continue

            if (dateUtilService.tomorrow > it.nextGenerationDate) {
                p = [:]

                // Build the subTask params.
                p.targetStartDate = it.nextTargetStartDate
                p.targetCompletionDate = it.nextTargetCompletionDate
                if(it.task.taskProcedureRevision) p.taskProcedureRevision = it.task.taskProcedureRevision
                if(it.task.assignedGroups) p.assignedGroups = new ArrayList(it.task.assignedGroups)
                if(it.task.assignedPersons) p.assignedPersons = new ArrayList(it.task.assignedPersons)

                def result = taskService.createSubTask(it.task, p)
                if( !result.error ) {
                    it.lastGeneratedSubTask = result.taskInstance
                    it.subTasksGenerated++
                    it.setNextTargetStartDate()
                    it.setNextGenerationDate()
                    it.setNextTargetCompletionDate()
                }
                else {
                    log.error "Sub task generation for recurring schedule ${it.id} failed."
                    log.error result.taskInstance.errors
                }
            }

            // Run the completion reached checks for a second time so
            // that this recurring schedule does not run again if
            // there are no more subTasks to be generated.
            if(maxSubTasksReached(it))
                continue

            if(targetCompletionDateReached(it))
                continue

        } // for()
    } // generateAll()

    /**
    * Creates a new recurring schedule for a task with the given params.
    * @param params The params to use when creating the new recurring schedule.
    * @returns A map containing result.error=true (if any error) and result.taskRecurringScheduleInstance and result.taskId.
    */
    def create(params) {
        TaskRecurringSchedule.withTransaction { status ->
            def result = [:]

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

            result.taskRecurringScheduleInstance = new TaskRecurringSchedule(params)
            result.taskId = result.taskRecurringScheduleInstance.task.id

            if(!result.taskRecurringScheduleInstance.validate())
                return fail()

            def taskInstance = Task.lock(result.taskId)

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

            if(taskInstance.taskRecurringSchedule)
                return fail('task', "tast.taskRecurringSchedule.alreadyExists")

            if(taskInstance.taskStatus.id == 3)
                return fail('task', "task.operationNotPermittedOnCompleteTask")

            if(taskInstance.trash)
                return fail('task', "task.operationNotPermittedOnTaskInTrash")

            if(result.taskRecurringScheduleInstance.nextTargetStartDate < dateUtilService.today)
                return fail("nextTargetStartDate", "taskRecurringSchedule.nextTargetStartDate.mayNotBePast")

            taskInstance.taskRecurringSchedule = result.taskRecurringScheduleInstance

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

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

        } //end withTransaction
    } // end create()

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

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

            result.taskRecurringScheduleInstance = TaskRecurringSchedule.get(params.id)

            if(!result.taskRecurringScheduleInstance)
                return fail('id', "taskRecurringSchedule.notFound")

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

            result.taskRecurringScheduleInstance.properties = params

            if(result.taskRecurringScheduleInstance.nextTargetStartDate < dateUtilService.today)
                return fail("nextTargetStartDate", "taskRecurringSchedule.nextTargetStartDate.mayNotBePast")

            result.taskRecurringScheduleInstance.setNextGenerationDate()
            result.taskRecurringScheduleInstance.setNextTargetCompletionDate()

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

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

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

} // end of class
