/**
 * Copyright 2017 - 2019  The Hyve B.V.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */ import { Injectable } from '@angular/core' ; import { Concept } from '../models/constraint-models/concept' ; import { Observable , Subject } from 'rxjs' ; import { TreeNodeService } from './tree-node.service' ; import { ConstraintService } from './constraint.service' ; import { Constraint } from '../models/constraint-models/constraint' ; import { CombinationConstraint } from '../models/constraint-models/combination-constraint' ; import { CombinationState } from '../models/constraint-models/combination-state' ; import { CountItem } from '../models/aggregate-models/count-item' ; import { CountService } from './count.service' ; import { GbTreeNode } from '../models/tree-node-models/gb-tree-node' ; import { CohortService } from './cohort.service' ; import { TreeNodeHelper } from '../utilities/tree-node-helper' ; import { FormatHelper } from '../utilities/format-helper' ; @ Injectable ( { providedIn : 'root' } ) export class VariableService { private _variables : Concept [ ] = [ ] ; private _variablesUpdated : Subject < Concept [ ] > = new Subject < Concept [ ] > ( ) ; private _isUpdatingVariables = false ; private _variablesTree : GbTreeNode [ ] = [ ] ; private _categorizedVariablesTree : GbTreeNode [ ] = [ ] ; private _selectedVariablesTree : GbTreeNode [ ] = [ ] ; private _selectedVariablesTreeUpdated : Subject < GbTreeNode [ ] > = new Subject < GbTreeNode [ ] > ( ) ; public variablesDragDropScope = 'PrimeNGVariablesDragDropContext' ; constructor ( private treeNodeService , private constraintService , private cohortService , private countService ) { this . subscribeToVariableChanges ( ) ; } private subscribeToVariableChanges ( ) { Observable . combineLatest ( this . countService . selectedConceptCountMapUpdated . asObservable ( ) , this . treeNodeService . treeNodesUpdated . asObservable ( ) ) . subscribe ( ( ) => { this . updateVariables ( ) ; } ) ; } private updateCategorizedVariablesTree ( variablesTree ) { this . categorizedVariablesTree = [ ] ; let variableNodesByType = new Map < string , GbTreeNode [ ] > ( ) ; let isVariableNode = TreeNodeHelper . isVariableNode ; groupDescendantVariableNodes ( variablesTree ) ; new Map < string , GbTreeNode [ ] > ( [ ... variableNodesByType . entries ( ) ] . sort ( ) ) . forEach ( ( nodes , type ) => { let typeNode = { name : type , children : nodes , expanded : true } as GbTreeNode ; this . categorizedVariablesTree . push ( typeNode ) ; } ) ; function groupDescendantVariableNodes ( tree ) { tree . forEach ( ( node ) => { if ( isVariableNode ( node ) ) { if ( ! variableNodesByType . has ( node . type ) ) { variableNodesByType . set ( node . type , [ ] ) ; } variableNodesByType . get ( node . type ) . push ( node ) ; } if ( node . children ) { groupDescendantVariableNodes ( node . children ) ; } } ) ; } } param selectedNodes public updateSelectedVariablesWithTreeNodes ( selectedNodes ) { const codes = [ ] ; selectedNodes . forEach ( ( node ) => { const code = node . conceptCode ; if ( TreeNodeHelper . isVariableNode ( node ) && code && ! codes . includes ( code ) ) { codes . push ( code ) ; } } ) ; this . variables . forEach ( ( v ) => { v . selected = codes . includes ( v . code ) ; } ) ; this . selectedVariablesTreeUpdated . next ( selectedNodes ) ; } public updateVariables ( ) { return new Promise < any > ( ( resolve , reject ) => { this . isUpdatingVariables = true ; if ( this . isTreeNodesLoading ) { window . setTimeout ( ( function ( ) { this . updateVariables ( resolve ) ; } ) . bind ( this ) , 500 ) ; } else { this . variables . length = 0 ; const codes = Array . from ( this . countService . selectedConceptCountMap . keys ( ) ) ; this . constraintService . concepts . forEach ( ( concept ) => { if ( codes . includes ( concept . code ) ) { concept . counts = this . countService . selectedConceptCountMap . get ( concept . code ) ; this . variables . push ( concept ) ; } } ) ; this . variablesUpdated . next ( this . variables ) ; this . updateVariablesTree ( ) ; this . updateCategorizedVariablesTree ( this . variablesTree ) ; this . isUpdatingVariables = false ; resolve ( true ) ; } } ) ; } public setVariableSelection ( b ) { this . variables . forEach ( ( variable ) => { variable . selected = b ; } ) ; this . selectAllVariablesTree ( b ) ; } public identifyDraggedElement ( ) { if ( this . treeNodeService . selectedTreeNode ) { return this . treeNodeService . getConceptFromTreeNode ( this . treeNodeService . selectedTreeNode ) ; } return null ; } /**
     * Update the tree data for rendering the tree nodes in variables panel,
     * based on a given set of concept codes as filtering criteria.
     */ public updateVariablesTree ( ) { if ( this . treeNodeService . treeNodesCopy . length === 0 ) { this . treeNodeService . treeNodesCopy = TreeNodeHelper . copyTreeNodes ( this . treeNodeService . treeNodes ) ; } this . variablesTree = this . updateVariablesTreeRecursion ( this . treeNodeService . treeNodesCopy ) ; this . selectAllVariablesTree ( true ) ; } private formatNodeWithCounts ( node , countItem ) { let subjectCount = FormatHelper . formatCountNumber ( countItem . subjectCount ) ; let countsText = `sub: ${ subjectCount }` ; if ( this . countService . showObservationCounts ) { countsText += `, obs: ${ FormatHelper . formatCountNumber ( countItem . observationCount ) }` ; } node . subjectCount = subjectCount ; node . label = `${ node . name } (${ countsText })` ; } param node private getVariableCounts ( node ) { let conceptMap = node . studyId && this . countService . selectedStudyConceptCountMap ? this . countService . selectedStudyConceptCountMap . get ( node . studyId ) : null ; if ( conceptMap && conceptMap . size > 0 ) { return conceptMap . get ( node . conceptCode ) ; } else { return this . countService . selectedConceptCountMap ? this . countService . selectedConceptCountMap . get ( node . conceptCode ) : null ; } } param nodes private updateVariablesTreeRecursion ( nodes ) { let result = [ ] ; for ( let node of nodes ) { if ( node . children && node . children . length > 0 ) { let children = this . updateVariablesTreeRecursion ( node . children ) ; node = TreeNodeHelper . copyTreeNodeUpward ( node ) ; node . expanded = TreeNodeHelper . depthOfTreeNode ( node ) <= 2 ; node . children = children ; } let hasPositiveSubjectCount = false ; if ( TreeNodeHelper . isTreeNodeStudy ( node ) ) { const countItem = this . countService . selectedStudyCountMap ? this . countService . selectedStudyCountMap . get ( node . studyId ) : null ; if ( countItem && countItem . subjectCount > 0 ) { this . formatNodeWithCounts ( node , countItem ) ; hasPositiveSubjectCount = true ; } } else if ( TreeNodeHelper . isVariableNode ( node ) ) { const countItem = this . getVariableCounts ( node ) ; if ( countItem && countItem . subjectCount > 0 ) { this . formatNodeWithCounts ( node , countItem ) ; if ( ! ( 'expanded' in node ) ) { node . expanded = false ; } node . styleClass = '' ; hasPositiveSubjectCount = true ; } } if ( hasPositiveSubjectCount || ( node . children && node . children . length > 0 ) ) { result . push ( node ) ; } } return result ; } public selectAllVariablesTree ( b ) { if ( b ) { let flattenedVariablesTreeData = [ ] ; TreeNodeHelper . flattenTreeNodes ( this . variablesTree , flattenedVariablesTreeData ) ; this . selectedVariablesTree = flattenedVariablesTreeData ; } else { this . selectedVariablesTree = [ ] ; } } public updateVariableSelection ( changedNode , isChecked ) { let variableNodes = [ ] ; TreeNodeHelper . getAllVariablesFromTreeNode ( changedNode , variableNodes ) ; if ( isChecked ) { this . selectVariablesTreeByFields ( this . variablesTree , variableNodes . map ( vn => vn . conceptCode ) , [ 'conceptCode' ] ) ; } else { this . unselectVariablesTreeByFields ( this . variablesTree , variableNodes . map ( vn => vn . conceptCode ) , [ 'conceptCode' ] ) ; } this . updateSelectedVariablesWithTreeNodes ( this . selectedVariablesTree ) ; } public selectVariablesTreeByFields ( nodes , values , fields ) { nodes . forEach ( ( node ) => { if ( node ) { const val = fields . length < 2 ? node [ fields [ 0 ] ] : ( node [ fields [ 0 ] ] || { } ) [ fields [ 1 ] ] ; if ( values . includes ( val ) && ! this . selectedVariablesTree . includes ( node ) ) { TreeNodeHelper . addNodeToSelectedNodes ( node , this . selectedVariablesTree ) ; TreeNodeHelper . updateParentNodesSelectionRecursively ( node . parent , this . selectedVariablesTree ) ; } if ( node . children ) { TreeNodeHelper . updateParentForAllChildren ( node ) ; this . selectVariablesTreeByFields ( node . children , values , fields ) ; } } } ) ; } public unselectVariablesTreeByFields ( nodes , values , fields ) { nodes . forEach ( ( node ) => { if ( node ) { const val = fields . length < 2 ? node [ fields [ 0 ] ] : ( node [ fields [ 0 ] ] || { } ) [ fields [ 1 ] ] ; if ( values . includes ( val ) && this . selectedVariablesTree . includes ( node ) ) { TreeNodeHelper . removeNodeFromSelectedNodes ( node , this . selectedVariablesTree ) ; TreeNodeHelper . updateParentNodesSelectionRecursively ( node . parent , this . selectedVariablesTree ) ; } if ( node . children ) { TreeNodeHelper . updateParentForAllChildren ( node ) ; this . unselectVariablesTreeByFields ( node . children , values , fields ) ; } } } ) ; } public importVariablesByNames ( names ) { this . selectVariablesTreeByFields ( this . variablesTree , names , [ 'metadata' , 'item_name' ] ) ; this . updateSelectedVariablesWithTreeNodes ( this . selectedVariablesTree ) ; } public importVariablesByPaths ( paths ) { this . selectVariablesTreeByFields ( this . variablesTree , paths , [ 'fullName' ] ) ; this . updateSelectedVariablesWithTreeNodes ( this . selectedVariablesTree ) ; } get combination ( ) : Constraint { return new CombinationConstraint ( [ this . cohortService . allSelectedCohortsConstraint , this . constraintService . variableConstraint ( this . variables ) ] , CombinationState . And ) ; } get isTreeNodesLoading ( ) : boolean { return ! this . treeNodeService . isTreeNodesLoadingCompleted ; } get variables ( ) : Concept [ ] { return this . _variables ; } set variables ( value ) { this . _variables = value ; } get variablesUpdated ( ) : Subject < Concept [ ] > { return this . _variablesUpdated ; } set variablesUpdated ( value ) { this . _variablesUpdated = value ; } get categorizedVariablesTree ( ) : GbTreeNode [ ] { return this . _categorizedVariablesTree ; } set categorizedVariablesTree ( value ) { this . _categorizedVariablesTree = value ; } get isUpdatingVariables ( ) : boolean { return this . _isUpdatingVariables ; } set isUpdatingVariables ( value ) { this . _isUpdatingVariables = value ; } get variablesTree ( ) : GbTreeNode [ ] { return this . _variablesTree ; } set variablesTree ( value ) { this . _variablesTree = value ; } get selectedVariablesTree ( ) : GbTreeNode [ ] { return this . _selectedVariablesTree ; } set selectedVariablesTree ( value ) { this . _selectedVariablesTree = value ; this . updateSelectedVariablesWithTreeNodes ( value ) ; } get selectedVariablesTreeUpdated ( ) : Subject < GbTreeNode [ ] > { return this . _selectedVariablesTreeUpdated ; } set selectedVariablesTreeUpdated ( value ) { this . _selectedVariablesTreeUpdated = value ; } get numSelectedVariables ( ) : number { return this . variables . filter ( ( variable ) => { return variable . selected ; } ) . length ; } }