const cleanPeriod = 60*1000;//1 минута

class WebSocketConnection {
    //messageLifeTime в минутах (cleanPeriod)
    constructor(messageLifeTime = 5) {
        this.ws = null;
        this.timer = null;
        this.listeners = [];
        this.messageQueue = [];
        this.messageLifeTime = messageLifeTime;
        this.requestId = 0;
    }

    addListener(listener) {
        if (this.listeners.indexOf(listener) < 0)
            this.listeners.push(Object.assign({regTime: Date.now()}, listener));
    }

    //рассылаем сообщение и удаляем те обработчики, которые его получили
    emit(mes, isError) {
        const len = this.listeners.length;
        if (len > 0) {
            let newListeners = [];
            for (const listener of this.listeners) {
                let emitted = false;
                if (isError) {
                    if (listener.onError)
                        listener.onError(mes);
                    emitted = true;
                } else {
                    if (listener.onMessage) {
                        if (listener.requestId) {
                            if (listener.requestId === mes.requestId) {
                                listener.onMessage(mes);
                                emitted = true;
                            }
                        } else {
                            listener.onMessage(mes);
                            emitted = true;
                        }
                    } else {
                        emitted = true;
                    }
                }

                if (!emitted)
                    newListeners.push(listener);
            }
            this.listeners = newListeners;
        }
        
        return this.listeners.length != len;
    }

    open(url) {
        return new Promise((resolve, reject) => {
            if (this.ws && this.ws.readyState == WebSocket.OPEN) {
                resolve(this.ws);
            } else {
                let protocol = 'ws:';
                if (window.location.protocol == 'https:') {
                    protocol = 'wss:'
                }

                url = url || `${protocol}//${window.location.host}/ws`;
                
                this.ws = new WebSocket(url);

                if (this.timer) {
                    clearTimeout(this.timer);
                }
                this.timer = setTimeout(() => { this.periodicClean(); }, cleanPeriod);

                let resolved = false;
                this.ws.onopen = (e) => {
                    resolved = true;
                    resolve(e);
                };

                this.ws.onmessage = (e) => {
                    try {
                        const mes = JSON.parse(e.data);
                        this.messageQueue.push({regTime: Date.now(), mes});

                        let newMessageQueue = [];
                        for (const message of this.messageQueue) {
                            if (!this.emit(message.mes)) {
                                newMessageQueue.push(message);
                            }
                        }

                        this.messageQueue = newMessageQueue;
                    } catch (e) {
                        this.emit(e.message, true);
                    }
                };

                this.ws.onerror = (e) => {
                    this.emit(e.message, true);
                    if (!resolved)
                        reject(e);
                };
            }
        });
    }

    //timeout в минутах (cleanPeriod)
    message(requestId, timeout = 2) {
        return new Promise((resolve, reject) => {
            this.addListener({
                requestId,
                timeout,
                onMessage: (mes) => {
                    resolve(mes);
                },
                onError: (e) => {
                    reject(e);
                }
            });
        });
    }

    send(req) {
        if (this.ws && this.ws.readyState == WebSocket.OPEN) {
            const requestId = ++this.requestId;
            this.ws.send(JSON.stringify(Object.assign({requestId}, req)));
            return requestId;
        } else {
            throw new Error('WebSocket connection is not ready');
        }
    }

    close() {
        if (this.ws && this.ws.readyState == WebSocket.OPEN) {
            this.ws.close();
        }
    }

    periodicClean() {
        try {
            this.timer = null;

            const now = Date.now();
            //чистка listeners
            let newListeners = [];
            for (const listener of this.listeners) {
                if (now - listener.regTime < listener.timeout*cleanPeriod - 50) {
                    newListeners.push(listener);
                } else {
                    if (listener.onError)
                        listener.onError('Время ожидания ответа истекло');
                }
            }
            this.listeners = newListeners;

            //чистка messageQueue
            let newMessageQueue = [];
            for (const message of this.messageQueue) {
                if (now - message.regTime < this.messageLifeTime*cleanPeriod - 50) {
                    newMessageQueue.push(message);
                }
            }
            this.messageQueue = newMessageQueue;
        } finally {
            if (this.ws.readyState == WebSocket.OPEN) {
                this.timer = setTimeout(() => { this.periodicClean(); }, cleanPeriod);
            }
        }
    }
}

export default new WebSocketConnection();