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 = [:]
        def currentUser = authService.currentUser
        result.quickSearch = params.quickSearch ?: "searchPlannersRange"

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

        switch (result.quickSearch) {
            case "myTodays":
                result.taskInstanceList = getMyTodays(params)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.my.todays", args:[currentUser])
                else
                    result.message = getMessage(code:"task.search.text.my.todays.none.found", args:[currentUser])
                break
            case "myYesterdays":
                result.taskInstanceList = getMyTodays(params, -1)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.my.yesterdays", args:[currentUser])
                else
                    result.message = getMessage(code:"task.search.text.my.yesterdays.none.found", args:[currentUser])
                break
            case "myTomorrows":
                result.taskInstanceList = getMyTodays(params, 1)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.my.tomorrows", args:[currentUser])
                else
                    result.message = getMessage(code:"task.search.text.my.tomorrows.none.found", args:[currentUser])
                break
            case "myPastWeek":
                result.taskInstanceList = getMyPastWeek(params)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.my.past.week", args:[currentUser])
                else
                    result.message = getMessage(code:"task.search.text.my.past.week.none.found", args:[currentUser])
                break
            case "todays":
                result.taskInstanceList = getTodays(params)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.todays")
                else
                    result.message = getMessage(code:"task.search.text.todays.none.found")
                break
            case "yesterdays":
                result.taskInstanceList = getTodays(params, -1)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.yesterdays")
                else
                    result.message = getMessage(code:"task.search.text.yesterdays.none.found")
                break
            case "tomorrows":
                result.taskInstanceList = getTodays(params, 1)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.tomorrows")
                else
                    result.message = getMessage(code:"task.search.text.tomorrows.none.found")
                break
            case "pastWeek":
                result.taskInstanceList = getPastWeek(params)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.past.week")
                else
                    result.message = getMessage(code:"task.search.text.past.week.none.found")
                break
            case "budgetUnplanned":
                result.taskInstanceList = getBudgetUnplanned(params)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.budget.unplanned")
                else
                    result.message = getMessage(code:"task.search.text.budget.unplanned.none.found")
                break
            case "budgetPlanned":
                result.taskInstanceList = getBudgetPlanned(params)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.budget.planned")
                else
                    result.message = getMessage(code:"task.search.text.budget.planned.none.found")
                break
            default:
                result.taskInstanceList = getPlannersRange(params)
                if(result.taskInstanceList.totalCount > 0)
                    result.message = getMessage(code:"task.search.text.planners.range")
                else
                    result.message = getMessage(code:"task.search.text.planners.range.none.found")
                break
        } // switch.

        // Success.
        return result

    } // getQuickSearch

    /**
    * Get all tasks that are not in the trash, by default today's tasks.
    * @param params The request params.
    * @param dayAdjustment The number of days to adjust from today, defaults to 0.
    */
    def getTodays(params, dayAdjustment=0) {
        params.max = Math.min(params?.max?.toInteger() ?: 10, paramsMax)
        params.offset = params?.offset?.toInteger() ?: 0
        params.sort = params?.sort ?: "attentionFlag"
        params.order = params?.order ?: "desc"

        def taskInstanceList = Task.createCriteria().list(
            max: params.max,
            offset: params.offset,
            sort: params.sort, 
            order: params.order) {
                lt("targetStartDate", dateUtilService.tomorrow+dayAdjustment)
                ge("targetCompletionDate", dateUtilService.today+dayAdjustment)
                eq("trash", false)
            } // createCriteria
    }

    /**
    * Get current user's tasks, by default today's tasks.
    * "Approved tasks where I am the lead or have been assigned"
    * @param params The request params.
    * @param dayAdjustment The number of days to adjust from today, defaults to 0.
    */
    def getMyTodays(params, dayAdjustment=0) {
        def paginateParams = [:]
        paginateParams.max = Math.min(params?.max?.toInteger() ?: 10, paramsMax)
        paginateParams.offset = params?.offset?.toInteger() ?: 0

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

        def namedParams = [:]
        namedParams.currentUser = authService.currentUser
        namedParams.startOfDay = dateUtilService.today+dayAdjustment
        namedParams.startOfNextDay = dateUtilService.tomorrow+dayAdjustment

        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 \
                                        where (task.trash = false \
                                                    and task.approved = true \
                                                    and ( \
                                                            task.targetStartDate < :startOfNextDay \
                                                            and task.targetCompletionDate >= :startOfDay \
                                                        ) \
                                                    and ( \
                                                            task.leadPerson = :currentUser \
                                                            or assignedPerson = :currentUser \
                                                            or assignedPersonViaGroup = :currentUser \
                                                            ) \
                                                    )"

        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
    } // getMyTodays

    /**
    * Get all tasks that are not in the trash, during the past week.
    * @param params The request params.
    */
    def getPastWeek(params) {
        params.max = Math.min(params?.max?.toInteger() ?: 10, paramsMax)
        params.offset = params?.offset?.toInteger() ?: 0
        params.sort = params?.sort ?: "attentionFlag"
        params.order = params?.order ?: "desc"

        def taskInstanceList = Task.createCriteria().list(
            max: params.max,
            offset: params.offset,
            sort: params.sort, 
            order: params.order) {
                lt("targetStartDate", dateUtilService.tomorrow)
                ge("targetCompletionDate", dateUtilService.oneWeekAgo)
                eq("trash", false)
            } // createCriteria
    }

    /**
    * Get current user's tasks in the past week.
    * "Approved tasks where I am the lead or have been assigned"
    * @param params The request params.
    */
    def getMyPastWeek(params) {
        def paginateParams = [:]
        paginateParams.max = Math.min(params?.max?.toInteger() ?: 10, paramsMax)
        paginateParams.offset = params?.offset?.toInteger() ?: 0

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

        def namedParams = [:]
        namedParams.currentUser = authService.currentUser
        namedParams.startOfDay = dateUtilService.oneWeekAgo
        namedParams.startOfNextDay = 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 \
                                        where (task.trash = false \
                                                    and task.approved = true \
                                                    and ( \
                                                            task.targetStartDate < :startOfNextDay \
                                                            and task.targetCompletionDate >= :startOfDay \
                                                        ) \
                                                    and ( \
                                                            task.leadPerson = :currentUser \
                                                            or assignedPerson = :currentUser \
                                                            or assignedPersonViaGroup = :currentUser \
                                                            ) \
                                                    )"

        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
    } // getMyTodays

    /**
    * "Tasks with budget status of Planned, in the past week."
    * @param params The request params.
    */
    def getBudgetPlanned(params) {
        params.max = Math.min(params?.max?.toInteger() ?: 10, paramsMax)
        params.offset = params?.offset?.toInteger() ?: 0
        params.sort = params?.sort ?: "targetStartDate"
        params.order = params?.order ?: "asc"

        def taskInstanceList = Task.createCriteria().list(
            max: params.max,
            offset: params.offset,
            sort: params.sort,
            order: params.order) {
                eq("taskBudgetStatus", TaskBudgetStatus.read(2))
                lt("targetStartDate", dateUtilService.tomorrow)
                ge("targetCompletionDate", dateUtilService.oneWeekAgo)
                eq("trash", false)
            } // createCriteria
    }

    /**
    * "Tasks with budget status of Unplanned, in the past week."
    * @param params The request params.
    */
    def getBudgetUnplanned(params) {
        params.max = Math.min(params?.max?.toInteger() ?: 10, paramsMax)
        params.offset = params?.offset?.toInteger() ?: 0
        params.sort = params?.sort ?: "targetStartDate"
        params.order = params?.order ?: "asc"

        def taskInstanceList = Task.createCriteria().list(
            max: params.max,
            offset: params.offset,
            sort: params.sort,
            order: params.order) {
                eq("taskBudgetStatus", TaskBudgetStatus.read(1))
                lt("targetStartDate", dateUtilService.tomorrow)
                ge("targetCompletionDate", dateUtilService.oneWeekAgo)
                eq("trash", false)
            } // createCriteria
    }

    /**
    * "Tasks in the past week and two weeks ahead."
    * @param params The request params.
    */
    def getPlannersRange(params) {
        params.max = Math.min(params?.max?.toInteger() ?: 10, paramsMax)
        params.offset = params?.offset?.toInteger() ?: 0
        params.sort = params?.sort ?: "targetStartDate"
        params.order = params?.order ?: "asc"

        def taskInstanceList = Task.createCriteria().list(
            max: params.max,
            offset: params.offset,
            sort: params.sort,
            order: params.order) {
                ge("targetStartDate", dateUtilService.oneWeekAgo)
                lt("targetStartDate", dateUtilService.today + 15)
                eq("trash", false)
            } // createCriteria
    }

    /**
    * Get work done by person and date.
    * A person ID and date may be specified in params otherwise the currentUser 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
    }

} // end class
