"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PriceClient = exports.msToS = void 0;
var tslib_1 = require("tslib");
var assert_1 = tslib_1.__importDefault(require("assert"));
var adapters_1 = require("./adapters");
function msToS(ms) {
    return Math.floor(ms / 1000);
}
exports.msToS = msToS;
var PriceClient = (function () {
    function PriceClient(logger, priceFeeds, throwOnFailure) {
        if (throwOnFailure === void 0) { throwOnFailure = false; }
        this.logger = logger;
        this.priceFeeds = priceFeeds;
        this.throwOnFailure = throwOnFailure;
        this.name = "PriceClient";
        this._maxPriceAge = 300;
        this.prices = {};
        (0, assert_1.default)(logger, "No logging instance supplied.");
        (0, assert_1.default)(priceFeeds.length > 0, "No price feeds supplied.");
        if (!throwOnFailure) {
            priceFeeds.push(new adapters_1.defaultAdapter.PriceFeed());
        }
    }
    Object.defineProperty(PriceClient.prototype, "maxPriceAge", {
        get: function () {
            return this._maxPriceAge;
        },
        set: function (age) {
            (0, assert_1.default)(age >= 0);
            this.logger.debug({
                at: "PriceClient#maxPriceAge",
                message: "Setting maxPriceAge (S) ".concat(this._maxPriceAge, " => ").concat(age, "."),
            });
            this._maxPriceAge = age;
        },
        enumerable: false,
        configurable: true
    });
    PriceClient.prototype.listPriceFeeds = function () {
        return this.priceFeeds.map(function (priceFeed) { return priceFeed.name; });
    };
    PriceClient.prototype.getPriceByAddress = function (address, currency) {
        if (currency === void 0) { currency = "usd"; }
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var tokenPrices;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4, this.getPricesByAddress([address], currency)];
                    case 1:
                        tokenPrices = _a.sent();
                        return [2, tokenPrices[0]];
                }
            });
        });
    };
    PriceClient.prototype.getPricesByAddress = function (addresses, currency) {
        if (currency === void 0) { currency = "usd"; }
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var priceCache, now, missed;
            var _this = this;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        (0, assert_1.default)(this.priceFeeds.length > 0, "No price feeds are registered.");
                        priceCache = this.getPriceCache(currency);
                        now = msToS(Date.now());
                        missed = {};
                        addresses.forEach(function (address) {
                            var _a;
                            var addrCacheKey = address.toLowerCase();
                            var tokenPrice = (_a = priceCache[addrCacheKey]) !== null && _a !== void 0 ? _a : { price: 0, timestamp: 0, address: address };
                            priceCache[addrCacheKey] = tokenPrice;
                            var age = now - tokenPrice.timestamp;
                            if (age > _this.maxPriceAge) {
                                missed[address] = age;
                            }
                        });
                        if (!(Object.keys(missed).length > 0)) return [3, 2];
                        this.logger.debug({
                            at: "PriceClient#getPricesByAddress",
                            message: "".concat(currency.toUpperCase(), " cache miss (age > ").concat(this.maxPriceAge, " S)."),
                            tokens: missed,
                        });
                        return [4, this.updatePrices(currency)];
                    case 1:
                        _a.sent();
                        _a.label = 2;
                    case 2: return [2, addresses.map(function (address) {
                            var _a = priceCache[address.toLowerCase()], price = _a.price, timestamp = _a.timestamp;
                            return { address: address, price: price, timestamp: timestamp };
                        })];
                }
            });
        });
    };
    PriceClient.prototype.expireCache = function (currency) {
        var priceCache = this.getPriceCache(currency);
        Object.values(priceCache).forEach(function (token) { return (token.timestamp = 0); });
        this.logger.debug({ at: "PriceClient#expireCache", message: "Expired ".concat(currency, " cache.") });
    };
    PriceClient.prototype.getPriceCache = function (currency) {
        if (this.prices[currency] === undefined)
            this.prices[currency] = {};
        return this.prices[currency];
    };
    PriceClient.prototype.updatePrices = function (currency) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var priceCache, skipped, addrsToRequest, _i, _a, priceFeed, feedPricesResponse, err_1;
            return tslib_1.__generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        priceCache = this.getPriceCache(currency);
                        skipped = [];
                        addrsToRequest = Object.entries(priceCache).map(function (entry) { return entry[1].address; });
                        _i = 0, _a = this.priceFeeds;
                        _b.label = 1;
                    case 1:
                        if (!(_i < _a.length)) return [3, 6];
                        priceFeed = _a[_i];
                        this.logger.debug({
                            at: "PriceClient#updatePrices",
                            message: "Looking up prices via ".concat(priceFeed.name, "."),
                            tokens: addrsToRequest,
                        });
                        _b.label = 2;
                    case 2:
                        _b.trys.push([2, 4, , 5]);
                        return [4, priceFeed.getPricesByAddress(addrsToRequest, currency)];
                    case 3:
                        feedPricesResponse = _b.sent();
                        skipped = this.updateCache(priceCache, feedPricesResponse, addrsToRequest);
                        if (skipped.length === 0)
                            return [3, 6];
                        return [3, 5];
                    case 4:
                        err_1 = _b.sent();
                        this.logger.debug({
                            at: "PriceClient#updatePrices",
                            message: "Price lookup against ".concat(priceFeed.name, " failed (").concat(err_1, ")."),
                            tokens: addrsToRequest,
                        });
                        return [3, 5];
                    case 5:
                        _i++;
                        return [3, 1];
                    case 6:
                        if (skipped.length !== 0) {
                            this.logger.debug({
                                at: "PriceClient#updatePrices",
                                message: "Unable to resolve some token prices.",
                                priceFeeds: this.listPriceFeeds(),
                                tokens: skipped,
                            });
                            throw new Error("Price lookup failed against all price feeds (".concat(this.listPriceFeeds().join(", "), ")"));
                        }
                        return [2];
                }
            });
        });
    };
    PriceClient.prototype.updateCache = function (priceCache, prices, expected) {
        var _this = this;
        var _a, _b;
        var updated = [];
        var skipped = {};
        var now = msToS(Date.now());
        expected.forEach(function (address) {
            var addr = address.toLowerCase();
            var tokenPrice = prices.find(function (price) { var _a; return ((_a = price === null || price === void 0 ? void 0 : price.address) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === addr; });
            if (tokenPrice === undefined) {
                skipped[address] = "Not included in price feed response.";
            }
            else if (tokenPrice.timestamp > now) {
                skipped[address] = "Token price timestamp is too new (timestamp ".concat(tokenPrice.timestamp, ").");
            }
            else if (tokenPrice.timestamp >= priceCache[addr].timestamp) {
                var price = tokenPrice.price, timestamp = tokenPrice.timestamp;
                priceCache[addr] = { price: price, timestamp: timestamp, address: priceCache[addr].address };
                updated.push(tokenPrice);
            }
            else if (tokenPrice.timestamp === priceCache[addr].timestamp) {
                _this.logger.debug({
                    at: "PriceClient#updateCache",
                    message: "No new price available for token ".concat(address, "."),
                    token: tokenPrice,
                });
            }
        });
        this.logger.debug({
            at: "PriceClient#updateCache",
            message: "Updated ".concat((_a = updated.length) !== null && _a !== void 0 ? _a : 0, " price(s), skipped ").concat((_b = Object.keys(skipped).length) !== null && _b !== void 0 ? _b : 0, "."),
            updated: updated,
            skipped: skipped,
        });
        return Object.keys(skipped);
    };
    return PriceClient;
}());
exports.PriceClient = PriceClient;
//# sourceMappingURL=priceClient.js.map