var CredentialsProvider = require('../credentials/CredentialsProvider.js').CredentialsProvider;
var Event = require('../event/Event.js').Event;
var ErrorEvent = require('../event/ErrorEvent.js').ErrorEvent;
var Loader = require('../net/Loader.js').Loader;

var OpalRequest = require('./OpalRequest.js').OpalRequest;
var OpalLoader = require('./OpalLoader.js').OpalLoader;

var Types = require('../common/Types.js');
var JsonRpcError = require('../jsonrpc/JsonRpcError').JsonRpcError;


// DEBUG
function __debug(credential, method, msg) {
    if (process.env['DISABLE_OCP_DEBUG']) {
        return;
    }

    var args = Array.prototype.slice.call(arguments, 2);
    args[0] = 'OpalCredentialsProvider/__debug/' + credential + '/' + method + ' ' + (msg || '');
    console.info.apply(console, args);
}

//Multiton placeholder
var instances = {};

var OpalCredentialsProvider = function (credentialId, identity, gtw) {
    __debug(credentialId, 'OpalCredentialsProvider');
    return OpalCredentialsProvider.getInstance(credentialId, identity, gtw);
};

/**
 * Multition
 * @static
 */
OpalCredentialsProvider.getInstance = function (credentialId, identity, gtw) {
    __debug(this._credentialId, 'getInstance');
    /* ustawienei domyslnego identity */
    if (!identity && process.env['OPAL_IDENTITY']) {
        identity = process.env['OPAL_IDENTITY'];
    }

    if (!gtw) {
        gtw = "default";
    }

    if (instances[credentialId] && instances[credentialId][identity] && instances[credentialId][identity][gtw]) {
        __debug(credentialId, 'getInstance', 'instance already exists');
        return instances[credentialId][identity][gtw];
    } else {
        __debug(credentialId, 'getInstance', 'creating new instance');
        var obj = Object.create(OpalCredentialsProvider.prototype);
        CredentialsProvider.call(obj);
        obj._credentialId = credentialId;
        obj._identity = identity;
        obj._currentCredentials = null;
        obj.refreshTime = OpalCredentialsProvider.DEFAULT_REFRESH_TIME;
        obj.refreshTimeError = OpalCredentialsProvider.ERROR_REFRESH_TIME;
        obj._retryCount = 0;
        obj.request = new OpalRequest({
            "url": "router.opaladmin.onetapi.pl",
            "method": "get_credentials",
            "params": {
                "ds_alias": obj._credentialId
            },
            "application": obj._identity
        });
        if (gtw && gtw != "default") {
            obj.request.setConnectionHost(gtw);
        }
        obj.loader = null;
        obj._refreshInProgress = false;
        obj._forceRefresh = false;
        obj._permError = null;
        obj._credentialsUpdatedTime = Date.now();
        if (!instances[credentialId]) {
            instances[credentialId] = {};
        }

        if (!instances[credentialId][identity]) {
            instances[credentialId][identity] = {};
        }

        instances[credentialId][identity][gtw] = obj;
        obj.refresh();

        __debug(credentialId, 'getInstance', 'created new instance');
        return obj;
    }
};

OpalCredentialsProvider.prototype = Object.create(CredentialsProvider.prototype);

OpalCredentialsProvider.prototype.getCredentialId = function () {
    __debug(this._credentialId, 'getCredentialId');
    return this._credentialId;
};

/**
 * Returns copy of credentials object
 *
 * @returns {String}
 */
OpalCredentialsProvider.prototype._credentialsCopy = function () {
    __debug(this._credentialId, '_credentialsCopy');
    return JSON.parse(JSON.stringify(this._currentCredentials));
};

