import net.kromhouts.HqlBuilder

/**
* Service class that encapsulates the business logic for Asset Reports.
*/
class AssetReportService {

    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 assets and their details.
    * @param params The request params, may contain params to specify the search.
    * @param locale The locale to use when generating result.message.
    */
    def getAssetRegister(params, locale) {
        def result = [:]

        result.section = Section.get(params.section.id.toLong())
        result.site = result.section.site

        // Inner join used to return only attribTypes that are used by AssetExtendedAttributes.
        // So the result is only asset extendedAttributeTypes.
//         def attribTypesQ = new HqlBuilder().query {
//             select 'distinct attribT.name'
//             from 'AssetExtendedAttribute attrib',
//                     'join attrib.extendedAttributeType as attribT'
//             order 'by attribT.name asc'
//         }

        // All active ExtendedAttributes.
        def attribTypesQ = new HqlBuilder().query {
            select 'distinct attribT.name'
            from 'ExtendedAttributeType attribT'
            where 'attribT.isActive = true'
            order 'by attribT.name asc'
        }
        result.attribTypes = ExtendedAttributeType.executeQuery(attribTypesQ.query, attribTypesQ.namedParams)

        // A result is returned for every asset and for any extended attributes.
        def q = new HqlBuilder().query {
            select 'new map(asset.name as name',
                        'asset.description as description',
                        'asset.comment as comment',
                        'attribT.name as attribType',
                        'attrib.value as attribValue)'
            from 'Asset asset',
                    'left join asset.assetExtendedAttributes as attrib',
                    'left join attrib.extendedAttributeType as attribT'
            where 'asset.section = :section'
                    namedParams.section = result.section
            order 'by asset.name asc, attribT.name asc'
        }
        def assetResults = Asset.executeQuery(q.query, q.namedParams)

        // Build the report table row for each asset.
        // Rows are keyed by asset.name and the value is a Map of the attributes.
        def rows = [:]
        assetResults.each { assetResult ->
            // Create row if it does not exist yet.
            if(!rows.containsKey(assetResult.name)) {
                rows[assetResult.name] = ['name':assetResult.name,
                                                            'description':assetResult.description,
                                                            'comment':assetResult.comment]

                // Add all attribType columns.
                result.attribTypes.each { column ->
                    rows[assetResult.name][column] = ' '
                }
            }

            // Assign value to column.
            rows[assetResult.name][assetResult.attribType] = assetResult.attribValue
        }

        // The value of each row is the dataList used by the report table.
        result.dataList = rows.collect {it.value}

        // Success.
        return result

    } // getAssetRegister

    /**
    * Selects and returns an asset (or all) and its details.
    * @param params The request params, may contain params to specify the search.
    * @param locale The locale to use when generating result.message.
    */
    def getAssetDetail(params, locale) {
        //def result = [:]
        def result

        //result.summaryOfCalculationMethod = ''

        // A result is returned for every asset and for any extended attributes.
        // The report then groups by asset.name
        def q = new HqlBuilder().query {
            select 'new map(asset.name as name',
                        'asset.description as description',
                        'asset.comment as comment',
                        'attribT.name as attribType',
                        'attrib.value as attribValue)'
            from 'Asset asset',
                    'left join asset.assetExtendedAttributes as attrib',
                    'left join attrib.extendedAttributeType as attribT'
            if(params.section instanceof Section) {
                namedParams.section = params.section
                where 'asset.section = :section'
            }
            order 'by asset.name asc, attribT.name asc'
        }

        // result.dataList = Asset.list()
        result = Asset.executeQuery(q.query, q.namedParams)

        // Success.
        return result

    } // getAssetDetail

