'use strict';

var dot = require('dot');
dot.templateSettings.strip = false;

function createTemplate(method) {
    return dot.template([
        'SELECT',
        'min({{=it._column}}) min_val,',
        'max({{=it._column}}) max_val,',
        'avg({{=it._column}}) avg_val,',
        method,
        'FROM ({{=it._sql}}) _table_sql WHERE {{=it._column}} IS NOT NULL'
    ].join('\n'));
}

var methods = {
    quantiles: 'CDB_QuantileBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as quantiles',
    equal: 'CDB_EqualIntervalBins(array_agg({{=it._column}}::numeric), {{=it._buckets}}) as equal',
    jenks: 'CDB_JenksBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as jenks',
    headtails: 'CDB_HeadsTailsBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as headtails'
};

var methodTemplates = Object.keys(methods).reduce(function(methodTemplates, methodName) {
    methodTemplates[methodName] = createTemplate(methods[methodName]);
    return methodTemplates;
}, {});

methodTemplates.category = dot.template([
    'WITH',
    'categories AS (',
    '  SELECT {{=it._column}} AS category, count(1) AS value, row_number() OVER (ORDER BY count(1) desc) as rank',
    '  FROM ({{=it._sql}}) _cdb_aggregation_all',
    '  GROUP BY {{=it._column}}',
    '  ORDER BY 2 DESC, 1 ASC',
    '),',
    'agg_categories AS (',
    '  SELECT category',
    '  FROM categories',
    '  WHERE rank <= {{=it._buckets}}',
    ')',
    'SELECT array_agg(category) AS category FROM agg_categories'
].join('\n'));

var STRATEGY = {
    SPLIT: 'split',
    EXACT: 'exact'
};

var method2strategy = {
    headtails: STRATEGY.SPLIT,
    category: STRATEGY.EXACT
};

function PostgresDatasource (psql, query) {
    this.psql = psql;
    this.query = query;
}

PostgresDatasource.prototype.getName = function () {
    return 'PostgresDatasource';
};

PostgresDatasource.prototype.getRamp = function (column, buckets, method, callback) {
    if (method && !methodTemplates.hasOwnProperty(method)) {
        return callback(new Error(
            'Invalid method "' + method + '", valid methods: [' + Object.keys(methodTemplates).join(',') + ']'
        ));
    }
    var methodName = method || 'quantiles';
    var template = methodTemplates[methodName];

    var query = template({ _column: column, _sql: this.query, _buckets: buckets });

    this.psql.query(query, function (err, resultSet) {
        if (err) {
            return callback(err);
        }

        var result = getResult(resultSet);
        var strategy = method2strategy[methodName];
        var ramp = result[methodName] || [];
        var stats = {
            min_val: result.min_val,
            max_val: result.max_val,
            avg_val: result.avg_val
        };
        // Skip null values from ramp
        // Generated turbo-carto won't be correct, but better to keep it working than failing
        // TODO fix cartodb-postgres extension quantification functions
        ramp = ramp.filter(function(value) { return value !== null; });
        if (strategy !== STRATEGY.EXACT) {
            ramp = ramp.sort(function(a, b) {
                return a - b;
            });
        }

        return callback(null, { ramp: ramp, strategy: strategy, stats: stats });
    }, true); // use read-only transaction
};

function getResult(resultSet) {
    resultSet = resultSet || {};
    var result = resultSet.rows || [];
    result = result[0] || {};

    return result;
}

module.exports = PostgresDatasource;