OpalCredentialsProvider.prototype.get = function (callback) {
    if (this._currentCredentials && !this._refreshInProgress) {
        callback(null, this._credentialsCopy());
    } else if (this._permError) {
        __debug(this._credentialId, 'get', 'permError=%s', this._permError);
        callback(CredentialsProvider.Event.ERROR, this._permError);
    } else {
        __debug(this._credentialId, 'get', 'refresh in progress');
        var that = this;
        var onCredentials = function (e) {
            __debug(that._credentialId, 'get', 'credential refreshed, calling callback');
            that.removeEventListener(CredentialsProvider.Event.LOAD, onCredentials);
            that.removeEventListener(CredentialsProvider.Event.TIMEOUT, onCredentialsTimeout);
            callback(null, e.data);
        };
        var onCredentialsTimeout = function () {
            __debug(that._credentialId, 'get', 'refreshing timeouted, calling callback');
            if (this._currentCredentials) {
                console.error(
                    "Credentials retrieving error / timeouted.",
                    "Credential key:", this._credentialId,
                    "Credentials identity:", this._identity,
                    "Returning old credentials:", this._currentCredentials
                );
                callback(CredentialsProvider.Event.TIMEOUT, this._currentCredentials);
            } else {
                console.error(
                    "Credentials retrieving error / timeouted.",
                    "Credential key:", this._credentialId,
                    "Credentials identity:", this._identity,
                    "No credentials"
                );
                callback(CredentialsProvider.Event.ERROR, null);
            }
            that.removeEventListener(CredentialsProvider.Event.LOAD, onCredentials);
            that.removeEventListener(CredentialsProvider.Event.TIMEOUT, onCredentialsTimeout);
        };
        this.addEventListener(CredentialsProvider.Event.LOAD, onCredentials, this);
        this.addEventListener(CredentialsProvider.Event.TIMEOUT, onCredentialsTimeout, this);
        if (!this._refreshInProgress) {
            this.refresh();
        }
    }
};

OpalCredentialsProvider.prototype.refresh = function (force) {
    __debug(this._credentialId, 'refresh');
    force = typeof (force) == 'undefined' ? false : force;

    if (!this._checkInterval) {
        this._checkInterval = setInterval(this._checkCredentialsRefresh.bind(this), OpalCredentialsProvider.DEFAULT_REFRESH_TIME);
    }

    if (!this._refreshInterval) {
        this._refreshInterval = setInterval(this._scheduleRefresh.bind(this), OpalCredentialsProvider.DEFAULT_REFRESH_TIME);
    }

    this._forceRefresh = force;
    this._refreshInProgress = true;
    this._scheduleRefresh();
};

CredentialsProvider.prototype.destroy = function () {
    __debug(this._credentialId, '_destroy');

    if (this._refreshTimer) {
        clearTimeout(this._refreshTimer);
    }
    if (this._refreshInterval) {
        clearInterval(this._refreshInterval);
    }
    if (this._checkInterval) {
        clearInterval(this._checkInterval);
    }

    this._retryCount = 0;
    this._clearLoader();
    delete instances[this._credentialId][this._identity];
};

OpalCredentialsProvider.prototype._handleRefresh = function () {
    __debug(this._credentialId, '_handleRefresh');
    if (!this.loader) {
        this.loader = new OpalLoader(this.request);
        this.loader.addEventListener(OpalLoader.Event.JSON_RESPONSE, this._onLoad, this);
        this.loader.addEventListener(OpalLoader.Event.HTTP_RESPONSE, this._onError, this);
        this.loader.addEventListener(Loader.Event.ERROR, this._onError, this);
        this.loader.load();
    }
};

