/**
 * 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 { ApplicationRef , Injectable } from '@angular/core' ; import { CrossTable } from '../models/table-models/cross-table' ; import { CategoricalAggregate } from '../models/aggregate-models/categorical-aggregate' ; import { Constraint } from '../models/constraint-models/constraint' ; import { GbDraggableCellComponent } from '../modules/gb-analysis-module/cross-table-components/gb-draggable-cell/gb-draggable-cell.component' ; import { ValueConstraint } from '../models/constraint-models/value-constraint' ; import { ResourceService } from './resource.service' ; import { CombinationConstraint } from '../models/constraint-models/combination-constraint' ; import { ConstraintHelper } from '../utilities/constraint-utilities/constraint-helper' ; import { ConceptConstraint } from '../models/constraint-models/concept-constraint' ; import { CombinationState } from '../models/constraint-models/combination-state' ; import { Col } from '../models/table-models/col' ; import { Row } from '../models/table-models/row' ; import { HttpErrorResponse } from '@angular/common/http' ; import { ErrorHelper } from '../utilities/error-helper' ; import { Promise } from 'es6-promise' ; import { ValueType } from '../models/constraint-models/value-type' ; import { Operator } from '../models/constraint-models/operator' ; import { CohortService } from './cohort.service' ; @ Injectable ( { providedIn : 'root' , } ) export class CrossTableService { private _crossTable : CrossTable ; private _selectedConstraintCell : GbDraggableCellComponent ; constructor ( private cohortService , private resourceService , private applicationRef ) { this . clear ( ) ; this . cohortService . cohortsUpdated . asObservable ( ) . subscribe ( ( ) => { this . crossTable . isUpdating = true ; this . updateCells ( ) . then ( ( ) => { this . crossTable . isUpdating = false ; } ) ; } ) ; } clear ( ) { this . crossTable = new CrossTable ( ) ; } param { Constraint [ ] } constraints public update ( constraints ) { return new Promise ( ( resolve , reject ) => { this . crossTable . isUpdating = true ; this . updateValueConstraints ( constraints ) . then ( this . updateCells . bind ( this ) ) . then ( ( ) => { this . crossTable . isUpdating = false ; /**
                 * For some funny reason, after the drop of a tree node to cross table,
                 * the cross table component does not update its view sometimes.
                 * Here we force it to update.
                 */ this . applicationRef . tick ( ) ; resolve ( true ) ; } ) . catch ( err => { reject ( ) ; } ) ; } ) ; } param { Constraint [ ] } constraints private updateValueConstraints ( constraints ) { return new Promise ( ( resolve , reject ) => { this . clearValueConstraints ( constraints ) ; let promises = [ ] ; for ( let constraint of constraints ) { constraint . textRepresentation = ConstraintHelper . brief ( constraint ) ; let needsAggregateCall = false ; if ( ConstraintHelper . isCategoricalConceptConstraint ( constraint ) ) { needsAggregateCall = true ; let categoricalConceptConstraint = < ConceptConstraint > constraint ; let promise = this . retrieveAggregate ( categoricalConceptConstraint , constraint ) ; promises . push ( promise ) ; } else if ( constraint . className === 'CombinationConstraint' ) { let combiConstraint = < CombinationConstraint > constraint ; if ( combiConstraint . isAnd ( ) ) { let categoricalConceptConstraints = combiConstraint . children . filter ( ( child ) => ConstraintHelper . isCategoricalConceptConstraint ( child ) ) ; if ( categoricalConceptConstraints . length === 1 ) { needsAggregateCall = true ; let promise = this . retrieveAggregate ( < ConceptConstraint > categoricalConceptConstraints [ 0 ] , constraint ) ; promises . push ( promise ) ; } } } if ( ! needsAggregateCall ) { this . crossTable . setValueConstraints ( constraint , [ constraint ] ) ; } } Promise . all ( promises ) . then ( ( ) => { resolve ( true ) ; } ) . catch ( err => { reject ( 'Fail to update cross table constraints.' ) ; } ) ; } ) ; } param { Constraint [ ] } constraints return { Constraint [ ] [ ] } public crossConstraints ( constraints ) { let constraintsWithValues = constraints . map ( constraint => { if ( ! this . valueConstraints . has ( constraint ) ) { throw new Error ( 'No value mapping for constraint' ) ; } let valuesForConstraint = this . valueConstraints . get ( constraint ) ; return valuesForConstraint . map ( valueConstraint => { let combination = new CombinationConstraint ( ) ; combination . combinationState = CombinationState . And ; combination . addChild ( constraint ) ; combination . addChild ( valueConstraint ) ; combination . textRepresentation = valueConstraint . textRepresentation ; return combination ; } ) ; } ) ; return ConstraintHelper . permuteConstraints ( constraintsWithValues ) ; } /**
     * a. call resource service to get the new cells for the table,
     * b. replace the old cells with the new cells
     * c. construct rows and cols based on the new cells
     */ public updateCells ( ) { return new Promise ( ( resolve , reject ) => { this . crossTable . isUpdating = true ; try { this . crossTable . rowHeaderConstraints = this . crossConstraints ( this . rowConstraints ) ; this . crossTable . columnHeaderConstraints = this . crossConstraints ( this . columnConstraints ) ; this . crossTable . constraint = this . cohortService . allSelectedCohortsConstraint ; this . resourceService . getCrossTable ( this . crossTable ) . subscribe ( ( crossTable ) => { this . crossTable = crossTable ; this . crossTable . isUpdating = false ; /**
                     * For some funny reason, after the drop of a tree node to cross table,
                     * the cross table component does not update its view sometimes.
                     * Here we force it to update.
                     */ this . applicationRef . tick ( ) ; resolve ( true ) ; } , ( err ) => { ErrorHelper . handleError ( err ) ; reject ( 'Fail to get table cells from server.' ) ; } ) ; } catch ( e ) { reject ( e . message ) ; } } ) ; } param { Constraint } constraint returns { boolean } public isValidConstraint ( constraint ) { return ConstraintHelper . isCategoricalConceptConstraint ( constraint ) || ConstraintHelper . isConjunctiveAndHasOneCategoricalConstraint ( constraint ) ; } private clearValueConstraints ( targetConstraints ) { targetConstraints . forEach ( ( target ) => { if ( this . valueConstraints . get ( target ) ) { this . valueConstraints . get ( target ) . length = 0 ; } } ) ; } private retrieveAggregate ( categoricalConceptConstraint , peerConstraint ) { return new Promise ( ( resolve , reject ) => { if ( categoricalConceptConstraint . concept . aggregate ) { const categoricalAggregate = < CategoricalAggregate > categoricalConceptConstraint . concept . aggregate ; this . setCategoricalValueConstraints ( categoricalAggregate , peerConstraint ) ; resolve ( true ) ; } else { this . resourceService . getCategoricalAggregate ( categoricalConceptConstraint ) . subscribe ( ( categoricalAggregate ) => { this . setCategoricalValueConstraints ( categoricalAggregate , peerConstraint ) ; resolve ( true ) ; } , ( err ) => { ErrorHelper . handleError ( err ) ; reject ( `Fail to retrieve aggregate of constraint ${ categoricalConceptConstraint . textRepresentation }` ) ; } ) ; } } ) ; } private setCategoricalValueConstraints ( categoricalAggregate , peerConstraint ) { let valueConstraints = categoricalAggregate . values . map ( ( value ) => { let val = new ValueConstraint ( ) ; val . valueType = ValueType . string ; val . operator = Operator . eq ; val . value = value ; val . textRepresentation = val . value . toString ( ) ; return val ; } ) ; this . crossTable . setValueConstraints ( peerConstraint , valueConstraints ) ; } get selectedConstraintCell ( ) : GbDraggableCellComponent { return this . _selectedConstraintCell ; } set selectedConstraintCell ( value ) { this . _selectedConstraintCell = value ; } get rowConstraints ( ) : Constraint [ ] { return this . crossTable . rowConstraints ; } get columnConstraints ( ) : Constraint [ ] { return this . crossTable . columnConstraints ; } get valueConstraints ( ) : Map < Constraint , Constraint [ ] > { return this . crossTable . valueConstraints ; } param { Constraint } value set constraint ( value ) { this . crossTable . constraint = value ; } get rows ( ) : Row [ ] { return this . crossTable . rows ; } get cols ( ) : Col [ ] { return this . crossTable . cols ; } get crossTable ( ) : CrossTable { return this . _crossTable ; } set crossTable ( value ) { this . _crossTable = value ; } }