    /**
    * Selects and returns level 1 sub items (aka machines or equipment) and their details.
    * @param params The request params, may contain params to specify the search.
    * @param locale The locale to use when generating result.message.
    */
    def getEquipmentRegister(params, locale) {
        def result = [:]

        def fail = { Map m ->
            result.error = [ code: m.code, args: [] ]
            return result
        }

        result.section = Section.get(params.section.id.toLong())
        result.site = result.section.site

        result.startDate = params.startDate ?: dateUtilService.oneWeekAgo
        result.endDate = params.endDate ?: dateUtilService.today
        // Auto swap date range.
        if(result.startDate > result.endDate) {
            def tempStartDate = result.startDate
            result.startDate = result.endDate
            result.endDate = tempStartDate
        }

        result.startDateString = g.formatDate(format: "EEE, dd-MMM-yyyy", date: result.startDate)
        result.endDateString = g.formatDate(format: "EEE, dd-MMM-yyyy", date: result.endDate)

        // Inner join used to return only attribTypes that are used by AssetSubItemExtendedAttributes.
        // So the result is only assetSubItem extendedAttributeTypes.
//         def attribTypesQ = new HqlBuilder().query {
//             select 'distinct attribT.name'
//             from 'AssetSubItemExtendedAttribute attrib',
//                     'join attrib.extendedAttributeType as attribT'
//             order 'by attribT.name asc'
//         }

        // All active ExtendedAttributes.
        def attribTypesQ = new HqlBuilder().query {
            select 'distinct attribT.name'
            from 'ExtendedAttributeType attribT'
            where 'attribT.isActive = true'
            order 'by attribT.name asc'
        }
        result.attribTypes = ExtendedAttributeType.executeQuery(attribTypesQ.query, attribTypesQ.namedParams)

        // A useful list of assets without subItems to be given to the user.
        def assetsWithoutEquipmentQ = new HqlBuilder().query {
            select 'distinct asset'
            from 'Asset asset',
                    'left join asset.assetSubItems as assetSubItem'
            where 'assetSubItem = null'
                namedParams.section = result.section
                and 'asset.section = :section'
        }
        result.assetsWithoutEquipment = Asset.executeQuery(assetsWithoutEquipmentQ.query, assetsWithoutEquipmentQ.namedParams)

        // Subquery to count regulatory tasks.
        def regulatoryTaskCountQ = new HqlBuilder().query {

            select 'count (distinct task)'
            from 'Task as task',
                    'left join task.associatedAssets as associatedAsset'
            where 'task.regulatoryRequirement = true'
                and 'task.targetStartDate < :endDate'
                and 'task.targetCompletionDate >= :startDate'
                and '(task.primaryAsset.id = asset.id or associatedAsset.id = asset.id)'
                and 'task.trash = false'
        }

        def totalRegulatoryTaskCountQ = regulatoryTaskCountQ.query

        regulatoryTaskCountQ.and 'task.taskStatus.id = 3'
        def completedRegulatoryTaskCountQ = regulatoryTaskCountQ.query

        // A result is returned for every level 1 assetSubItem and for any extended attributes.
        def q = new HqlBuilder().query {

            select 'new map(asset.name as assetName',
                        'assetSubItem.name as name',
                        'assetSubItem.description as description',
                        'assetSubItem.comment as comment',
                        "0 as totalRegulatoryTaskCount",
                        "0 as completedRegulatoryTaskCount",
                        'attribT.name as attribType',
                        'attrib.value as attribValue)'
            from 'AssetSubItem assetSubItem',
                    'inner join assetSubItem.assets as asset',
                    'left join assetSubItem.assetSubItemExtendedAttributes as attrib',
                    'left join attrib.extendedAttributeType as attribT'
            where 'asset != null' // ensure that only level 1 assetSubItems are returned.
                namedParams.section = result.section
                and 'asset.section = :section'
            order 'by asset.name asc, assetSubItem.name asc, attribT.name asc'
        }
        def equipmentResults = AssetSubItem.executeQuery(q.query, q.namedParams)

        // A result is returned for every asset and for any extended attributes.
        def assetResultsQ = new HqlBuilder().query {

            // Subquery namedParams.
            namedParams.startDate = result.startDate
            namedParams.endDate = result.endDate+1

            select 'new map(asset.name as assetName',
                        "'   Asset Details' as name", // Place holder 'equipment' name, 3 leading spaces for sorting.
                        'asset.description as description',
                        'asset.comment as comment',
                        "($totalRegulatoryTaskCountQ) as totalRegulatoryTaskCount",
                        "($completedRegulatoryTaskCountQ) as completedRegulatoryTaskCount",
                        'attribT.name as attribType',
                        'attrib.value as attribValue)'
            from 'Asset asset',
                    'left join asset.assetExtendedAttributes as attrib',
                    'left join attrib.extendedAttributeType as attribT'
            where 'asset.section = :section'
                    namedParams.section = result.section
            order 'by asset.name asc, attribT.name asc'
        }
        def assetResults = Asset.executeQuery(assetResultsQ.query, assetResultsQ.namedParams)

        // Add asset details to equipmentResults.
        equipmentResults.addAll(assetResults)
        equipmentResults.sort { p1, p2 -> p1.assetName.compareToIgnoreCase(p2.assetName) ?: p1.name.compareToIgnoreCase(p2.name) }

        // Build the report table rows.
        // Rows are keyed by equipmentResult.assetName+equipmentResult.name` while the value is a Map of the attributes.
        // The report table then groups by assetName.
        def rows = [:]
        equipmentResults.each { equipmentResult ->

            def rowKey = equipmentResult.assetName+equipmentResult.name

            // Create new row if it does not exist yet.
            if(!rows.containsKey(rowKey)) {
                rows[rowKey] = ['assetName': equipmentResult.assetName,
                                            'name':equipmentResult.name,
                                            'description':equipmentResult.description,
                                            'comment':equipmentResult.comment,
                                            'Regulatory Task Completion': ' ']

                // Add all attribType columns.
                result.attribTypes.each { column ->
                    rows[rowKey][column] = ' '
                }

                // Caluculate and assign RegulatoryTaskCompletion, only for Assets.
                if(params.calculateRegulatoryTaskCompletion) {

                    if(equipmentResult.totalRegulatoryTaskCount) {
                        def percentComplete = (equipmentResult.completedRegulatoryTaskCount / equipmentResult.totalRegulatoryTaskCount)*100
                        rows[rowKey]['Regulatory Task Completion'] = "${percentComplete.toInteger()}% (${equipmentResult.completedRegulatoryTaskCount}/${equipmentResult.totalRegulatoryTaskCount})"
                    }
                    else if(equipmentResult.name == '   Asset Details')
                        rows[rowKey]['Regulatory Task Completion'] = 'N/A'
                }

            } // Create new row.

            // Assign value to column.
            rows[rowKey][equipmentResult.attribType] = equipmentResult.attribValue
        } // each.

        // The value of each row is the dataList used by the report table.
        result.dataList = rows.collect {it.value}
        // Print formatting, since we are done with these as objects.
        result.attribTypes = result.attribTypes.join(', ')
        result.assetsWithoutEquipment = result.assetsWithoutEquipment.collect {it.name}.join(', ')

        // Success.
        return result

    } // getEquipmentRegister

