import { Inject, InjectionToken, NgModule } from '@angular/core';
import { BehaviorSubject as BehaviorSubject$1 } from 'rxjs/BehaviorSubject';
import { Observable as Observable$1 } from 'rxjs/Observable';
import { Observable as Observable$2 } from 'rxjs/Rx';

class CacheLayer extends Map {
    /**
     * @param {?} layer
     */
    constructor(layer) {
        super();
        this.items = new BehaviorSubject$1([]);
        this.name = layer.name;
        this.config = layer.config;
        if (this.config.localStorage) {
            layer.items.forEach(item => this.set(item['key'], item));
            if (layer.items.constructor === BehaviorSubject$1) {
                layer.items = layer.items.getValue() || [];
            }
            this.items.next([...this.items.getValue(), ...layer.items]);
        }
        this.initHook(layer);
    }
    /**
     * @param {?} config
     * @return {?}
     */
    static createCacheParams(config) {
        if (config.params.constructor === Object) {
            return; // Todo
        }
        else if (config.constructor === String) {
            return; // Todo
        }
        else if (config.params.constructor === Number) {
            return; // Todo
        }
        else if (config.params.constructor === Array) {
            return; // Todo
        }
    }
    /**
     * @param {?} key
     * @param {?} data
     * @return {?}
     */
    set(key, data) {
        return super.set(key, data);
    }
    /**
     * @param {?} name
     * @return {?}
     */
    get(name) {
        return super.get(name);
    }
    /**
     * @param {?} layer
     * @return {?}
     */
    initHook(layer) {
        this.onExpireAll(layer);
    }
    /**
     * @param {?} layer
     * @return {?}
     */
    onExpireAll(layer) {
        layer.items.forEach(item => this.onExpire(item['key']));
    }
    /**
     * @param {?} layerItem
     * @return {?}
     */
    putItemHook(layerItem) {
        this.onExpire(layerItem['key']);
    }
    /**
     * @param {?} key
     * @return {?}
     */
    getItem(key) {
        if (this.has(key)) {
            return this.get(key);
        }
        else {
            return null;
        }
    }
    /**
     * @param {?} layerItem
     * @return {?}
     */
    putItem(layerItem) {
        this.set(layerItem['key'], layerItem);
        const /** @type {?} */ item = this.get(layerItem['key']);
        const /** @type {?} */ filteredItems = this.items.getValue().filter(item => item['key'] !== layerItem['key']);
        if (this.config.localStorage) {
            localStorage.setItem(this.name, JSON.stringify(/** @type {?} */ ({
                config: this.config,
                name: this.name,
                items: [...filteredItems, item]
            })));
        }
        this.items.next([...filteredItems, item]);
        this.putItemHook(layerItem);
        return layerItem;
    }
    /**
     * @param {?} key
     * @return {?}
     */
    onExpire(key) {
        Observable$2
            .create(observer => observer.next())
            .timeoutWith(this.config.maxAge, Observable$2.of(1))
            .skip(1)
            .subscribe(observer => this.removeItem(key));
    }
    /**
     * @param {?} key
     * @return {?}
     */
    removeItem(key) {
        let /** @type {?} */ newLayerItems = this.items.getValue().filter(item => item['key'] !== key);
        if (this.config.localStorage) {
            const /** @type {?} */ newLayer = ({
                config: this.config,
                name: this.name,
                items: newLayerItems
            });
            localStorage.setItem(this.name, JSON.stringify(newLayer));
        }
        this.delete(key);
        this.items.next(newLayerItems);
    }
    /**
     * @param {?} key
     * @return {?}
     */
    getItemObservable(key) {
        return this.items.asObservable().map(res => this.has(key) ? this.items.getValue().filter(item => item['key'] === key)[0] : null);
    }
}

// console.log(Array.from(this.keys()))

class CacheServiceConfigInterface {
    constructor() {
        this.deleteOnExpire = 'aggressive';
        this.cacheFlushInterval = 60 * 60 * 1000;
        this.maxAge = 15 * 60 * 1000;
    }
}

