// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 (see README.md for details)

const { parse_level_indexes, parse_pg_dsl } = require('./lp_optimizer/dsl_pgs.js');

function validate_pool_cfg(pool_id, pool_cfg, placement_levels, warn)
{
    pool_cfg.pg_size = Math.floor(pool_cfg.pg_size);
    pool_cfg.pg_minsize = Math.floor(pool_cfg.pg_minsize);
    pool_cfg.parity_chunks = Math.floor(pool_cfg.parity_chunks) || undefined;
    pool_cfg.pg_count = Math.floor(pool_cfg.pg_count);
    pool_cfg.max_osd_combinations = Math.floor(pool_cfg.max_osd_combinations) || 10000;
    if (!/^[1-9]\d*$/.exec(''+pool_id))
    {
        if (warn)
            console.log('Pool ID '+pool_id+' is invalid');
        return false;
    }
    if (pool_cfg.scheme !== 'xor' && pool_cfg.scheme !== 'replicated' &&
        pool_cfg.scheme !== 'ec' && pool_cfg.scheme !== 'jerasure')
    {
        if (warn)
            console.log('Pool '+pool_id+' has invalid coding scheme (one of "xor", "replicated", "ec" and "jerasure" required)');
        return false;
    }
    if (!pool_cfg.pg_size || pool_cfg.pg_size < 1 || pool_cfg.pg_size > 256 ||
        pool_cfg.scheme !== 'replicated' && pool_cfg.pg_size < 3)
    {
        if (warn)
            console.log('Pool '+pool_id+' has invalid pg_size');
        return false;
    }
    if (!pool_cfg.pg_minsize || pool_cfg.pg_minsize < 1 || pool_cfg.pg_minsize > pool_cfg.pg_size ||
        pool_cfg.scheme === 'xor' && pool_cfg.pg_minsize < (pool_cfg.pg_size - 1))
    {
        if (warn)
            console.log('Pool '+pool_id+' has invalid pg_minsize');
        return false;
    }
    if (pool_cfg.scheme === 'xor' && pool_cfg.parity_chunks != 0 && pool_cfg.parity_chunks != 1)
    {
        if (warn)
            console.log('Pool '+pool_id+' has invalid parity_chunks (must be 1)');
        return false;
    }
    if ((pool_cfg.scheme === 'ec' || pool_cfg.scheme === 'jerasure') &&
        (pool_cfg.parity_chunks < 1 || pool_cfg.parity_chunks > pool_cfg.pg_size-2))
    {
        if (warn)
            console.log('Pool '+pool_id+' has invalid parity_chunks (must be between 1 and pg_size-2)');
        return false;
    }
    if (!pool_cfg.pg_count || pool_cfg.pg_count < 1)
    {
        if (warn)
            console.log('Pool '+pool_id+' has invalid pg_count');
        return false;
    }
    if (!pool_cfg.name)
    {
        if (warn)
            console.log('Pool '+pool_id+' has empty name');
        return false;
    }
    if (pool_cfg.max_osd_combinations < 100)
    {
        if (warn)
            console.log('Pool '+pool_id+' has invalid max_osd_combinations (must be at least 100)');
        return false;
    }
    if (pool_cfg.root_node && typeof(pool_cfg.root_node) != 'string')
    {
        if (warn)
            console.log('Pool '+pool_id+' has invalid root_node (must be a string)');
        return false;
    }
    if (pool_cfg.osd_tags && typeof(pool_cfg.osd_tags) != 'string' &&
        (!(pool_cfg.osd_tags instanceof Array) || pool_cfg.osd_tags.filter(t => typeof t != 'string').length > 0))
    {
        if (warn)
            console.log('Pool '+pool_id+' has invalid osd_tags (must be a string or array of strings)');
        return false;
    }
    if (pool_cfg.primary_affinity_tags && typeof(pool_cfg.primary_affinity_tags) != 'string' &&
        (!(pool_cfg.primary_affinity_tags instanceof Array) || pool_cfg.primary_affinity_tags.filter(t => typeof t != 'string').length > 0))
    {
        if (warn)
            console.log('Pool '+pool_id+' has invalid primary_affinity_tags (must be a string or array of strings)');
        return false;
    }
    if (!get_pg_rules(pool_id, pool_cfg, placement_levels, true))
    {
        return false;
    }
    return true;
}

function get_pg_rules(pool_id, pool_cfg, placement_levels, warn)
{
    if (pool_cfg.level_placement)
    {
        const pg_size = (0|pool_cfg.pg_size);
        let rules = pool_cfg.level_placement;
        if (typeof rules === 'string')
        {
            rules = rules.split(/\s+/).map(s => s.split(/=/, 2)).reduce((a, c) => { a[c[0]] = c[1]; return a; }, {});
        }
        else
        {
            rules = { ...rules };
        }
        // Always add failure_domain to prevent rules from being totally incorrect
        const all_diff = [];
        for (let i = 1; i <= pg_size; i++)
        {
            all_diff.push(i);
        }
        rules[pool_cfg.failure_domain || 'host'] = all_diff;
        placement_levels = placement_levels||{};
        placement_levels.host = placement_levels.host || 100;
        placement_levels.osd = placement_levels.osd || 101;
        for (const k in rules)
        {
            if (!placement_levels[k] || typeof rules[k] !== 'string' &&
                (!(rules[k] instanceof Array) ||
                rules[k].filter(s => typeof s !== 'string' && typeof s !== 'number').length > 0))
            {
                if (warn)
                    console.log('Pool '+pool_id+' configuration is invalid: level_placement should be { [level]: string | (string|number)[] }');
                return null;
            }
            else if (rules[k].length != pg_size)
            {
                if (warn)
                    console.log('Pool '+pool_id+' configuration is invalid: values in level_placement should contain exactly pg_size ('+pg_size+') items');
                return null;
            }
        }
        return parse_level_indexes(rules);
    }
    else if (typeof pool_cfg.raw_placement === 'string')
    {
        try
        {
            return parse_pg_dsl(pool_cfg.raw_placement);
        }
        catch (e)
        {
            if (warn)
                console.log('Pool '+pool_id+' configuration is invalid: invalid raw_placement: '+e.message);
        }
    }
    else
    {
        let rules = [ [] ];
        let prev = [ 1 ];
        for (let i = 1; i < pool_cfg.pg_size; i++)
        {
            rules.push([ [ pool_cfg.failure_domain||'host', '!=', prev ] ]);
            prev = [ ...prev, i+1 ];
        }
        return rules;
    }
}

module.exports = {
    validate_pool_cfg,
    get_pg_rules,
};