OpalCredentialsProvider.prototype._handleError = function (error) {
    __debug(this._credentialId, '_handleError', 'dispatch event "ERROR", retryCount=%s, error=%s', this._retryCount, error);
    this._clearLoader();
    console.error(
        "Credentials retrieving error.",
        "Credential key:", this._credentialId,
        "Credentials identity:", this._identity,
        "Error:", error
    );

    if (Types.isInstanceOf(error, JsonRpcError) && error.getCode() == OpalCredentialsProvider.OpalError.NOT_FOUND) {
        this._permError = OpalCredentialsProvider.OpalError.NOT_FOUND;
    }

    this._retryCount++;

    if (this._retryCount > 3) {
        __debug(this._credentialId, '_handleError', 'failed to refresh credential, dispatch event "TIMEOUT"');
        this._retryCount = 0;
        this.refreshTimeError = OpalCredentialsProvider.ERROR_REFRESH_TIME;
        this.dispatchEvent(new ErrorEvent(CredentialsProvider.Event.ERROR, error));
        this.dispatchEvent(new Event(CredentialsProvider.Event.TIMEOUT, error)); // FIXME: Remove when will be no longer used in EdgeServer
    } else {
        __debug(this._credentialId, '_handleError', 'retrying credential refresh');
        this._scheduleRefresh(this.refreshTimeError);
        this.refreshTimeError *= 2; // zwiększam timeout odpytywania od credentiale przy error
    }
};

OpalCredentialsProvider.prototype._onLoad = function (e) {
    __debug(this._credentialId, '_onLoad');
    this._clearLoader();
    this._refreshInProgress = false;
    var rpc = e.data.getBody();
    if (rpc.isError()) {
        this._handleError(rpc.getError());
        return;
    }
    this._permError = null;
    this._retryCount = 0;
    this.refreshTimeError = OpalCredentialsProvider.ERROR_REFRESH_TIME;
    var newCredentials = rpc.getResult();

    this._credentialsUpdatedTime = Date.now();
    if (JSON.stringify(newCredentials) != JSON.stringify(this._currentCredentials) || this._forceRefresh) {
        this._forceRefresh = false;
        this._currentCredentials = newCredentials;
        __debug(this._credentialId, '_onLoad', 'dispatch event "LOAD"');
        this.dispatchEvent(new Event(CredentialsProvider.Event.LOAD, this._credentialsCopy()));
    }
};

OpalCredentialsProvider.prototype._onError = function (e) {
    __debug(this._credentialId, '_onError');
    this._handleError(e.data);
};

OpalCredentialsProvider.prototype._clearLoader = function () {
    __debug(this._credentialId, '_clearLoader');
    if (this.loader) {
        __debug(this._credentialId, '_clearLoader', 'clearing loader');
        this.loader.removeEventListener(OpalLoader.Event.JSON_RESPONSE, this._onLoad);
        this.loader.removeEventListener(Loader.Event.ERROR, this._onError);
        this.loader = null;
    }
};

OpalCredentialsProvider.prototype._scheduleRefresh = function (period) {
    __debug(this._credentialId, '_scheduleRefresh');
    if (this._refreshTimer) {
        clearTimeout(this._refreshTimer);
        this._refreshTimer = null;
        __debug(this._credentialId, '_scheduleRefresh', 'removed previous timeout');
    }

    if (!period) {
        this._handleRefresh();
    } else {
        __debug(this._credentialId, '_scheduleRefresh', 'postponing refresh, period=%s', period);
        this._refreshTimer = setTimeout(this._scheduleRefresh.bind(this), period);
    }
};

OpalCredentialsProvider.prototype._checkCredentialsRefresh = function () {
    __debug(this._credentialId, '_checkCredentialsRefresh');
    var waitTime = OpalCredentialsProvider.DEFAULT_REFRESH_TIME * 2;
    var diff = Date.now() - this._credentialsUpdatedTime;
    if (diff > waitTime) {
        console.error("OCP/Debug: Credentials haven't been loaded successfully for %s seconds |" + this._credentialId, diff / 1000);
    }
};

OpalCredentialsProvider.DEFAULT_REFRESH_TIME = 60000;
OpalCredentialsProvider.ERROR_REFRESH_TIME = 500;

OpalCredentialsProvider.OpalError = {};
OpalCredentialsProvider.OpalError.NOT_FOUND = -31999;

exports.OpalCredentialsProvider = OpalCredentialsProvider;
