import { Injectable } from "@angular/core" ; import * as _ from "lodash" ; import { Subject } from "rxjs" ; import { UserSettingsService } from "../../shared/services/user-settings/user-settings.service" ; import { UserZonesModel , ZoneModel } from "@elevate/shared/models" ; import { userSettingsData } from "@elevate/shared/data" ; import { ZoneChangeWhisperModel } from "./zone-change-whisper.model" ; import { ZoneChangeOrderModel } from "./zone-change-order.model" ; import { ZoneDefinitionModel } from "../../shared/models/zone-definition.model" ; @ Injectable ( ) export class ZonesService { private readonly MAX_ZONES_COUNT : number = 40 ; private readonly MIN_ZONES_COUNT : number = 3 ; public currentZones : ZoneModel [ ] ; returns { Subject < ZoneChangeWhisperModel > } public zoneChangeOrderUpdates : Subject < ZoneChangeOrderModel > ; /**
     * Subscription mechanism that notify changes made by <ZonesService> via a zones update.
     */ public zonesUpdates : Subject < ZoneModel [ ] > ; public stepUpdates : Subject < number > ; public zoneDefinition : ZoneDefinitionModel ; constructor ( public userSettingsService ) { this . zoneChangeOrderUpdates = new Subject < ZoneChangeOrderModel > ( ) ; this . zonesUpdates = new Subject < ZoneModel [ ] > ( ) ; this . stepUpdates = new Subject < number > ( ) ; } returns { Promise < string > } public addLastZone ( ) { return new Promise ( ( resolve , reject ) => { if ( this . currentZones . length >= this . getMaxZoneCount ( ) ) { reject ( "You can't add more than " + this . getMaxZoneCount ( ) + " zones..." ) ; } else { const oldLastZone = this . getLastZone ( ) ; const intermediateZoneValue = Math . floor ( ( oldLastZone . from + oldLastZone . to ) / 2 ) ; const lastZone = { from : intermediateZoneValue , to : oldLastZone . to , } ; this . currentZones [ this . currentZones . length - 1 ] . to = intermediateZoneValue ; this . currentZones . push ( lastZone ) ; resolve ( "Zone <" + this . currentZones . length + "> has been added." ) ; } } ) ; } returns { Promise < string > } public removeLastZone ( ) { return new Promise ( ( resolve , reject ) => { if ( this . currentZones . length <= this . getMinZoneCount ( ) ) { reject ( "You can't remove more than " + this . getMinZoneCount ( ) + " zones..." ) ; } else { this . currentZones . pop ( ) ; resolve ( "Zone <" + ( this . currentZones . length + 1 ) + "> has been removed." ) ; } } ) ; } param { number } index returns { Promise < string > } public removeZoneAtIndex ( index ) { return new Promise ( ( resolve , reject ) => { if ( this . currentZones . length <= this . getMinZoneCount ( ) ) { reject ( "You can't remove more than " + this . getMinZoneCount ( ) + " zones..." ) ; } else { const isFirstZone = ( index === 0 ) ; const isLastZone = ( index === ( this . currentZones . length - 1 ) ) ; if ( isFirstZone || isLastZone ) { this . currentZones . splice ( index , 1 ) ; resolve ( "Zone <" + ( index + 1 ) + "> has been removed." ) ; } else { this . currentZones [ index + 1 ] . from = this . currentZones [ index - 1 ] . to ; this . currentZones . splice ( index , 1 ) ; resolve ( "Zone <" + ( index + 1 ) + "> has been removed." ) ; } } } ) ; } param { ZoneChangeWhisperModel } zoneChange public whisperZoneChange ( zoneChange ) { if ( zoneChange . to && zoneChange . from && ( zoneChange . to === zoneChange . from ) ) { this . zoneChangeOrderUpdates . error ( "Impossible to notify both 'from' & 'to' changes at the same time" ) ; } if ( ! _ . isNumber ( zoneChange . value ) ) { this . zoneChangeOrderUpdates . error ( "Value provided is not a number" ) ; } const isFirstZoneChange = ( zoneChange . sourceId === 0 ) ; const isLastZoneChange = ( zoneChange . sourceId === ( this . currentZones . length - 1 ) ) ; let instruction = { sourceId : zoneChange . sourceId , destinationId : null , to : null , from : null , value : zoneChange . value , } ; if ( ! isFirstZoneChange && ! isLastZoneChange ) { if ( zoneChange . from ) { instruction . destinationId = zoneChange . sourceId - 1 ; instruction . from = false ; instruction . to = true ; } if ( zoneChange . to ) { instruction . destinationId = zoneChange . sourceId + 1 ; instruction . from = true ; instruction . to = false ; } } else if ( isFirstZoneChange ) { if ( zoneChange . to ) { instruction . destinationId = zoneChange . sourceId + 1 ; instruction . from = true ; instruction . to = false ; } if ( zoneChange . from ) { instruction = null ; } } else if ( isLastZoneChange ) { if ( zoneChange . to ) { instruction = null ; } if ( zoneChange . from ) { instruction . destinationId = zoneChange . sourceId - 1 ; instruction . from = false ; instruction . to = true ; } } this . zoneChangeOrderUpdates . next ( instruction ) ; } returns { string } public isZonesCompliant ( zones ) { const NOT_COMPLIANT_ZONE = "Not compliant zones provided: pattern is not respected." ; if ( ! zones ) { return "No zones provided" ; } if ( zones . length > this . getMaxZoneCount ( ) ) { return "Not compliant zones provided: expected at max " + this . getMaxZoneCount ( ) + " zones" ; } if ( zones . length < this . getMinZoneCount ( ) ) { return "Not compliant zones provided: expected at least " + this . getMinZoneCount ( ) + " zones" ; } for ( let i = 0 ; i < zones . length ; i ++ ) { if ( i === 0 ) { if ( zones [ i ] . to !== zones [ i + 1 ] . from ) { return NOT_COMPLIANT_ZONE ; } } else if ( i < ( zones . length - 1 ) ) { if ( zones [ i ] . to !== zones [ i + 1 ] . from || zones [ i ] . from !== zones [ i - 1 ] . to ) { return NOT_COMPLIANT_ZONE ; } } else { if ( zones [ i ] . from !== zones [ i - 1 ] . to ) { return NOT_COMPLIANT_ZONE ; } } } return null ; } returns { Promise < string > } public saveZones ( ) { return new Promise ( ( resolve , reject ) => { const complianceError = this . isZonesCompliant ( this . currentZones ) ; if ( _ . isNull ( complianceError ) ) { this . userSettingsService . saveZones ( this . zoneDefinition , this . currentZones ) . then ( ( ) => { return this . userSettingsService . clearLocalStorageOnNextLoad ( ) ; } ) . then ( ( ) => { resolve ( ) ; } ) . catch ( error => { reject ( error ) ; } ) ; } else { reject ( complianceError ) ; } } ) ; } returns { Promise < void > } public resetZonesToDefault ( ) { return new Promise ( ( resolve , reject ) => { this . currentZones = UserZonesModel . deserialize ( _ . clone ( _ . propertyOf ( userSettingsData . zones ) ( this . zoneDefinition . value ) ) ) ; this . saveZones ( ) . then ( ( ) => { resolve ( ) ; this . zonesUpdates . next ( this . currentZones ) ; } , ( error ) => { reject ( error ) ; this . zonesUpdates . error ( error ) ; } ) ; } ) ; } param { string } jsonInput returns { Promise < void > } public importZones ( jsonInput ) { return new Promise ( ( resolve , reject ) => { try { this . currentZones = < ZoneModel [ ] > JSON . parse ( jsonInput ) ; } catch ( error ) { reject ( "Provided zones do not respect expected format" ) ; return ; } this . saveZones ( ) . then ( ( ) => { this . zonesUpdates . next ( this . currentZones ) ; resolve ( ) ; } , error => { reject ( error ) ; } ) ; } ) ; } param { number } step public notifyStepChange ( step ) { this . stepUpdates . next ( step ) ; } public getLastZone ( ) { return _ . last ( this . currentZones ) ; } public getMaxZoneCount ( ) { return this . MAX_ZONES_COUNT ; } public getMinZoneCount ( ) { return this . MIN_ZONES_COUNT ; } }