var _ = require('underscore');
var dot = require('dot');
var NamedMapMapConfigProvider = require('../models/mapconfig/provider/named-map-provider');
var templateName = require('../backends/template_maps').templateName;
var queue = require('queue-async');

var LruCache = require("lru-cache");

function NamedMapProviderCache(templateMaps, pgConnection, metadataBackend, userLimitsApi, mapConfigAdapter) {
    this.templateMaps = templateMaps;
    this.pgConnection = pgConnection;
    this.metadataBackend = metadataBackend;
    this.userLimitsApi = userLimitsApi;
    this.mapConfigAdapter = mapConfigAdapter;

    this.providerCache = new LruCache({ max: 2000 });
}

module.exports = NamedMapProviderCache;

NamedMapProviderCache.prototype.get = function(user, templateId, config, authToken, params, callback) {
    var namedMapKey = createNamedMapKey(user, templateId);
    var namedMapProviders = this.providerCache.get(namedMapKey) || {};

    var providerKey = createProviderKey(config, authToken, params);
    if (!namedMapProviders.hasOwnProperty(providerKey)) {
        namedMapProviders[providerKey] = new NamedMapMapConfigProvider(
            this.templateMaps,
            this.pgConnection,
            this.metadataBackend,
            this.userLimitsApi,
            this.mapConfigAdapter,
            user,
            templateId,
            config,
            authToken,
            params
        );
        this.providerCache.set(namedMapKey, namedMapProviders);

        // early exit, if provider did not exist we just return it
        return callback(null, namedMapProviders[providerKey]);
    }

    var namedMapProvider = namedMapProviders[providerKey];

    var self = this;
    queue(2)
        .defer(namedMapProvider.getTemplate.bind(namedMapProvider))
        .defer(this.templateMaps.getTemplate.bind(this.templateMaps), user, templateId)
        .awaitAll(function templatesQueueDone(err, results) {
            if (err) {
                return callback(err);
            }

            // We want to reset provider its template has changed
            // Ideally this should be done in a passive mode where this cache gets notified of template changes
            var uniqueFingerprints = _.uniq(results.map(self.templateMaps.fingerPrint)).length;
            if (uniqueFingerprints > 1) {
                namedMapProvider.reset();
            }
            return callback(null, namedMapProvider);
        });
};

NamedMapProviderCache.prototype.invalidate = function(user, templateId) {
    this.providerCache.del(createNamedMapKey(user, templateId));
};

function createNamedMapKey(user, templateId) {
    return user + ':' + templateName(templateId);
}

var providerKey = '{{=it.authToken}}:{{=it.configHash}}:{{=it.format}}:{{=it.layer}}:{{=it.scale_factor}}';
var providerKeyTpl = dot.template(providerKey);

function createProviderKey(config, authToken, params) {
    var tplValues = _.defaults({}, params, {
        authToken: authToken || '',
        configHash: NamedMapMapConfigProvider.configHash(config),
        layer: '',
        format: '',
        scale_factor: 1
    });
    return providerKeyTpl(tplValues);
}