    /**
    * Selects and returns assets regulatory requirements as specified in recurring regulatory tasks.
    * @param params The request params, may contain params to specify the search.
    * @param locale The locale to use when generating result.message.
    */
    def getRegulatoryRequirements(params, locale) {
        def result = [:]

        def fail = { Map m ->
            result.error = [ code: m.code, args: [] ]
            return result
        }

        result.section = Section.get(params.section.id.toLong())
        result.site = result.section.site

        result.startDate = params.startDate ?: dateUtilService.oneWeekAgo
        result.endDate = params.endDate ?: dateUtilService.today
        // Auto swap date range.
        if(result.startDate > result.endDate) {
            def tempStartDate = result.startDate
            result.startDate = result.endDate
            result.endDate = tempStartDate
        }

        result.startDateString = g.formatDate(format: "EEE, dd-MMM-yyyy", date: result.startDate)
        result.endDateString = g.formatDate(format: "EEE, dd-MMM-yyyy", date: result.endDate)

        result.summary = "This report only selects primary assets and not associated assets. \n"
        result.summary += "Tasks must have recurrence enabled and regulatory requirement set."

        // Subquery to count subTasks..
        def subTaskQ = new HqlBuilder().query {
            select 'count(subTask)'
            from 'task.subTasks as subTask'
            where 'subTask.trash = false'
                and 'subTask.targetStartDate < :endDate'
                and 'subTask.targetCompletionDate >= :startDate'
        }
        def subTaskTotalQ = subTaskQ.query

        subTaskQ.and 'subTask.taskStatus.id = 3' // Complete.
        def subTaskCompletedQ = subTaskQ.query

        def regulatoryTaskQ = new HqlBuilder().query {
            select 'new map(primaryAsset.name as assetName',
                        'primaryAsset.description as assetDescription',
                        'primaryAsset.isActive as assetIsActive',
                        'task.id as taskId',
                        'task.description as taskDescription',
                        "($subTaskTotalQ) as subTaskTotalCount",
                        "($subTaskCompletedQ) as subTaskCompletedCount)"
                        namedParams.startDate = result.startDate
                        namedParams.endDate = result.endDate
            from 'Task task',
                    'left join task.primaryAsset as primaryAsset',
                    'left join task.taskRecurringSchedule as taskRecurringSchedule'
            where 'task.regulatoryRequirement = true'
                and 'taskRecurringSchedule.enabled = true'
                and 'task.trash = false'
                        namedParams.sectionId = result.section.id
                and 'primaryAsset.section.id = :sectionId'
        }
        result.tasks = Task.executeQuery(regulatoryTaskQ.query, regulatoryTaskQ.namedParams)

        // Build the report table row for each task.
        result.tasks.each { task ->

            // Caluculate percentages and build description.
            def percentComplete
            def completionFigures
            if(task.subTaskTotalCount) {
                percentComplete = (task.subTaskCompletedCount / task.subTaskTotalCount) * 100
                task.completionFigures = "${percentComplete.toInteger()}% ($task.subTaskCompletedCount/$task.subTaskTotalCount)"
            }
            else
                task.completionFigures = '0 sub tasks in date range'
        } // tasks.each

        result.dataList = result.tasks
        result.dataList.sort { p1, p2 -> p1.assetName.compareToIgnoreCase(p2.assetName) }

        // Success.
        return result

    } // getRegulatoryRequirements

