var testHelper = require('../../../support/test_helper');
var LayergroupToken = require('../../../support/layergroup-token');

var step = require('step');
var assert = require('../../../support/assert');
var _ = require('underscore');
var querystring = require('querystring');
var mapnik = require('windshaft').mapnik;
var CartodbServer = require('../../../../lib/cartodb/server');
var PortedServerOptions = require('./ported_server_options');

var DEFAULT_POINT_STYLE = [
    '#layer {',
    '  marker-fill: #FF6600;',
    '  marker-opacity: 1;',
    '  marker-width: 16;',
    '  marker-line-color: white;',
    '  marker-line-width: 3;',
    '  marker-line-opacity: 0.9;',
    '  marker-placement: point;',
    '  marker-type: ellipse;',
    '  marker-allow-overlap: true;',
    '}'
].join('');

module.exports = {
    createLayergroup: createLayergroup,
    withLayergroup: withLayergroup,

    singleLayerMapConfig: singleLayerMapConfig,
    defaultTableMapConfig: defaultTableMapConfig,

    getStaticBbox: getStaticBbox,
    getStaticCenter: getStaticCenter,
    getGrid: getGrid,
    getGridJsonp: getGridJsonp,
    getTorque: getTorque,
    getTile: getTile,
    getTileLayer: getTileLayer
};


var server = new CartodbServer(PortedServerOptions);
server.setMaxListeners(0);

var jsonContentType = 'application/json; charset=utf-8';
var jsContentType = 'text/javascript; charset=utf-8';
var pngContentType = 'image/png';

function createLayergroup(layergroupConfig, options, callback) {
    if (!callback) {
        callback = options;
        options = {
            method: 'POST',
            statusCode: 200
        };
    }

    var expectedResponse = {
        status: options.statusCode || 200,
        headers: options.headers || {
            'Content-Type': 'application/json; charset=utf-8'
        }
    };

    step(
        function requestLayergroup() {
            var next = this;
            var request = layergroupRequest(layergroupConfig, options.method, options.callbackName, options.params);
            assert.response(serverInstance(options), request, expectedResponse, function (res, err) {
                next(err, res);
            });
        },
        function validateLayergroup(err, res) {
            assert.ok(!err, 'Failed to request layergroup');

            var parsedBody;
            var layergroupid;
            if (options.callbackName) {
                global[options.callbackName] = function(layergroup) {
                    layergroupid = layergroup.layergroupid;
                };
                // jshint ignore:start
                eval(res.body);
                // jshint ignore:end
                delete global[options.callbackName];
            } else {
                parsedBody = JSON.parse(res.body);
                layergroupid = parsedBody.layergroupid;
                if (layergroupid) {
                    layergroupid = LayergroupToken.parse(layergroupid).token;
                }
            }

            if (layergroupid) {
                var keysToDelete = {
                    'user:localhost:mapviews:global': 5
                };
                var redisKey = 'map_cfg|' + layergroupid;
                keysToDelete[redisKey] = 0;
                testHelper.deleteRedisKeys(keysToDelete, function() {
                    return callback(err, res, parsedBody);
                });
            } else {
                return callback(err, res, parsedBody);
            }
        }
    );
}

function serverInstance(options) {
    if (options.server) {
        return options.server;
    }

    if (options.serverOptions) {
        var otherServer = new CartodbServer(options.serverOptions);
        otherServer.req2params = options.serverOptions.req2params;
        otherServer.setMaxListeners(0);
        return otherServer;
    }

    return server;
}

function layergroupRequest(layergroupConfig, method, callbackName, extraParams) {
    method = method || 'POST';

    var request = {
        url: '/database/windshaft_test/layergroup',
        headers: {
            'Content-Type': 'application/json'
        }
    };

    var urlParams = _.extend({}, extraParams);
    if (callbackName) {
        urlParams.callback = callbackName;
    }

    if (method.toUpperCase() === 'GET') {
        request.method = 'GET';
        urlParams.config = JSON.stringify(layergroupConfig);
    } else {
        request.method = 'POST';
        request.data = JSON.stringify(layergroupConfig);
    }

    if (Object.keys(urlParams).length) {
        request.url += '?' + querystring.stringify(urlParams);
    }

    return request;
}