const INTERNAL_PROCEDURE_CACHE_NAME = 'cache_layers';
const FRIENDLY_ERROR_MESSAGES = {
    TRY_TO_UNSUBSCRIBE: 'Someone try to unsubscribe from collection directly... agghhh.. read docs! Blame: ',
    LOCAL_STORAGE_DISABLED: 'LocalStorage is disabled switching to regular in-memory storage.Please relate issue if you think it is enabled and there is a problem with the library itself.'
};
class CacheService extends Map {
    /**
     * @param {?} config
     */
    constructor(config) {
        super();
        this.config = config;
        this._cachedLayers = new BehaviorSubject$1([]);
        if (this.config.localStorage && CacheService.isLocalStorageUsable()) {
            const layers = JSON.parse(localStorage.getItem(INTERNAL_PROCEDURE_CACHE_NAME));
            if (layers) {
                layers.forEach(layer => {
                    const cachedLayer = JSON.parse(localStorage.getItem(layer));
                    if (cachedLayer) {
                        this.createLayer(cachedLayer);
                    }
                });
            }
            else {
                localStorage.setItem(INTERNAL_PROCEDURE_CACHE_NAME, JSON.stringify([]));
            }
        }
    }
    /**
     * @param {?} name
     * @return {?}
     */
    get(name) {
        return super.get(name);
    }
    /**
     * @template T
     * @param {?} cacheLayer
     * @return {?}
     */
    static createCacheInstance(cacheLayer) {
        return new CacheLayer(cacheLayer);
    }
    /**
     * @return {?}
     */
    static isLocalStorageUsable() {
        let /** @type {?} */ error = [];
        try {
            localStorage.setItem('test-key', JSON.stringify({ key: 'test-object' }));
            localStorage.removeItem('test-key');
        }
        catch (e) {
            error.push(e);
            console.log(FRIENDLY_ERROR_MESSAGES.LOCAL_STORAGE_DISABLED);
        }
        return error.length ? false : true;
    }
    /**
     * @template T
     * @param {?} name
     * @return {?}
     */
    getLayer(name) {
        const /** @type {?} */ exists = this.has(name);
        if (!exists) {
            return this.createLayer({ name: name });
        }
        return this.get(name);
    }
    /**
     * @template T
     * @param {?} layer
     * @return {?}
     */
    createLayer(layer) {
        const /** @type {?} */ exists = this.has(layer.name);
        if (exists) {
            return this.get(layer.name);
        }
        layer.items = layer.items || [];
        layer.config = layer.config || this.config || CACHE_MODULE_DI_CONFIG;
        let /** @type {?} */ cacheLayer = CacheService.createCacheInstance(layer);
        if (layer.config.localStorage && CacheService.isLocalStorageUsable()) {
            localStorage.setItem(INTERNAL_PROCEDURE_CACHE_NAME, JSON.stringify([...CacheService.getLayersFromLS().filter(l => l !== cacheLayer.name), cacheLayer.name]));
            localStorage.setItem(cacheLayer.name, JSON.stringify(layer));
        }
        this.set(cacheLayer.name, cacheLayer);
        this._cachedLayers.next([...this._cachedLayers.getValue(), cacheLayer]);
        this.LayerHook(cacheLayer);
        return cacheLayer;
    }
    /**
     * @template T
     * @param {?} layerInstance
     * @return {?}
     */
    LayerHook(layerInstance) {
        this.protectLayerFromInvaders(layerInstance);
        this.OnExpire(layerInstance);
    }
    /**
     * @template T
     * @param {?} cacheLayer
     * @return {?}
     */
    protectLayerFromInvaders(cacheLayer) {
        cacheLayer.items.constructor.prototype.unsubsribeFromLayer = cacheLayer.items.constructor.prototype.unsubscribe;
        cacheLayer.items.constructor.prototype.unsubscribe = () => {
            console.error(FRIENDLY_ERROR_MESSAGES.TRY_TO_UNSUBSCRIBE + cacheLayer.name);
        };
    }
    /**
     * @template T
     * @param {?} layerInstance
     * @return {?}
     */
    OnExpire(layerInstance) {
        Observable$1
            .create(observer => observer.next())
            .timeoutWith(layerInstance.config.cacheFlushInterval, Observable$1.of(1))
            .skip(1)
            .subscribe(observer => this.removeLayer(layerInstance));
    }
    /**
     * @template T
     * @param {?} layerInstance
     * @return {?}
     */
    removeLayer(layerInstance) {
        this.delete(layerInstance.name);
        if (this.config.localStorage) {
            localStorage.removeItem(layerInstance.name);
            localStorage.setItem(INTERNAL_PROCEDURE_CACHE_NAME, JSON.stringify(CacheService.getLayersFromLS().filter(layer => layer !== layerInstance.name)));
        }
        this._cachedLayers.next([...this._cachedLayers.getValue().filter(layer => layer.name !== layerInstance.name)]);
    }
    /**
     * @param {?} name
     * @param {?} newCacheLayers
     * @return {?}
     */
    transferItems(name, newCacheLayers) {
        const /** @type {?} */ oldLayer = this.getLayer(name);
        const /** @type {?} */ newLayers = [];
        newCacheLayers.forEach(layerName => {
            const /** @type {?} */ newLayer = this.createLayer(layerName);
            oldLayer.items.getValue().forEach(item => newLayer.putItem(item));
            newLayers.push(newLayer);
        });
        return newLayers;
    }
    /**
     * @return {?}
     */
    static getLayersFromLS() {
        return JSON.parse(localStorage.getItem(INTERNAL_PROCEDURE_CACHE_NAME));
    }
}
/**
 * @nocollapse
 */
CacheService.ctorParameters = () => [
    { type: CacheServiceConfigInterface, decorators: [{ type: Inject, args: [CACHE_MODULE_CONFIG,] },] },
];

const CACHE_MODULE_CONFIG = new InjectionToken('module.config');
const CACHE_MODULE_DI_CONFIG = ({
    deleteOnExpire: 'aggressive',
    cacheFlushInterval: 60 * 60 * 1000,
    maxAge: 15 * 60 * 1000
});
class CacheModule {
    /**
     * @param {?=} config
     * @return {?}
     */
    static forRoot(config) {
        return {
            ngModule: CacheModule,
            providers: [
                { provide: CACHE_MODULE_CONFIG, useValue: config || CACHE_MODULE_DI_CONFIG },
                CacheService
            ]
        };
    }
}
CacheModule.decorators = [
    { type: NgModule },
];
/**
 * @nocollapse
 */
CacheModule.ctorParameters = () => [];

export { CACHE_MODULE_CONFIG, CACHE_MODULE_DI_CONFIG, CacheModule, CacheService, CacheLayer, CacheServiceConfigInterface };
