import { each, remove, uniq } from 'lodash';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { StringifyingMap } from '../../../core';
export class RemoteVariableCommunicator {
    constructor(remoteVariableProtocol, logger) {
        this.remoteVariableProtocol = remoteVariableProtocol;
        this.logger = logger;
        this.observers = new Map();
        this.appIdVariablesMap = new Map();
        this.variableStateObservers = new Map();
        this.variableStateCache = new Map();
        this.variableValueCache = new StringifyingMap(k => JSON.stringify(k));
        this.connectedSubscription = this.remoteVariableProtocol.connected.subscribe(() => {
            const variableNamesToStart = [];
            this.variableStateObservers.forEach((__, variableName) => {
                variableNamesToStart.push(variableName);
            });
            this.remoteVariableProtocol.subscribeVariableStates(variableNamesToStart);
            this.appIdVariablesMap.forEach((variableNames, appId) => {
                this.remoteVariableProtocol.tryOpenVariables(variableNames, appId);
            });
        });
        this.doVariableStatesChanged();
        this.doVariableValuesChanged();
    }
    doVariableValuesChanged() {
        if (this.variableValuesChangedSubscription !== undefined) {
            this.variableValuesChangedSubscription.unsubscribe();
        }
        this.variableValuesChangedSubscription =
            this.remoteVariableProtocol.variableValuesChanged.subscribe((values) => {
                each(values, variableValue => {
                    const variableName = variableValue.variableName;
                    this.variableValueCache.set(variableName, variableValue);
                    if (this.observers.has(variableName)) {
                        each(this.observers.get(variableName), ob => {
                            // TODO 同一个ob监听多个变量的情况，组合成数组一次推送。
                            ob.next([variableValue]);
                        });
                    }
                });
            });
    }
    doVariableStatesChanged() {
        if (this.variableStatesChangedSubscription !== undefined) {
            this.variableStatesChangedSubscription.unsubscribe();
        }
        this.variableStatesChangedSubscription =
            this.remoteVariableProtocol.variableStatesChanged.subscribe((states) => {
                each(states, state => {
                    const variableName = state.variableName;
                    this.variableStateCache.set(variableName, state);
                    if (this.variableStateObservers.has(variableName)) {
                        each(this.variableStateObservers.get(variableName), ob => {
                            // TODO 同一个ob监听多个变量的情况，组合成数组一次推送。
                            ob.next([state]);
                        });
                    }
                });
            });
    }
    subscribeVariableStates(variableNames) {
        return new Observable(observer => {
            let variableNamesToStart = [];
            each(variableNames, variableName => {
                if (!this.variableStateObservers.has(variableName)) {
                    this.variableStateObservers.set(variableName, []);
                    variableNamesToStart.push(variableName);
                }
                else {
                    if (this.variableStateCache.has(variableName)) {
                        observer.next([this.variableStateCache.get(variableName)]);
                    }
                }
                this.variableStateObservers.get(variableName).push(observer);
            });
            variableNamesToStart = uniq(variableNamesToStart);
            if (variableNamesToStart && variableNamesToStart.length > 0) {
                if (!this.variableStatesToBeOpened) {
                    this.variableStatesToBeOpened = [];
                    setTimeout(() => {
                        this.remoteVariableProtocol.subscribeVariableStates(this.variableStatesToBeOpened);
                        delete this.variableStatesToBeOpened;
                    });
                }
                this.variableStatesToBeOpened.push(...variableNamesToStart);
            }
            return () => {
                const stopedVariableStates = [];
                each(variableNames, variableName => {
                    const obs = this.variableStateObservers.get(variableName);
                    if (obs) {
                        remove(obs, item => item === observer);
                        if (obs.length <= 0) {
                            this.variableStateObservers.delete(variableName);
                            stopedVariableStates.push(variableName);
                        }
                    }
                });
                if (!stopedVariableStates.length) {
                    return;
                }
                if (!this.variableStatesToBeClosed) {
                    this.variableStatesToBeClosed = [];
                    setTimeout(() => {
                        this.remoteVariableProtocol.unsubscribeVariableStates(this.variableStatesToBeClosed);
                        delete this.variableStatesToBeClosed;
                    });
                }
                this.variableStatesToBeClosed.push(...stopedVariableStates);
            };
        });
    }
    subscribeVariableState(variableName) {
        return this.subscribeVariableStates([variableName]).pipe(map(values => values[0]));
    }
    openVariables(variableNames, appId = '') {
        return new Observable(observer => {
            let variablesToStart = [];
            each(variableNames, variableName => {
                if (variableName) {
                    if (!this.observers.has(variableName)) {
                        this.observers.set(variableName, []);
                        variablesToStart.push(variableName);
                    }
                    else {
                        if (this.variableValueCache.has(variableName)) {
                            observer.next([this.variableValueCache.get(variableName)]);
                        }
                    }
                    this.observers.get(variableName).push(observer);
                    if (!this.appIdVariablesMap.get(appId)) {
                        this.appIdVariablesMap.set(appId, []);
                    }
                    else {
                        if (this.appIdVariablesMap.get(appId).indexOf(variableName) === -1) {
                            this.appIdVariablesMap.get(appId).push(variableName);
                        }
                    }
                }
            });
            variablesToStart = uniq(variablesToStart);
            if (variablesToStart && variablesToStart.length > 0) {
                if (!this.variablesToBeOpened) {
                    this.variablesToBeOpened = [];
                    setTimeout(() => {
                        this.remoteVariableProtocol.tryOpenVariables(this.variablesToBeOpened, appId);
                        delete this.variablesToBeOpened;
                    });
                }
                this.variablesToBeOpened.push(...variablesToStart);
            }
            return () => {
                const stopedDataMonitorPoints = [];
                each(variableNames, variableName => {
                    const obs = this.observers.get(variableName);
                    if (obs) {
                        remove(obs, item => item === observer);
                        if (obs.length <= 0) {
                            this.observers.delete(variableName);
                            stopedDataMonitorPoints.push(variableName);
                        }
                    }
                });
                if (!stopedDataMonitorPoints.length) {
                    return;
                }
                if (!this.variablesToBeClosed) {
                    this.variablesToBeClosed = [];
                    setTimeout(() => {
                        this.remoteVariableProtocol.tryStopVariables(this.variablesToBeClosed, appId);
                        delete this.variablesToBeClosed;
                    });
                }
                this.variablesToBeClosed.push(...stopedDataMonitorPoints);
            };
        });
    }
    openVariable(variableName, appId = '') {
        if (!variableName) {
            return;
        }
        return this.openVariables([variableName], appId).pipe(map(values => values[0]));
    }
    write(variableName, value) {
        if (!variableName) {
            return;
        }
        this.remoteVariableProtocol.writeVariable(variableName, value);
        return new Observable(__ => {
            /* do nothing right now, if want value change immediately, can use this code below:
              observer.next(new VariableValue(variable, VariableStateEnum.DataNormal, value))*/
        });
    }
    writeWordByBit(variableName, index, value) {
        if (!variableName) {
            return;
        }
        this.remoteVariableProtocol.writeVariableWordByBit(variableName, index, value);
        return new Observable(__ => {
            /* do nothing right now, if want value change immediately, can use this code below:
              observer.next(new VariableValue(variable, VariableStateEnum.DataNormal, value))*/
        });
    }
    dispose() {
        // TODO调用所有的ob.complete()。
        this.connectedSubscription.unsubscribe();
        this.variableStatesChangedSubscription.unsubscribe();
        this.variableValuesChangedSubscription.unsubscribe();
        if (this.logger && this.logger.isDebugEnabled()) {
            this.logger.debug('[VariableCommunicator] RemoteVariableCommunicator disposed.');
        }
    }
}