function singleLayerMapConfig(sql, cartocss, cartocssVersion, interactivity) {
    return {
        version: '1.3.0',
        layers: [
            {
                type: 'mapnik',
                options: {
                    sql: sql,
                    cartocss: cartocss || DEFAULT_POINT_STYLE,
                    cartocss_version: cartocssVersion || '2.3.0',
                    interactivity: interactivity,
                    geom_column: 'the_geom'
                }
            }
        ]
    };
}

function defaultTableMapConfig(tableName, cartocss, cartocssVersion, interactivity) {
    return singleLayerMapConfig(defaultTableQuery(tableName), cartocss, cartocssVersion, interactivity);
}

function defaultTableQuery(tableName) {
    return _.template('SELECT * FROM <%= tableName %>', {tableName: tableName});
}

function getStaticBbox(layergroupConfig, west, south, east, north, width, height, expectedResponse, callback) {
    if (!callback) {
        callback = expectedResponse;
        expectedResponse = pngContentType;
    }

    var url = [
        'static',
        'bbox',
        '<%= layergroupid %>',
        [west, south, east, north].join(','),
        width,
        height
    ].join('/') + '.png';
    return getGeneric(layergroupConfig, url, expectedResponse, callback);
}

function getStaticCenter(layergroupConfig, zoom, lat, lon, width, height, expectedResponse, callback) {
    if (!callback) {
        callback = expectedResponse;
        expectedResponse = pngContentType;
    }

    var url = [
        'static',
        'center',
        '<%= layergroupid %>',
        zoom,
        lat,
        lon,
        width,
        height
    ].join('/') + '.png';
    return getGeneric(layergroupConfig, url, expectedResponse, callback);
}

function getGrid(layergroupConfig, layer, z, x, y, expectedResponse, callback) {
    if (!callback) {
        callback = expectedResponse;
        expectedResponse = jsonContentType;
    }

    var options = {
        layer: layer,
        z: z,
        x: x,
        y: y,
        format: 'grid.json'
    };
    return getLayer(layergroupConfig, options, expectedResponse, callback);
}

function getGridJsonp(layergroupConfig, layer, z, x, y, jsonpCallbackName, expectedResponse, callback) {
    if (!callback) {
        callback = expectedResponse;
        expectedResponse = jsContentType;
    }

    var options = {
        layer: layer,
        z: z,
        x: x,
        y: y,
        format: 'grid.json',
        jsonpCallbackName: jsonpCallbackName
    };
    return getLayer(layergroupConfig, options, expectedResponse, callback);
}

function getTorque(layergroupConfig, layer, z, x, y, expectedResponse, callback) {
    if (!callback) {
        callback = expectedResponse;
        expectedResponse = jsonContentType;
    }

    var options = {
        layer: layer,
        z: z,
        x: x,
        y: y,
        format: 'torque.json'
    };
    return getLayer(layergroupConfig, options, expectedResponse, callback);
}

function getTile(layergroupConfig, z, x, y, expectedResponse, callback) {
    if (!callback) {
        callback = expectedResponse;
        expectedResponse = pngContentType;
    }

    var options = {
        z: z,
        x: x,
        y: y,
        format: 'png'
    };
    return getLayer(layergroupConfig, options, expectedResponse, callback);
}

function getTileLayer(layergroupConfig, options, expectedResponse, callback) {
    if (!callback) {
        callback = expectedResponse;
        expectedResponse = pngContentType;
    }

    return getLayer(layergroupConfig, options, expectedResponse, callback);
}

function getLayer(layergroupConfig, options, expectedResponse, callback) {
    return getGeneric(layergroupConfig, tileUrlStrategy(options), expectedResponse, callback);
}

function tileUrlStrategy(options) {
    var urlLayerPattern = [
        '<%= layer %>',
        '<%= z %>',
        '<%= x %>',
        '<%= y %>'
    ].join('/') + '.<%= format %>';

    if (options.jsonpCallbackName) {
        urlLayerPattern += '?callback=<%= jsonpCallbackName %>';
    }

    var urlNoLayerPattern = [
        '<%= z %>',
        '<%= x %>',
        '<%= y %>'
    ].join('/') + '.<%= format %>';

    var urlTemplate = _.template((options.layer === undefined) ? urlNoLayerPattern : urlLayerPattern);

    options.format = options.format || 'png';

    return '<%= layergroupid %>/' + urlTemplate(_.defaults(options, { z: 0, x: 0, y: 0, layer: 0 }));
}

