#include awaitable.js
#include utility.js

const MamiConnectionManager = function(client, settings, urls, eventTarget) {
    const validateClient = value => {
        if(typeof value !== 'object' || value === null)
            throw 'client must be a non-null object';
    };

    validateClient(client);

    if(typeof settings !== 'object' || settings === null)
        throw 'settings must be a non-null object';
    if(!Array.isArray(urls))
        throw 'urls must be an array';
    if(typeof eventTarget !== 'object' || eventTarget === null)
        throw 'eventTarget must be a non-null object';

    const delays = [
             0,
          2000,  2000,  2000,  2000,   2000,
          5000,  5000,  5000,  5000,   5000,
         10000, 10000, 10000, 10000,  10000,
         15000, 30000, 45000, 60000, 120000,
        300000
    ];

    let timeout;
    let attempts, started, delay, url;
    let startResolve;

    const resetTimeout = () => {
        if(timeout !== undefined) {
            clearTimeout(timeout);
            timeout = undefined;
        }
    };

    const clear = () => {
        resetTimeout();
        attempts = started = delay = 0;
        url = undefined;
    };

    $as(urls);

    const attempt = () => {
        started = Date.now();
        url = urls[attempts % urls.length];
        if(url.startsWith('//'))
            url = location.protocol.replace('http', 'ws') + url;

        const attemptNo = attempts + 1;

        timeout = setTimeout(() => {
            resetTimeout();

            (async () => {
                if(settings.get('onlyConnectWhenVisible'))
                    await MamiWaitVisible();

                eventTarget.dispatch('attempt', {
                    url: url,
                    started: started,
                    attempt: attemptNo,
                });

                try {
                    const result = await client.connect(url);
                    if(typeof result === 'boolean' && !result)
                        throw {};

                    eventTarget.dispatch('success', {
                        url: url,
                        started: started,
                        attempt: attemptNo,
                    });

                    startResolve();
                    startResolve = undefined;
                } catch(ex) {
                    ++attempts;
                    delay = attempts < delays.length ? delays[attempts] : delays[delays.length - 1];

                    eventTarget.dispatch('fail', {
                        url: url,
                        started: started,
                        attempt: attempts,
                        delay: delay,
                        error: ex,
                    });

                    attempt();
                }
            })();
        }, delay);
    };

    const isActive = () => timeout !== undefined || startResolve !== undefined;

    return {
        get isActive() { return isActive(); },

        get client() { return client; },
        set client(value) {
            validateClient(value);
            client = value;
        },

        start: () => {
            return new Promise(resolve => {
                if(isActive())
                    throw 'already attempting to connect';

                startResolve = resolve;

                clear();
                attempt();
            });
        },
        force: () => {
            if(!isActive())
                return;

            resetTimeout();
            delay = 0;
            attempt();
        },
        clear: clear,
        watch: eventTarget.watch,
        unwatch: eventTarget.unwatch,
    };
};
