(function (window) {
    'use strict';

    /**
     * @class ReliableConnection
     * @description
     * Connection Class.
     *
     * @param {object} config
     * Connection Configuration.
     *
     * @param {number} [config.attempts=10]
     * Maximum number of connection attempts.
     *
     * @param {number | number[]} [config.delays=[100, 1000, 5000]]
     * Delays between connection attempts, in milliseconds.
     *
     * It can be either a single delay, or an array of delays. The last delay in the array
     * is reused when the number of attempts is greater than the array's length.
     *
     * @param {function} config.onConnect
     * A callback to establish a connection and return a promise.
     *
     * @param {function} config.onStatus
     * Status change notification, with the following parameters:
     *  - `status`
     *  - `data`
     *
     * @returns {ReliableConnection}
     */
    function ReliableConnection(config) {
        if (!(this instanceof ReliableConnection)) {
            return new ReliableConnection(config);
        }

        if (!config || typeof config !== 'object') {
            throw new TypeError('Invalid configuration object.');
        }

        /*
                var attempts = ReliableConnection.defaults.attempts,
                    delays = ReliableConnection.defaults.delays;

                if ('attempts' in config) {

                }

                if ('delays' in config) {
                    if (Array.isArray(config.delays)) {
                        delays = config.delays.map(function(value, idx) {
                            var d = parseInt(value);
                            if (isFinite(d) && d >= 0) {
                                return d;
                            }
                            throw new TypeError('Invalid delay value at position ' + idx);
                        });
                    } else {
                        delays = config.delays >= 0 ? [parseInt(config.delays)] : defaults.delays;
                    }
                }
        */

        // temporary:
        var maxAttempts = ReliableConnection.defaults.attempts,
            delays = ReliableConnection.defaults.delays;

        var $p = function (cb) {
            return new Promise(cb);
        };
        $p.resolve = Promise.resolve;
        $p.reject = Promise.reject;

        /**
         * @method ReliableConnection#start
         * @description
         * Initiates connection attempts.
         *
         * It is to be called after creating the class, or after calling `stop()`.
         *
         * The method does nothing, if the current status is either `connected` or `connecting`.
         */
        this.start = function () {

        };

        /**
         * @method ReliableConnection#stop
         * @description
         * Notifies the driver of ceasing all connectivity at once, and to reset itself to the initial state.
         *
         * The connection either already has been closed, or to be closed on status = `stopped`.
         */
        this.stop = function () {
            notifyStatus.call(this, ReliableConnection.status.stopped);
        };

        /**
         * @method ReliableConnection#disconnect
         * @description
         * Notifies the driver of the lost connection, to trigger reconnection attempts.
         */
        this.disconnect = function () {
            reconnect();
        };

        function reconnect(attempt) {
            var delay = attempt < delays.length ? delays[attempt] : delays[delays.length - 1];
            return $p(function (resolve, reject) {
                setTimeout(function () {
                    // the state may have changed here
                    config.connect()
                        .then(function (obj) {
                            notifyStatus('connected', {
                                connection: obj
                            });
                            resolve(obj);
                        })
                        .catch(function (error) {
                            notifyStatus('error', {
                                error: error
                            });
                            if (++attempt < maxAttempts) {
                                reconnect(attempt)
                                    .then(resolve)
                                    .catch(reject);
                            } else {
                                reject(error);
                            }
                        });
                }, delay);
            });
        }

        function notifyStatus(cb, status, data) {
            if (typeof cb === 'function') {
                var self = this;
                setTimeout(function () {
                    cb.call(self, status, data);
                });
            }
        }

    }

    /**
     * @enum {string}
     * @member ReliableConnection.status
     * @description
     * Connection status enum.
     *
     * @property idle
     * Means the class is in the state of doing nothing.
     *
     * It is acquired in the following cases:
     *  - Immediately following instantiation of the class (the default state)
     *  - After reaching the state of `failed`
     *  - After reaching the state of `stopped`
     *  - After calling method `disconnect`
     *
     * `data` = previous state: `undefined` when the class was just created,
     *  or `stopped`/`failed`/`disconnected` otherwise.
     *
     * @property connected
     * The connection just has been established.
     *
     * `data` - an object with connection details + statistics:
     *  - `connection` - the object that represents the connection, which is the value resolved by `onConnect`
     *  - `success` - number of times the connection has been established since the class was created
     *     or method `stop` was called.
     *  - `attempts` - number of attempts in the session it took to successfully connect
     *
     * @property disconnected
     * Method `disconnect` just has been called.
     *
     * `data` = `undefined` (not used)
     *
     * @property connecting
     * Trying to connect.
     *
     * `data` - connection statistics object:
     *  - `start` - Date/Time when the current session started
     *  - `attempt` - number of previous unsuccessful attempts in the current connection session.
     *
     * @property failed
     * Permanent failure, after the maximum number of attempts has been reached.
     *
     * @property stopped
     * Method `stop()` just has been called, the driver is about to become `idle`.
     *
     * @property error
     * Another connection attempt has failed.
     *
     * `data` - the error status object:
     *  - `error` - the connection error, which is the rejection reason from `onConnect`
     *  - `start` - Date/Time when the current connection session started
     *  - `count` - number of connection attempts made in the current connection session
     *  - `terminal` - boolean that's set when it was the last connection attempt
     */
    ReliableConnection.status = {
        idle: 'idle',
        connected: 'connected',
        disconnected: 'disconnected',
        connecting: 'connecting',
        failed: 'failed',
        stopped: 'stopped',
        error: 'error'
    };

    /**
     * @member ReliableConnection.defaults
     * @description
     * Default configuration parameters.
     *
     * @property {number} attempts
     *
     * @property {number[]} delays
     */
    ReliableConnection.defaults = {
        attempts: 10,
        delays: [100, 1000, 5000]
    };

    /* istanbul ignore else */
    if (typeof module === 'object' && module && typeof module.exports === 'object') {
        module.exports = ReliableConnection;
    }
    else {
        window.ReliableConnection = ReliableConnection;
    }
})(this);