function getGeneric(layergroupConfig, url, expectedResponse, callback) {
    if (_.isString(expectedResponse)) {
        expectedResponse = {
            status: 200,
            headers: {
                'Content-Type': expectedResponse
            }
        };
    }
    var contentType = expectedResponse.headers['Content-Type'];

    var layergroupid = null;

    step(
        function requestLayergroup() {
            var next = this;
            var request = {
                url: '/database/windshaft_test/layergroup',
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                data: JSON.stringify(layergroupConfig)
            };
            var expectedResponse = {
                status: 200,
                headers: {
                    'Content-Type': 'application/json; charset=utf-8'
                }
            };
            assert.response(server, request, expectedResponse, function (res, err) {
                next(err, res);
            });
        },
        function validateLayergroup(err, res) {
            assert.ok(!err, 'Failed to create layergroup');

            var parsedBody = JSON.parse(res.body);
            layergroupid = parsedBody.layergroupid;

            assert.ok(layergroupid);

            return res;
        },
        function requestTile(err, res) {
            assert.ok(!err, 'Invalid layergroup response: ' + res.body);

            var next = this;

            var finalUrl = '/database/windshaft_test/layergroup/' + _.template(url, {
                layergroupid: layergroupid
            });

            var request = {
                url: finalUrl,
                method: 'GET'
            };

            if (contentType === pngContentType) {
                request.encoding = 'binary';
            }

            assert.response(server, request, expectedResponse, function (res, err) {
                next(err, res);
            });
        },
        function validateTile(err, res) {
            assert.ok(!err, 'Failed to get tile');

            var img;
            if (contentType === pngContentType) {
                img = new mapnik.Image.fromBytesSync(new Buffer(res.body, 'binary'));
            }

            var keysToDelete = {
                'user:localhost:mapviews:global': 5
            };
            var redisKey = 'map_cfg|' + LayergroupToken.parse(layergroupid).token;
            keysToDelete[redisKey] = 0;
            testHelper.deleteRedisKeys(keysToDelete, function() {
                return callback(err, res, img);
            });
        }
    );
}

function withLayergroup(layergroupConfig, options, callback) {
    var validationLayergroupFn = function() {};
    if (!callback) {
        callback = options;
        options = {};
    }

    if (_.isFunction(options)) {
        validationLayergroupFn = options;
        options = {};
    }

    var layergroupExpectedResponse = {
        status: 200,
        headers: {
            'Content-Type': 'application/json; charset=utf-8'
        }
    };

    step(
        function requestLayergroup() {
            var next = this;
            var request = layergroupRequest(layergroupConfig, 'POST');
            assert.response(serverInstance(options), request, layergroupExpectedResponse, function (res, err) {
                next(err, res);
            });
        },
        function validateLayergroup(err, res) {
            assert.ok(!err, 'Failed to request layergroup');

            var parsedBody = JSON.parse(res.body);
            var layergroupid = parsedBody.layergroupid;

            assert.ok(layergroupid, 'No layergroup was created');

            validationLayergroupFn(res);

            function requestTile(layergroupUrl, options, callback) {
                if (!callback) {
                    callback = options;
                    options = {
                        statusCode: 200,
                        contentType: pngContentType
                    };
                }

                var baseUrlTpl = '/database/windshaft_test/layergroup/<%= layergroupid %>';
                var finalUrl = _.template(baseUrlTpl, { layergroupid: layergroupid }) + layergroupUrl;

                var request = {
                    url: finalUrl,
                    method: 'GET'
                };

                if (options.contentType === pngContentType) {
                    request.encoding = 'binary';
                }

                var tileExpectedResponse = {
                    status: options.statusCode || 200,
                    headers: {
                        'Content-Type': options.contentType || pngContentType
                    }
                };

                assert.response(serverInstance(options), request, tileExpectedResponse, function (res, err) {
                    callback(err, res);
                });
            }

            function finish(done) {
                var keysToDelete = {
                    'user:localhost:mapviews:global': 5
                };
                var redisKey = 'map_cfg|' + LayergroupToken.parse(layergroupid).token;
                keysToDelete[redisKey] = 0;
                testHelper.deleteRedisKeys(keysToDelete, done);
            }

            return callback(err, requestTile, finish);
        }
    );
}