    /**
    * Selects and returns assets mandatory requirements as specified in recurring mandatory tasks.
    * @param params The request params, may contain params to specify the search.
    * @param locale The locale to use when generating result.message.
    */
    def getMandatoryRequirements(params, locale) {
        def result = [:]

        def fail = { Map m ->
            result.error = [ code: m.code, args: [] ]
            return result
        }

        result.section = Section.get(params.section.id.toLong())
        result.site = result.section.site

        result.startDate = params.startDate ?: dateUtilService.oneWeekAgo
        result.endDate = params.endDate ?: dateUtilService.today
        // Auto swap date range.
        if(result.startDate > result.endDate) {
            def tempStartDate = result.startDate
            result.startDate = result.endDate
            result.endDate = tempStartDate
        }

        result.startDateString = g.formatDate(format: "EEE, dd-MMM-yyyy", date: result.startDate)
        result.endDateString = g.formatDate(format: "EEE, dd-MMM-yyyy", date: result.endDate)

        result.summary = "This report only selects primary assets and not associated assets. \n"
        result.summary += "Tasks must have recurrence enabled and mandatory requirement set."

        // Subquery to count subTasks..
        def subTaskQ = new HqlBuilder().query {
            select 'count(subTask)'
            from 'task.subTasks as subTask'
            where 'subTask.trash = false'
                and 'subTask.targetStartDate < :endDate'
                and 'subTask.targetCompletionDate >= :startDate'
        }
        def subTaskTotalQ = subTaskQ.query

        subTaskQ.and 'subTask.taskStatus.id = 3' // Complete.
        def subTaskCompletedQ = subTaskQ.query

        def mandatoryTaskQ = new HqlBuilder().query {
            select 'new map(primaryAsset.name as assetName',
                        'primaryAsset.description as assetDescription',
                        'primaryAsset.isActive as assetIsActive',
                        'task.id as taskId',
                        'task.description as taskDescription',
                        "($subTaskTotalQ) as subTaskTotalCount",
                        "($subTaskCompletedQ) as subTaskCompletedCount)"
                        namedParams.startDate = result.startDate
                        namedParams.endDate = result.endDate
            from 'Task task',
                    'left join task.primaryAsset as primaryAsset',
                    'left join task.taskRecurringSchedule as taskRecurringSchedule'
            where 'task.mandatoryRequirement = true'
                and 'taskRecurringSchedule.enabled = true'
                and 'task.trash = false'
                        namedParams.sectionId = result.section.id
                and 'primaryAsset.section.id = :sectionId'
        }
        result.tasks = Task.executeQuery(mandatoryTaskQ.query, mandatoryTaskQ.namedParams)

        // Build the report table row for each task.
        result.tasks.each { task ->

            // Caluculate percentages and build description.
            def percentComplete
            def completionFigures
            if(task.subTaskTotalCount) {
                percentComplete = (task.subTaskCompletedCount / task.subTaskTotalCount) * 100
                task.completionFigures = "${percentComplete.toInteger()}% ($task.subTaskCompletedCount/$task.subTaskTotalCount)"
            }
            else
                task.completionFigures = '0 sub tasks in date range'
        } // tasks.each

        result.dataList = result.tasks
        result.dataList.sort { p1, p2 -> p1.assetName.compareToIgnoreCase(p2.assetName) }

        // Success.
        return result

    } // getMandatoryRequirements

} // end class
