'use strict';

var path = require('path');
var mapnik = require('@carto/mapnik');
var mapnik_pool = require('mapnik-pool');
var timeoutDecorator = require('./utils/timeout-decorator')

// Register datasource plugins
mapnik.register_default_input_plugins();

var mapnikPool = mapnik_pool(mapnik);

module.exports = Bridge;

function Bridge(uri, callback) {
    if (!uri.xml) {
        return callback(new Error('No xml'));
    }

    // whether to compress the vector tiles or not, true by default
    this._gzip = typeof uri.gzip === 'boolean' ? uri.gzip : true;

    uri.limits = (uri.query && uri.query.limits) ? uri.query.limits : {};

    if (typeof uri.limits.render === 'undefined') {
        uri.limits.render = 0;
    }

    if (uri.limits.render > 0) {
        const errorMsg = 'Render timed out';
        this.getTile = timeoutDecorator(this.getTile.bind(this), uri.limits.render, errorMsg);
    }

    // For currently unknown reasons map objects can currently be acquired
    // without being released under certain circumstances. When this occurs
    // a source cannot be closed fully during a copy or other operation. For
    // now error out in these scenarios as a close timeout.
    const errorMsgClose = 'Source resource pool drain timed out after 5s';
    this.close = timeoutDecorator(this.close.bind(this), 5000, errorMsgClose);

    const bufferSize = (uri.query && Number.isFinite(uri.query.bufferSize) && uri.query.bufferSize >= 0) ? uri.query.bufferSize : 256;
    const initOptions = { size: 256, bufferSize };
    const mapOptions = { strict: false, base: `${path.resolve(uri.base || __dirname)}/` };

    this._mapPool = mapnikPool.fromString(uri.xml, initOptions, mapOptions);

    return callback(null, this);
}

Bridge.registerProtocols = function(tilelive) {
    tilelive.protocols['bridge:'] = Bridge;
};

Bridge.prototype.close = function (callback) {
    this._mapPool.drain(() => this._mapPool.destroyAllNow(callback));
};

Bridge.prototype.getTile = function (z, x, y, callback) {
    this._mapPool.acquire((err, map) => {
        if (err) {
            return callback(err);
        }

        var opts = {};

        var headers = {};
        headers['Content-Type'] = 'application/x-protobuf';

        // The buffer size is in vector tile coordinates, while the buffer size on the
        // map object is in image coordinates. Therefore, lets multiply the buffer_size
        // by the old "path_multiplier" value of 16 to get a proper buffer size.
        try {
            // Try-catch is necessary here because the constructor will throw if x and y
            // are out of bounds at zoom-level z
            var vtile = new mapnik.VectorTile(+z,+x,+y, {buffer_size:16*map.bufferSize});
        } catch(err) {
            return callback(err, null, headers);
        }

        map.extent = vtile.extent();

        // Since we (CARTO) are already simplifying the geometries in the Postgresql query
        // we don't want another simplification as it will have a visual impact
        opts.simplify_distance = 0;

        opts.threading_mode = getThreadingMode(map);

        // enable strictly_simple
        opts.strictly_simple = true;

        // make zoom, x, y and bbox variables available to mapnik postgis datasource
        opts.variables = {
            zoom_level: z, // for backwards compatibility
            zoom: z,
            x: x,
            y: y,
            bbox: JSON.stringify(map.extent)
        };

        map.render(vtile, opts, (err, vtile) => {
            this._mapPool.release(map);

            if (err) {
                return callback(err);
            }

            headers['x-tilelive-contains-data'] = vtile.painted();
            if (vtile.empty()) {
                return callback(null, new Buffer(0), headers);
            }
            vtile.getData({ compression: this._gzip ? 'gzip' : 'none' }, (err, pbfz) => {
                if (err) {
                    return callback(err);
                }

                if (this._gzip) {
                    headers['Content-Encoding'] = 'gzip';
                }

                return callback(err, pbfz, headers);
            });
        });
    });
};

// We (CARTO) are using always the default value: deferred = 2;
function getThreadingMode (map) {
    const threadingType = map.parameters.threading_mode;

    if (threadingType === 'auto') {
        return mapnik.threadingMode.auto; // 3
    }

    if (threadingType === 'async') {
        return mapnik.threadingMode.async; // 1
    }

    return mapnik.threadingMode.deferred; // 2
}

@@ -8,19 +8,21 @@
 rict';%0A%0A
-var
+const
  path = 
@@ -34,27 +34,29 @@
 re('path');%0A
-var
+const
  mapnik = re
@@ -79,19 +79,21 @@
 pnik');%0A
-var
+const
  mapnik_
@@ -123,19 +123,21 @@
 pool');%0A
-var
+const
  timeout
@@ -259,19 +259,21 @@
 ins();%0A%0A
-var
+const
  mapnikP
@@ -2140,15 +2140,20 @@
     
-var
+const
  opt
+ion
 s = 
@@ -2165,19 +2165,21 @@
         
-var
+const
  headers
@@ -2246,16 +2246,35 @@
 obuf';%0A%0A
+        let vtile;%0A
         
@@ -2689,12 +2689,8 @@
     
-var 
 vtil
@@ -2726,16 +2726,17 @@
 +x,+y, %7B
+ 
 buffer_s
@@ -2743,11 +2743,14 @@
 ize:
+ 
 16
-*
+ * 
 map.
@@ -2759,16 +2759,17 @@
 fferSize
+ 
 %7D);%0A    
@@ -3067,16 +3067,19 @@
      opt
+ion
 s.simpli
@@ -3107,16 +3107,19 @@
      opt
+ion
 s.thread
@@ -3198,16 +3198,19 @@
      opt
+ion
 s.strict
@@ -3324,16 +3324,19 @@
      opt
+ion
 s.variab
@@ -3544,16 +3544,19 @@
 ile, opt
+ion
 s, (err,

Rename variable