/*
 * (C) Copyright 2013-2015 Kurento (http://kurento.org/)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

"use strict";

var OpenViduLogger = require('../../../../Logger/OpenViduLogger').OpenViduLogger;
var Logger = OpenViduLogger.getInstance();

var MAX_RETRIES = 2000; // Forever...
var RETRY_TIME_MS = 3000; // FIXME: Implement exponential wait times...

var CONNECTING = 0;
var OPEN = 1;
var CLOSING = 2;
var CLOSED = 3;

/*
config = {
uri : wsUri,
onconnected : callback method to invoke when connection is successful,
ondisconnect : callback method to invoke when the connection is lost (max retries for reconnecting reached),
onreconnecting : callback method to invoke when the client is reconnecting,
onreconnected : callback method to invoke when the client successfully reconnects,
};
*/
function WebSocketWithReconnection(config) {
    var closing = false;
    var registerMessageHandler;
    var wsUri = config.uri;
    var reconnecting = false;

    var ws = new WebSocket(wsUri);

    ws.onopen = () => {
        Logger.debug("WebSocket connected to " + wsUri);
        if (config.onconnected) {
            config.onconnected();
        }
    };

    ws.onerror = error => {
        Logger.error(
            "Could not connect to " + wsUri + " (invoking onerror if defined)",
            error
        );
        if (config.onerror) {
            config.onerror(error);
        }
    };

    var reconnectionOnClose = () => {
        if (ws.readyState === CLOSED) {
            if (closing) {
                Logger.debug("Connection closed by user");
            } else {
                if (config.ismasternodecrashed()) {
                    Logger.error("Master Node has crashed. Stopping reconnection process");
                } else {
                    Logger.debug("Connection closed unexpectedly. Reconnecting...");
                    reconnect(MAX_RETRIES, 1);
                }
            }
        } else {
            Logger.debug("Close callback from previous websocket. Ignoring it");
        }
    };

    ws.onclose = reconnectionOnClose;

    function reconnect(maxRetries, numRetries) {
        Logger.debug(
            "reconnect (attempt #" + numRetries + ", max=" + maxRetries + ")"
        );
        if (numRetries === 1) {
            if (reconnecting) {
                Logger.warn(
                    "Trying to reconnect when already reconnecting... Ignoring this reconnection."
                );
                return;
            } else {
                reconnecting = true;
            }
            if (config.onreconnecting) {
                config.onreconnecting();
            }
        }
        reconnectAux(maxRetries, numRetries);
    }

    function addReconnectionQueryParamsIfMissing(uriString) {
        var searchParams = new URLSearchParams((new URL(uriString)).search);
        if (!searchParams.has("reconnect")) {
            uriString = (Array.from(searchParams).length > 0) ? (uriString + '&reconnect=true') : (uriString + '?reconnect=true');
        }
        return uriString;
    }

    function reconnectAux(maxRetries, numRetries) {
        Logger.debug("Reconnection attempt #" + numRetries);
        ws.close(4104, 'Connection closed for reconnection');

        wsUri = addReconnectionQueryParamsIfMissing(wsUri);
        ws = new WebSocket(wsUri);

        ws.onopen = () => {
            Logger.debug(
                "Reconnected to " + wsUri + " after " + numRetries + " attempts..."
            );
            reconnecting = false;
            registerMessageHandler();
            if (config.onreconnected()) {
                config.onreconnected();
            }
            ws.onclose = reconnectionOnClose;
        };

        ws.onerror = error => {
            Logger.warn("Reconnection error: ", error);
            if (numRetries === maxRetries) {
                if (config.ondisconnect) {
                    config.ondisconnect();
                }
            } else {
                setTimeout(() => {
                    reconnect(maxRetries, numRetries + 1);
                }, RETRY_TIME_MS);
            }
        };
    }

    this.close = (code, reason) => {
        closing = true;
        ws.close(code, reason);
    };

    this.reconnectWs = () => {
        Logger.debug("reconnectWs");
        reconnect(MAX_RETRIES, 1);
    };

    this.send = message => {
        ws.send(message);
    };

    this.addEventListener = (type, callback) => {
        registerMessageHandler = () => {
            ws.addEventListener(type, callback);
        };
        registerMessageHandler();
    };

    this.getReadyState = () => {
        return ws.readyState;
    }
}

module.exports = WebSocketWithReconnection;