// Adds to the target select object all elements in array that
// correspond to the elements selected in source.
//     - array should be a array of arrays, indexed by product name. the
//       array should contain the elements that correspont to that
//       product. Example:
//         var array = Array();
//         array['ProductOne'] = [ 'ComponentA', 'ComponentB' ];
//         updateSelect(array, source, target);
//     - sel is a list of selected items, either whole or a diff
//       depending on sel_is_diff.
//     - sel_is_diff determines if we are sending in just a diff or the
//       whole selection. a diff is used to optimize adding selections.
//     - target should be the target select object.
//     - single specifies if we selected a single item. if we did, no
//       need to merge.

function updateSelect( array, sel, target, sel_is_diff, single, blank ) {

    var i, j, comp;

    // if single, even if it's a diff (happens when you have nothing
    // selected and select one item alone), skip this.
    if ( ! single ) {

        // array merging/sorting in the case of multiple selections
        if ( sel_is_diff ) {

            // merge in the current options with the first selection
            comp = merge_arrays( array[sel[0]], target.options, 1 );

            // merge the rest of the selection with the results
            for ( i = 1 ; i < sel.length ; i++ ) {
                comp = merge_arrays( array[sel[i]], comp, 0 );
            }
        } else {
            // here we micro-optimize for two arrays to avoid merging with a
            // null array
            comp = merge_arrays( array[sel[0]],array[sel[1]], 0 );

            // merge the arrays. not very good for multiple selections.
            for ( i = 2; i < sel.length; i++ ) {
                comp = merge_arrays( comp, array[sel[i]], 0 );
            }
        }
    } else {
        // single item in selection, just get me the list
        comp = array[sel[0]];
    }

    // save the selection in the target select so we can restore it later
    var selections = new Array();
    for ( i = 0; i < target.options.length; i++ )
      if (target.options[i].selected) selections.push(target.options[i].value);

    // clear select
    target.options.length = 0;

    // add empty "Any" value back to the list
    if (blank) target.options[0] = new Option( blank, "" );

    // load elements of list into select
    for ( i = 0; i < comp.length; i++ ) {
        target.options[target.options.length] = new Option( comp[i], comp[i] );
    }

    // restore the selection
    for ( i=0 ; i<selections.length ; i++ )
      for ( j=0 ; j<target.options.length ; j++ )
        if (target.options[j].value == selections[i]) target.options[j].selected = true;

}

// Returns elements in a that are not in b.
// NOT A REAL DIFF: does not check the reverse.
//     - a,b: arrays of values to be compare.

function fake_diff_array( a, b ) {
    var newsel = new Array();

    // do a boring array diff to see who's new
        for ( var ia in a ) {
            var found = 0;
            for ( var ib in b ) {
                if ( a[ia] == b[ib] ) {
                    found = 1;
                }
            }
            if ( ! found ) {
                newsel[newsel.length] = a[ia];
            }
            found = 0;
        }
        return newsel;
    }

// takes two arrays and sorts them by string, returning a new, sorted
// array. the merge removes dupes, too.
//     - a, b: arrays to be merge.
//     - b_is_select: if true, then b is actually an optionitem and as
//       such we need to use item.value on it.

    function merge_arrays( a, b, b_is_select ) {
        var pos_a = 0;
        var pos_b = 0;
        var ret = new Array();
        var bitem, aitem;

    // iterate through both arrays and add the larger item to the return
    // list. remove dupes, too. Use toLowerCase to provide
    // case-insensitivity.

        while ( ( pos_a < a.length ) && ( pos_b < b.length ) ) {

            if ( b_is_select ) {
                bitem = b[pos_b].value;
            } else {
                bitem = b[pos_b];
            }
            aitem = a[pos_a];

        // smaller item in list a
            if ( aitem.toLowerCase() < bitem.toLowerCase() ) {
                ret[ret.length] = aitem;
                pos_a++;
            } else {
            // smaller item in list b
                if ( aitem.toLowerCase() > bitem.toLowerCase() ) {
                    ret[ret.length] = bitem;
                    pos_b++;
                } else {
                // list contents are equal, inc both counters.
                    ret[ret.length] = aitem;
                pos_a++;
                pos_b++;
            }
        }
        }

    // catch leftovers here. these sections are ugly code-copying.
        if ( pos_a < a.length ) {
            for ( ; pos_a < a.length ; pos_a++ ) {
                ret[ret.length] = a[pos_a];
            }
        }

        if ( pos_b < b.length ) {
            for ( ; pos_b < b.length; pos_b++ ) {
                if ( b_is_select ) {
                    bitem = b[pos_b].value;
                } else {
                    bitem = b[pos_b];
                }
                ret[ret.length] = bitem;
            }
        }
        return ret;
    }

// selectProduct reads the selection from f[productfield] and updates
// f.version, component and target_milestone accordingly.
//     - f: a form containing product, component, varsion and
//       target_milestone select boxes.
// globals (3vil!):
//     - cpts, vers, tms: array of arrays, indexed by product name. the
//       subarrays contain a list of names to be fed to the respective
//       selectboxes. For bugzilla, these are generated with perl code
//       at page start.
//     - usetms: this is a global boolean that is defined if the
//       bugzilla installation has it turned on. generated in perl too.
//     - first_load: boolean, specifying if it's the first time we load
//       the query page.
//     - last_sel: saves our last selection list so we know what has
//       changed, and optimize for additions.

function selectProduct( f , productfield, componentfield, blank ) {

    // this is to avoid handling events that occur before the form
    // itself is ready, which happens in buggy browsers.

    if ( ( !f ) || ( ! f[productfield] ) ) {
        return;
    }

    // if this is the first load and nothing is selected, no need to
    // merge and sort all components; perl gives it to us sorted.

    if ( ( first_load ) && ( f[productfield].selectedIndex == -1 ) ) {
            first_load = 0;
            return;
    }

    // turn first_load off. this is tricky, since it seems to be
    // redundant with the above clause. It's not: if when we first load
    // the page there is _one_ element selected, it won't fall into that
    // clause, and first_load will remain 1. Then, if we unselect that
    // item, selectProduct will be called but the clause will be valid
    // (since selectedIndex == -1), and we will return - incorrectly -
    // without merge/sorting.

    first_load = 0;

    // - sel keeps the array of products we are selected.
    // - is_diff says if it's a full list or just a list of products that
    //   were added to the current selection.
    // - single indicates if a single item was selected
    var sel = Array();
    var is_diff = 0;
    var single;

    // if nothing selected, pick all
    if ( f[productfield].selectedIndex == -1 ) {
        for ( var i = 0 ; i < f[productfield].length ; i++ ) {
            sel[sel.length] = f[productfield].options[i].value;
        }
        single = 0;
    } else {

        for ( i = 0 ; i < f[productfield].length ; i++ ) {
            if ( f[productfield].options[i].selected ) {
                sel[sel.length] = f[productfield].options[i].value;
            }
        }

        single = ( sel.length == 1 );

        // save last_sel before we kill it
            var tmp = last_sel;
        last_sel = sel;

        // this is an optimization: if we've added components, no need
        // to remerge them; just merge the new ones with the existing
        // options.

        if ( ( tmp ) && ( tmp.length < sel.length ) ) {
            sel = fake_diff_array(sel, tmp);
            is_diff = 1;
        }
    }

    // do the actual fill/update
    updateSelect( cpts, sel, f[componentfield], is_diff, single, blank );
}
