"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.NetworkAdapter = void 0;
const Network_1 = require("../Utilities/Network");
const internal_1 = require("../internal");
/**
 * Network adapter is an abstract class that contains logic for adapters dealing with data across network.
 * Please see GithubAdapter, AdlsAdapter or RemoteAdapter for usage of this class.
 * When extending this class a user has to define CdmHttpClient with the specified endpoint and callback function in the constructor
 * and can then use the class helper methods to set up Cdm HTTP requests and read data.
 * If a user doesn't specify timeout, maximutimeout or number of retries in the config under 'httpConfig' property
 * default values will be used as specified in the class.
 */
class NetworkAdapter extends internal_1.StorageAdapterBase {
    constructor() {
        super(...arguments);
        // Use some default values in the case a user doesn't set them up.
        this.defaultTimeout = 5000;
        this.defaultMaximumTimeout = 10000;
        this.defaultNumberOfRetries = 2;
        this.defaultShortestTimeWait = 500;
        this._timeout = this.defaultTimeout;
        this._maximumTimeout = this.defaultMaximumTimeout;
        this._numberOfRetries = this.defaultNumberOfRetries;
        /**
         * A set of HttpStatusCodes that will stop the retry logic if the HTTP response has one of these types.
         */
        this.avoidRetryCodes = new Set([404]);
    }
    get timeout() {
        return this._timeout;
    }
    set timeout(val) {
        this._timeout = (val === undefined || val < 0) ? this.defaultTimeout : val;
    }
    get maximumTimeout() {
        return this._maximumTimeout;
    }
    set maximumTimeout(val) {
        this._maximumTimeout = (val === undefined || val < 0) ? this.defaultMaximumTimeout : val;
    }
    get numberOfRetries() {
        return this._numberOfRetries;
    }
    set numberOfRetries(val) {
        this._numberOfRetries = (val === undefined || val < 0) ? this.defaultNumberOfRetries : val;
    }
    get waitTimeCallback() {
        return this._waitTimeCallback ? this._waitTimeCallback : this.defaultWaitTimeCallback;
    }
    set waitTimeCallback(val) {
        this._waitTimeCallback = val;
    }
    updateNetworkConfig(config) {
        const configsJson = JSON.parse(config);
        if (configsJson.timeout !== undefined) {
            this.timeout = configsJson.timeout;
        }
        if (configsJson.maximumTimeout !== undefined) {
            this.maximumTimeout = configsJson.maximumTimeout;
        }
        if (configsJson.numberOfRetries !== undefined) {
            this.numberOfRetries = configsJson.numberOfRetries;
        }
    }
    fetchNetworkConfig() {
        return {
            timeout: this.timeout,
            maximumTimeout: this.maximumTimeout,
            numberOfRetries: this.numberOfRetries
        };
    }
    executeRequest(httpRequest) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                const res = yield this.httpClient.SendAsync(httpRequest, this.waitTimeCallback.bind(this), this.ctx);
                if (res === undefined) {
                    throw new Error('The result of a network adapter request is undefined.');
                }
                if (!res.isSuccessful) {
                    throw new Error(`HTTP ${res.statusCode} - ${res.reason}. Response headers: ${Array.from(res.responseHeaders.entries()).map((m) => `${m[0]}:${m[1]}`).join(', ')}. URL: ${httpRequest.stripSasSig()}`);
                }
                return res;
            }
            catch (err) {
                throw err;
            }
        });
    }
    /**
     * Sets up a CDM request that can be used by CDM Http Client.
     * @param {string} path The partial or full path to a network location.
     * @param {number} numberOfRetries The number of retries.
     * @param {Map<string, string>} headers Optional headers.
     * @param {string} method The HTTP method.
     * @return {CdmHttpRequest}, representing the CDM HTTP request.
     */
    setUpCdmRequest(path, headers, method) {
        const httpRequest = new Network_1.CdmHttpRequest(path);
        httpRequest.headers = headers ? headers : new Map();
        httpRequest.timeout = this.timeout;
        httpRequest.maximumTimeout = this.maximumTimeout;
        httpRequest.numberOfRetries = this.numberOfRetries;
        httpRequest.method = method;
        return httpRequest;
    }
    /**
     * The callback function for CDM Http client, it does exponential backoff.
     * @param {CdmHttpResponse} response The response received by system's Http client.
     * @param {boolean} hasFailed Denotes whether the request has failed (usually an exception or 500 error).
     * @param {number} retryNumber The current retry number (starts from 1) up to the number of retries specified by a CDM request.
     * @return {number}, specifying the waiting time in milliseconds, or undefined if no wait time is necessary.
     */
    defaultWaitTimeCallback(response, hasFailed, retryNumber) {
        if (response !== undefined && ((response.isSuccessful && !hasFailed) || this.avoidRetryCodes.has(response.statusCode))) {
            return undefined;
        }
        else {
            const upperBound = 1 << retryNumber;
            // Default wait time is calculated using exponential backoff with with random jitter value to avoid 'waves'.
            return Math.floor(Math.random() * upperBound) * this.defaultShortestTimeWait;
        }
    }
}
exports.NetworkAdapter = NetworkAdapter;
//# sourceMappingURL=NetworkAdapter.js.map