/* License: Dual-license GPL 3.0+ or MPL 1.1+
 * Contributor(s): Vitaliy Filippov <vitalif@mail.ru>
 */

/*

JavaScript code to hide and show values of select fields on the query form.
I.e. hide dependent values when their controlling value is hidden.
This code uses client-side cached qfVisibility hash data generated by
fieldvaluecontrol.cgi?type=search.

qfVisibility format: {
    // for each field:
    field_name : {
        // all legal values for the field:
        legal   : [ { id : 0, name : "NAME" }, ... ],
        // visibility data for field values:
        values  : { controlled_field_name : { controlled_value_id : { visibility_value_id : 1, ... }, ... }, ... },
        // visibility data for shown/hidden fields:
        fields  : { controlled_field_name : { visibility_value_id : 1, ... }, ... },
    },
    ...
};

Important nuances of implementation:

Non-custom product-dependent Bugzilla fields (component, version, target_milestone)
values are not uniquely identified by their name. There could be many values with
same name belonging to different products.

Logically, such behavior is more convenient than one implemented in Bugzilla
custom fields, where each value has a unique name and could be "visible"
inside some, for example, products (you cannot possibly add different
descriptions for the value inside different products). So we need to support it
and remove duplicate names inside selectboxes.

But simply using names instead of IDs for filtering is insufficient: if
someday we'll have a custom field (say cf_1) depending on one of such "non-unique-value-name"
fields (say milestone), then selecting a product AND a milestone will display
the list of cf_1 values which are visible for ANY of milestones with selected name
ignoring selected product.

So we need to remember "which IDs of each name" are active now and filter
dependent values accordingly. I.e. when ONE product is selected, only ONE ID
could be present for each milestone displayed in selectbox.

We also need to preserve the sort order of dependent "non-unique-value-name" values,
respecting this "list of active IDs".

*/

var qfHandling = {};

// This is set to onload event from fieldvaluecontrol.cgi
function initQueryformFields()
{
    for (var i in qfVisibility)
    {
        if (!qfHandling[i])
        {
            var e = document.getElementById(i);
            if (e)
                handleQueryformField(null, e);
            else
                qfHandling[i] = true;
        }
        initQueryformField(i);
    }
}

function initQueryformField(i)
{
    var f = document.getElementById(i);
    if (f)
        addListener(f, 'change', handleQueryformField_this);
}

// Get selected IDs of names selected in selectbox sel
function getQueryformSelectedIds(sel)
{
    var opt = {};
    var a;
    var has_selected;
    var l2 = sel.id.length+4;
    // No selection is equivalent to full selection
    for (var i = 0; i < sel.options.length; i++)
    {
        if (sel.options[i].selected)
        {
            has_selected = true;
            break;
        }
    }
    // Iterate over all options
    for (var i = 0; i < sel.options.length; i++)
    {
        if (sel.options[i].selected || !has_selected)
        {
            // IDs of options are qf_<SELECTBOX_ID>_1_2_3_...
            // where 1_2_3 is the list of IDs mapped to this option name
            // which are currently visible.
            a = sel.options[i].id.substr(l2).split('_');
            for (var j in a)
                opt[a[j]] = true;
        }
    }
    return opt;
}

// Get the array with values of options selected in sel selectbox
function getPlainSelectedIds(sel)
{
    var o = {};
    for (var i = 0; i < sel.options.length; i++)
        if (sel.options[i].selected)
            o[sel.options[i].value] = true;
    return o;
}

// Check visibility of some field or value when it's visible only for ids
// which are keys of $visible_for_ids, and if $selected_ids are selected by now.
// Examples:
// f({ 1: 1, 3: 1 }, { 1: 1, 2: 1 }) = true
// f({},             { 1: 1, 2: 1 }) = true
// f({ 2: 1 },       { 1: 1 })       = false
function qfCheckVisibility(visible_for_ids, selected_ids)
{
    vis = true;
    // Visible also if visible_for_ids is an empty hash
    if (visible_for_ids)
    {
        for (var cid in visible_for_ids)
        {
            vis = false;
            if (selected_ids[cid])
            {
                vis = true;
                break;
            }
        }
    }
    return vis;
}

// handleQueryformField on 'this' change
function handleQueryformField_this(event)
{
    return handleQueryformField(event, this);
}

// Handle change of selection inside a selectbox 'controller'
// Update all dependent fields
function handleQueryformField(event, controller)
{
    var controlled, controlled_selected;
    var vis, item, legal, name2id, name2id_order, valueVD;
    qfHandling[controller.id] = true; // prevent double-action during init
    var VD = qfVisibility[controller.id];
    var visibility_selected = getQueryformSelectedIds(controller);
    var ids = {};
    for (var i in VD.fields)
        ids[i] = true;
    for (var i in VD.values)
        ids[i] = true;
    for (var controlled_id in ids)
    {
        controlled = document.getElementById(controlled_id);
        if (!controlled)
            continue;
        if (controlled.nodeName != 'SELECT')
        {
            // Just show/hide non-select fields
            item = document.getElementById(controlled_id+'_cont');
            if (item)
            {
                item.style.display = qfCheckVisibility(
                    VD.fields[controlled_id], visibility_selected
                ) ? '' : 'none';
            }
            continue;
        }
        // Save and clear old selection for dependent field:
        controlled_selected = getPlainSelectedIds(controlled);
        bz_clearOptions(controlled);
        // Loop over all legal values and remember currently
        // visible IDs inside name2id preserving their original
        // order using name2id_order
        legal = qfVisibility[controlled_id]['legal'];
        name2id = {};
        name2id_order = [];
        if (qfCheckVisibility(VD.fields[controlled_id], visibility_selected))
        {
            var vis_val = VD.values[controlled_id];
            // Field is visible
            for (var i in legal)
            {
                if (!vis_val || qfCheckVisibility(vis_val[legal[i][0]], visibility_selected))
                {
                    // Value is visible
                    if (!name2id[legal[i][1]])
                    {
                        name2id[legal[i][1]] = [];
                        name2id_order.push(legal[i][1]);
                    }
                    name2id[legal[i][1]].push(legal[i][0]);
                }
            }
        }
        // Create options
        for (var i in name2id_order)
        {
            i = name2id_order[i];
            item = bz_createOptionInSelect(controlled, i, i);
            /* Save particular selected IDs for the same name
               for cascade selection of such fields.
               At the moment, only component, version and target_milestone fields
               can have many values with the same name, and they do not affect the
               case of cascade selection when there are no custom fields depending
               on them. */
            item.id = 'qf_'+controlled_id+'_'+name2id[i].join('_');
            if (controlled_selected[i])
            {
                // Restore selection
                item.selected = true;
            }
        }
        handleQueryformField(event, controlled);
        item = document.getElementById(controlled_id+'_cont');
        if (item)
        {
            // Hide fields with no options
            item.style.display = controlled.options.length ? '' : 'none';
        }
    }
}
