import grails.orm.PagedResultList

/**
* Service class that encapsulates the business logic for Task searches.
*/
class TaskSearchService {

    boolean transactional = false

    def authService
    def dateUtilService
    def messageSource

    def g = new org.codehaus.groovy.grails.plugins.web.taglib.ApplicationTagLib()

    def paramsMax = 100000

    /**
    * Selects and returns the correct search results based on the supplied quickSearch.
    * @param params The request params, may contain params.quickSearch string to specify the search.
    * @param locale The locale to use when generating result.message.
    */
    def getQuickSearch(params, locale) {
        def result = [:]
        result.quickSearch = params.quickSearch ?: "plannersRange"

        def currentUser = authService.currentUser
        def startOfToday = dateUtilService.today
        def startOfYesterday = dateUtilService.yesterday
        def startOfTomorrow = dateUtilService.tomorrow
        def oneWeekAgo = dateUtilService.oneWeekAgo

        def formattedStartOfToday = g.formatDate(format: "EEE, dd-MMM-yyyy", date: startOfToday)
        def formattedStartOfYesterday = g.formatDate(format: "EEE, dd-MMM-yyyy", date: startOfYesterday)
        def formattedStartOfTomorrow = g.formatDate(format: "EEE, dd-MMM-yyyy", date: startOfTomorrow)
        def formattedOneWeekAgo = g.formatDate(format: "EEE, dd-MMM-yyyy", date: oneWeekAgo)

        def getMessage = { Map m ->
            messageSource.getMessage(m.code, m.args == null ? null : m.args.toArray(), locale)
        }

        switch (result.quickSearch) {
            case "myTodays":
                result.taskInstanceList = getPersonsTasks(params)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.persons.tasks.message",
                                                                    args:[currentUser, formattedStartOfToday])
                else
                    result.message = getMessage(code:"task.search.text.persons.tasks.none.found",
                                                                    args:[currentUser, formattedStartOfToday])
                break
            case "myYesterdays":
                result.taskInstanceList = getPersonsTasks(params, currentUser, startOfYesterday, startOfToday)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.persons.tasks.message",
                                                                    args:[currentUser, formattedStartOfYesterday])
                else
                    result.message = getMessage(code:"task.search.text.persons.tasks.none.found",
                                                                    args:[currentUser, formattedStartOfYesterday])
                break
            case "myTomorrows":
                result.taskInstanceList = getPersonsTasks(params, currentUser, startOfTomorrow, startOfTomorrow+1)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.persons.tasks.message",
                                                                    args:[currentUser, formattedStartOfTomorrow])
                else
                    result.message = getMessage(code:"task.search.text.persons.tasks.none.found",
                                                                    args:[currentUser, formattedStartOfTomorrow])
                break
            case "myPastWeek":
                result.taskInstanceList = getPersonsTasks(params, currentUser, oneWeekAgo, startOfTomorrow)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.persons.tasks.between.message",
                                                                    args:[currentUser, formattedOneWeekAgo, formattedStartOfToday])
                else
                    result.message = getMessage(code:"task.search.text.persons.tasks.between.none.found",
                                                                    args:[currentUser, formattedOneWeekAgo, formattedStartOfToday])
                break
            case "todays":
                result.taskInstanceList = getTasks(params)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.all.tasks.message",
                                                                    args:[formattedStartOfToday])
                else
                    result.message = getMessage(code:"task.search.text.all.tasks.none.found",
                                                                    args:[formattedStartOfToday])
                break
            case "yesterdays":
                result.taskInstanceList = getTasks(params, startOfYesterday, startOfToday)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.all.tasks.message",
                                                                    args:[formattedStartOfYesterday])
                else
                    result.message = getMessage(code:"task.search.text.all.tasks.none.found",
                                                                    args:[formattedStartOfYesterday])
                break
            case "tomorrows":
                result.taskInstanceList = getTasks(params, startOfTomorrow, startOfTomorrow+1)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.all.tasks.message",
                                                                    args:[formattedStartOfTomorrow])
                else
                    result.message = getMessage(code:"task.search.text.all.tasks.none.found",
                                                                    args:[formattedStartOfTomorrow])
                break
            case "pastWeek":
                result.taskInstanceList = getTasks(params, oneWeekAgo, startOfTomorrow)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.all.tasks.between.message",
                                                                    args:[formattedOneWeekAgo, formattedStartOfToday])
                else
                    result.message = getMessage(code:"task.search.text.all.tasks.between.none.found",
                                                                    args:[formattedOneWeekAgo, formattedStartOfToday])
                break
            case "budgetUnplanned":
                result.taskInstanceList = getBudgetTasks(params, TaskBudgetStatus.read(1), oneWeekAgo, startOfTomorrow)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.budget.unplanned.message",
                                                                    args:[formattedOneWeekAgo, formattedStartOfToday])
                else
                    result.message = getMessage(code:"task.search.text.budget.unplanned.none.found",
                                                                    args:[formattedOneWeekAgo, formattedStartOfToday])
                break
            case "budgetPlanned":
                result.taskInstanceList = getBudgetTasks(params, TaskBudgetStatus.read(2), oneWeekAgo, startOfTomorrow)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.budget.planned.message",
                                                                    args:[formattedOneWeekAgo, formattedStartOfToday])
                else
                    result.message = getMessage(code:"task.search.text.budget.planned.none.found",
                                                                    args:[formattedOneWeekAgo, formattedStartOfToday])
                break
            default:
                //case "plannersRange":
                result.taskInstanceList = getTasks(params, oneWeekAgo, startOfToday+15)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.all.tasks.between.message",
                                                                    args:[formattedOneWeekAgo,
                                                                            g.formatDate(format: "EEE, dd-MMM-yyyy", date: startOfToday+14)])
                else
                    result.message = getMessage(code:"task.search.text.all.tasks.between.none.found",
                                                                    args:[formattedOneWeekAgo,
                                                                            g.formatDate(format: "EEE, dd-MMM-yyyy", date: startOfToday+14)])
                break
        } // switch.

        // Success.
        return result

    } // getQuickSearch

    /**
    * Get all tasks that are not in the trash bin, by default today's tasks.
    * @param params The request params.
    * @param startDate The start date to get tasks for, defaults to the start of today and is inclusive (greater than or equal to).
    * @param endDate The end date to get tasks for, defaults to the start of tomorrow and is exclusive (less than).
    */
    def getTasks(params, startDate=null, endDate=null) {
        def paginateParams = [:]
        paginateParams.max = Math.min(params?.max?.toInteger() ?: 10, paramsMax)
        paginateParams.offset = params?.offset?.toInteger() ?: 0

        def orderBy = ''
        if(params.sort?.contains('.')) // protect against filterpane bug.
            params.sort = null
        if(params.sort && params.order) {
            def sort = "task." + params.sort
            def order = (params.order == "asc") ? "asc" : "desc"
            orderBy = " order by " + sort + ' ' + order
        }
        else
            orderBy = " order by task.taskStatus, task.taskPriority, task.targetStartDate"

        def namedParams = [:]
        namedParams.startDate = startDate ?: dateUtilService.today
        namedParams.endDate = endDate ?: dateUtilService.tomorrow

        def baseQuery = "from Task as task \
                                        where (task.trash = false \
                                                    and task.targetStartDate < :endDate \
                                                    and task.targetCompletionDate >= :startDate \
                                        )"

        def searchQuery = "select distinct task " + baseQuery + orderBy
        def list = Task.executeQuery(searchQuery, namedParams, paginateParams)

        def countQuery = "select count(distinct task) as taskCount " + baseQuery
        def totalCount = Task.executeQuery(countQuery, namedParams)[0].toInteger()

        def taskInstanceList = new PagedResultList(list, totalCount)
        return taskInstanceList
    } // getPTasks()

    /**
    * Get a person's tasks, by default current user and today's tasks.
    * "My tasks and approved tasks that I am assigned to"
    * @param params The request params.
    * @param person The person to get tasks for, defaults to current user.
    * @param startDate The start date to get tasks for, defaults to the start of today and is inclusive (greater than or equal to).
    * @param endDate The end date to get tasks for, defaults to the start of tomorrow and is exclusive (less than).
    */
    def getPersonsTasks(params, person=null, startDate=null, endDate=null) {
        def paginateParams = [:]
        paginateParams.max = Math.min(params?.max?.toInteger() ?: 10, paramsMax)
        paginateParams.offset = params?.offset?.toInteger() ?: 0

        def orderBy = ''
        if(params.sort?.contains('.')) // protect against filterpane bug.
            params.sort = null
        if(params.sort && params.order) {
            def sort = "task." + params.sort
            def order = (params.order == "asc") ? "asc" : "desc"
            orderBy = " order by " + sort + ' ' + order
        }
        else
            orderBy = " order by task.taskStatus, task.taskPriority, task.targetStartDate"

        def namedParams = [:]
        namedParams.person = person ?: authService.currentUser
        namedParams.startDate = startDate ?: dateUtilService.today
        namedParams.endDate = endDate ?: dateUtilService.tomorrow

        def baseQuery = "from Task as task \
                                        left join task.assignedPersons as assignedPersonOfTask \
                                        left join assignedPersonOfTask.person as assignedPerson \
                                        left join task.assignedGroups as assignedGroupOfTask \
                                        left join assignedGroupOfTask.personGroup as personGroup \
                                        left join personGroup.persons as assignedPersonViaGroup \
                                        left join task.taskModifications as taskModification \
                                        left join taskModification.person as createdBy \
                                        left join taskModification.taskModificationType as taskModificationType \
                                        where (task.trash = false \
                                                    and task.targetStartDate < :endDate \
                                                    and task.targetCompletionDate >= :startDate \
                                                    and ( \
                                                        (taskModificationType.id = 1 \
                                                        and createdBy = :person \
                                                        and task.leadPerson = :person) \
                                                        or ( \
                                                            task.approved = true \
                                                            and ( \
                                                                task.leadPerson = :person \
                                                                or assignedPerson = :person \
                                                                or assignedPersonViaGroup = :person \
                                                            ) \
                                                        ) \
                                                    ) \
                                        )"

        def searchQuery = "select distinct task " + baseQuery + orderBy
        def list = Task.executeQuery(searchQuery, namedParams, paginateParams)

        def countQuery = "select count(distinct task) as taskCount " + baseQuery
        def totalCount = Task.executeQuery(countQuery, namedParams)[0].toInteger()

        def taskInstanceList = new PagedResultList(list, totalCount)
        return taskInstanceList
    } // getPersonsTasks()

    /**
    * Get tasks by budget status, by default planned in the last week.
    * @param params The request params.
    * @param budgetStatus Defaults to planned.
    * @param startDate The start date to get tasks for, defaults to the start of today and is inclusive (greater than or equal to).
    * @param endDate The end date to get tasks for, defaults to the start of tomorrow and is exclusive (less than).
    */
    def getBudgetTasks(params, taskBudgetStatus=null, startDate=null, endDate=null) {
        def paginateParams = [:]
        paginateParams.max = Math.min(params?.max?.toInteger() ?: 10, paramsMax)
        paginateParams.offset = params?.offset?.toInteger() ?: 0

        def sort = "task." + (params?.sort ?: "targetStartDate")
        def order = params?.order == "desc" ? "desc" : "asc"
        def orderBy = " order by " + sort + ' ' + order

        def namedParams = [:]
        namedParams.taskBudgetStatus = taskBudgetStatus ?: TaskBudgetStatus.read(2) // Planned.
        namedParams.startDate = startDate ?: dateUtilService.today
        namedParams.endDate = endDate ?: dateUtilService.tomorrow

        def baseQuery = "from Task as task \
                                        where (task.trash = false \
                                                    and task.taskBudgetStatus = :taskBudgetStatus \
                                                    and task.targetStartDate < :endDate \
                                                    and task.targetCompletionDate >= :startDate \
                                        )"

        def searchQuery = "select distinct task " + baseQuery + orderBy
        def list = Task.executeQuery(searchQuery, namedParams, paginateParams)

        def countQuery = "select count(distinct task) as taskCount " + baseQuery
        def totalCount = Task.executeQuery(countQuery, namedParams)[0].toInteger()

        def taskInstanceList = new PagedResultList(list, totalCount)
        return taskInstanceList
    } // getBudgetTasks()

    /**
    * Get work done by person and date.
    * A person ID and date may be specified in params otherwise the current user and today are used.
    * @param params The request params.
    * @returns A map containing entries, totalEntries, startOfDay, person, totalHours, totalMinutes.
    */
    def getWorkDone(params, locale) {
        def result = [:]
        result.person = params.person?.id ? Person.get(params.person.id.toInteger()) : authService.currentUser

        if(params.date_year && params.date_month && params.date_day)
            result.startOfDay = dateUtilService.makeDate(params.date_year, params.date_month, params.date_day)
        else
            result.startOfDay = dateUtilService.today

        result.startOfNextDay = result.startOfDay + 1

        def formattedStartOfDay = g.formatDate(format: "EEE, dd-MMM-yyyy", date: result.startOfDay)

        def getMessage = { Map m ->
            messageSource.getMessage(m.code, m.args == null ? null : m.args.toArray(), locale)
        }

        result.entries = Entry.createCriteria().list() {
            eq("enteredBy", result.person)
            ge("dateDone", result.startOfDay)
            lt("dateDone", result.startOfNextDay)
            entryType {
                eq("id", 3L)
            }
        } // createCriteria

        result.totalEntries = result.entries.size()

        if(result.totalEntries > 0)
            result.message = getMessage(code:"task.search.text.work.done.message",
                                                                args:[result.person, formattedStartOfDay])
        else
            result.message = getMessage(code:"task.search.text.work.done.none.found",
                                                                args:[result.person, formattedStartOfDay])

        result.totalHours = 0
        result.totalMinutes = 0
        result.entries.each() {
            result.totalMinutes += (it.durationHour*60) + it.durationMinute
        }
        result.totalHours = (result.totalMinutes / 60).toInteger()
        result.totalMinutes = result.totalMinutes % 60

        return result
    } // getWorkDone()

} // end class
