"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var GenericResourceCache = /** @class */ (function () {
    function GenericResourceCache(resourceDefinition) {
        this.resourceDefinition = resourceDefinition;
        this.resources = new Map();
        this.fetchPromises = new Map();
    }
    /**
     * Fetches a resource when it's not yet available or retrieves the existing resource from cache. Keeps track of inflight fetches and prevents multiple fetches
     * for the same resource.
     *
     * Adds the specified component id to the users of the target resource
     *
     * @param {string} componentId
     * @param {P} params
     * @param {(params: P) => Promise<T>} fetchResource
     * @returns {Promise<GenericResourceCacheResource<T>>}
     */
    GenericResourceCache.prototype.ensureResource = function (params, fetchResource) {
        var _this = this;
        var key = this.createResourceName(params);
        if (this.hasResource(params)) {
            var resource = this.getResource(params);
            if (this.resourceDefinition.isResourceValid(params, resource.params)) {
                return Promise.resolve(resource);
            }
            this.removeResource(key);
        }
        var existingFetchPromise = this.fetchPromises.get(key);
        if (existingFetchPromise !== undefined) {
            return existingFetchPromise.then(function () { return _this.getResourceByIdentifier(key); });
        }
        var fetchPromise = fetchResource(params);
        this.fetchPromises.set(key, fetchPromise);
        return fetchPromise
            .then(function (res) {
            _this.fetchPromises.delete(key);
            var resource = _this.addResource(params, res);
            return resource;
        })
            .catch(function (err) {
            _this.fetchPromises.delete(key);
            return Promise.reject(err);
        });
    };
    /**
     * Determines wether the specified resource is currently being loaded
     *
     * @param {P} params
     * @returns {boolean}
     */
    GenericResourceCache.prototype.isResourceFetching = function (params) {
        return this.fetchPromises.has(this.createResourceName(params));
    };
    GenericResourceCache.prototype.findResource = function (cb) {
        var e_1, _a;
        try {
            for (var _b = tslib_1.__values(this.resources), _c = _b.next(); !_c.done; _c = _b.next()) {
                var _d = tslib_1.__read(_c.value, 2), resource = _d[1];
                if (cb(resource)) {
                    return resource;
                }
            }
        }
        catch (e_1_1) { e_1 = { error: e_1_1 }; }
        finally {
            try {
                if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
            }
            finally { if (e_1) throw e_1.error; }
        }
    };
    GenericResourceCache.prototype.getAllResources = function () {
        return Array.from(this.resources.values());
    };
    /**
     * Retrieves the registered resource for the provided parameters
     *
     * @param {P} params
     * @returns {(GenericResourceCacheResource<T> | undefined)}
     */
    GenericResourceCache.prototype.getResource = function (params) {
        return this.getResourceByIdentifier(this.createResourceName(params));
    };
    GenericResourceCache.prototype.getResourceByIdentifier = function (identifier) {
        return this.resources.get(identifier);
    };
    GenericResourceCache.prototype.getResourcesByUser = function (componentId) {
        var e_2, _a;
        var result = [];
        try {
            for (var _b = tslib_1.__values(this.resources), _c = _b.next(); !_c.done; _c = _b.next()) {
                var _d = tslib_1.__read(_c.value, 2), resource = _d[1];
                if (resource.users.indexOf(componentId) > -1) {
                    result.push(resource);
                }
            }
        }
        catch (e_2_1) { e_2 = { error: e_2_1 }; }
        finally {
            try {
                if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
            }
            finally { if (e_2) throw e_2.error; }
        }
        return result;
    };
    /**
     * Retrieves all resources that are fully or partially owned by the provided component
     *
     * @param {string} componentId
     * @returns {GenericResourceCacheResource<T, P>[]}
     */
    GenericResourceCache.prototype.getResourcesByOwner = function (componentId) {
        var e_3, _a;
        var result = [];
        try {
            for (var _b = tslib_1.__values(this.resources), _c = _b.next(); !_c.done; _c = _b.next()) {
                var _d = tslib_1.__read(_c.value, 2), resource = _d[1];
                if (resource.owner === componentId) {
                    result.push(resource);
                }
            }
        }
        catch (e_3_1) { e_3 = { error: e_3_1 }; }
        finally {
            try {
                if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
            }
            finally { if (e_3) throw e_3.error; }
        }
        return result;
    };
    /**
     * Registers a new resource.
     * Overrides existing resources
     *
     * @param {P} params
     * @param {T} data
     * @returns {GenericResourceCacheResource<T>}
     */
    GenericResourceCache.prototype.addResource = function (params, data) {
        var identifier = this.createResourceName(params);
        var owner = params.owner, dependencies = params.dependencies, specificParams = tslib_1.__rest(params, ["owner", "dependencies"]);
        var resource = {
            identifier: identifier,
            users: [],
            owner: owner,
            dependencies: dependencies,
            data: data,
            params: specificParams,
        };
        this.resources.set(identifier, resource);
        if (this.resourceDefinition.onResourceAdded !== undefined) {
            this.resourceDefinition.onResourceAdded(resource);
        }
        return resource;
    };
    /**
     * Adds a user for the specified resource, if it's not yet registered as a user
     *
     * @param {string} identifier
     * @param {string} componentId
     */
    GenericResourceCache.prototype.addResourceUser = function (identifier, componentId) {
        var resource = this.resources.get(identifier);
        if (resource === undefined) {
            throw new Error("Can not add \"" + componentId + "\" to the users of \"" + identifier + "\", because no such resource exists");
        }
        if (resource.users.indexOf(componentId) === -1) {
            resource.users.push(componentId);
        }
    };
    /**
     * Removes a user from the specified resource
     *
     * @param {string} identifier
     * @param {string} componentId
     * @returns {void}
     */
    GenericResourceCache.prototype.removeResourceUser = function (identifier, componentId) {
        var resource = this.resources.get(identifier);
        if (resource === undefined) {
            return;
        }
        var index = resource.users.indexOf(componentId);
        if (index > -1) {
            resource.users.splice(index, 1);
        }
        if (resource.users.length === 0 && resource.owner) {
            // for now only drop resources which are not used and belong a single component
            this.dropResourceByKey(identifier);
        }
    };
    /**
     *  Checks wether a resource exists
     *
     * @param {P} params
     * @returns {boolean}
     */
    GenericResourceCache.prototype.hasResource = function (params) {
        return this.resources.has(this.createResourceName(params));
    };
    /**
     * Removes a resource
     *
     * @param {P} params
     */
    GenericResourceCache.prototype.removeResource = function (identifier) {
        this.dropResourceByKey(identifier);
    };
    /**
     * Removes all resources (also dependency resources) that are only used by this component or children of this component
     *
     * @param {string} componentId
     */
    GenericResourceCache.prototype.cleanupResources = function (componentId) {
        var e_4, _a, e_5, _b;
        try {
            for (var _c = tslib_1.__values(this.resources), _d = _c.next(); !_d.done; _d = _c.next()) {
                var _e = tslib_1.__read(_d.value, 2), resource = _e[1];
                // if the resource owner is a sub component of the provided component
                if (resource.owner !== undefined &&
                    resource.owner.indexOf(componentId) > -1 &&
                    resource.users.length <= 1) {
                    this.dropResourceByKey(resource.identifier);
                    continue;
                }
                if (resource.owner === componentId && resource.users.length <= 1) {
                    this.dropResourceByKey(resource.identifier);
                    continue;
                }
                var invalidUsers = resource.users.filter(function (user) { return user.indexOf(componentId) > -1; });
                try {
                    for (var invalidUsers_1 = (e_5 = void 0, tslib_1.__values(invalidUsers)), invalidUsers_1_1 = invalidUsers_1.next(); !invalidUsers_1_1.done; invalidUsers_1_1 = invalidUsers_1.next()) {
                        var componentId_1 = invalidUsers_1_1.value;
                        this.removeResourceUser(resource.identifier, componentId_1);
                    }
                }
                catch (e_5_1) { e_5 = { error: e_5_1 }; }
                finally {
                    try {
                        if (invalidUsers_1_1 && !invalidUsers_1_1.done && (_b = invalidUsers_1.return)) _b.call(invalidUsers_1);
                    }
                    finally { if (e_5) throw e_5.error; }
                }
            }
        }
        catch (e_4_1) { e_4 = { error: e_4_1 }; }
        finally {
            try {
                if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
            }
            finally { if (e_4) throw e_4.error; }
        }
    };
    GenericResourceCache.prototype.createResourceName = function (params) {
        var name = this.resourceDefinition.createResourceIdentifier(params);
        if (params.owner !== undefined) {
            name += ">>>" + params.owner;
        }
        return name;
    };
    GenericResourceCache.prototype.dropResourceByKey = function (key) {
        var resource = this.resources.get(key);
        if (resource === undefined) {
            return;
        }
        if (this.resourceDefinition.onResourceRemoved !== undefined) {
            this.resourceDefinition.onResourceRemoved(resource);
        }
        this.resources.delete(key);
    };
    return GenericResourceCache;
}());
exports.GenericResourceCache = GenericResourceCache;
