
/**
 * Defines an object with multiple fields, for use when inputting properties for multiple
 * equal types, for example multi-sort fields or scope search terms. One instance of
 * MultiObject encapsulates one 'row' in the form, and preserves the state of the row
 * across removing or adding a row.
 *
 * @param  fieldsId        the id of the element containing the base row (used as template
 *                         for additional rows)
 * @param  extraFieldsId   the id of the element where additional rows are inserted
 * @param  multiFields     an array of all form elements in a row ({@see MultiField}s)
 * @param  substitutionPrefixes  an array of additional prefixes for which to replace the
 *                         integer counter
 * @param  insertBefore    a snippet of html inserted before the template in all the
 *                         additional rows
 * @param  insertAfter     a snippet of html inserted after the template in all the
 *                         additional rows
 */
function MultiObject(fieldsId, extraFieldsId, multiFields, substitutionPrefixes, insertBefore, insertAfter) {
    this.fieldsId = fieldsId;
    this.extraFieldsId = extraFieldsId;
    this.multiFields = multiFields;
    this.substitutionPrefixes = substitutionPrefixes;
    this.insertBefore = insertBefore;
    this.insertAfter = insertAfter;
    this.extraFields = 0;
}

/**
 * Defines an input field in a row. Contains the prefix of the field's id and its type.
 *
 * @param  prefix           the prefix of the field's id, appended with [0, n-1] to obtain
 *                          the actual fields
 * @param  type             the type of the field (text, hidden, select, checkbox)
 * @param  defaultValue     the default value of the field
 * @param  defaultDisabled  whether the field is disabled by default
 * @param  deafultDisplay   the default value of the field's style.display property (e.g.
 *                          inline, block, or none)
 */
function MultiField(prefix, type, defaultValue, defaultDisabled, defaultDisplay) {
    this.prefix = prefix;
    this.type = type;
    this.defaultValue = defaultValue;
    this.defaultDisabled = defaultDisabled;
    this.defaultDisplay = defaultDisplay;
}

/**
 * Add a new row to the given multi-object.
 *
 * @param  multiObject  the object to add a new row to
 */
function addRow(multiObject) {
    __changeNumRows(multiObject, 1, -1);
}

/**
 * Remove the spcified row from the given multi-object.
 *
 * @param  multiObject  the object to remove a row from
 * @param  removeRowId  the id of the row to remove
 */
function removeRow(multiObject, removeRowId) {
    __changeNumRows(multiObject, -1, removeRowId);
}



/*
 * Private implementation methods...
 */



/**
 * Change the number of rows displayed for a multi-object.
 *
 * @param  multiObject  the object to change
 * @param  deltaRows    the change in number of rows, > 0 to add rows
 * @param  removeRowId  the id of a row to remove, -1 to not remove any
 */
function __changeNumRows(multiObject, deltaRows, removeRowId) {
    var attributes = __getFieldAttributes(multiObject, deltaRows, removeRowId);
    var fieldsElement = byId(multiObject.fieldsId);
    var additionalFieldsElement = byId(multiObject.extraFieldsId);
    var i, j;
    if (fieldsElement && additionalFieldsElement) {
        var newExtraFields = deltaRows >= 0 ? additionalFieldsElement.innerHTML : "";
        for (i = deltaRows >= 0 ? multiObject.extraFields + 1 : 1; i <= multiObject.extraFields + deltaRows; ++i) {
            var template = multiObject.insertBefore;
            template += fieldsElement.innerHTML;
            template += multiObject.insertAfter;
            for (j = 0; j < multiObject.multiFields.length; ++j) {
                template = template.replace(new RegExp(multiObject.multiFields[j].prefix + "0", "gi"),
                                            multiObject.multiFields[j].prefix + i);
            }
            for (j = 0; j < multiObject.substitutionPrefixes.length; ++j) {
                template = template.replace(new RegExp(multiObject.substitutionPrefixes[j] + "0", "gi"),
                                            multiObject.substitutionPrefixes[++j] + i);
            }
            newExtraFields += template;
        }
        additionalFieldsElement.innerHTML = newExtraFields;
        multiObject.extraFields += deltaRows;
    }
    __setFieldAttributes(multiObject, attributes);
}

/**
 * Get a multi-dimensional array containing the current values of all the inputs in the
 * multi-input grid. The array has the following dimensions: attributes[x][y][z],
 * x = field/column, y = object/row, z = (disabled, display, value).
 *
 * @param  multiObject  the definition of the multi-input to traverse
 * @param  deltaRows    the number of new rows (negative if less rows)
 * @param  removeRowId  the id of any row to remove (y)
 */
function __getFieldAttributes(multiObject, deltaRows, removeRowId) {
    var attributes = new Array();
    var i, j;
    for (j = 0; j < multiObject.multiFields.length; ++j) {
        attributes[j] = new Array();
    }
    var adjustment = 0;
    for (i = 0; i <= multiObject.extraFields; ++i) {
        if (i == removeRowId) {
            ++adjustment;
            continue;
        }
        for (j = 0; j < multiObject.multiFields.length; ++j) {
            var config = multiObject.multiFields[j];
            var element = byId(config.prefix + i);
            if (!element) continue;
            attributes[j][i - adjustment] = new Array();
            attributes[j][i - adjustment][0] = element.disabled;
            attributes[j][i - adjustment][1] = element.style.display;
            if (config.type == "select") {
                attributes[j][i - adjustment][2] = element.selectedIndex;
                element.selectedIndex = 0;
            } else if (config.type == "checkbox" || config.type == "radio") {
                attributes[j][i - adjustment][2] = element.checked;
                element.checked = false;
            } else {
                attributes[j][i - adjustment][2] = element.value;
                element.value = "";
            }
        }
    }
    for (i = 1; i <= deltaRows; ++i) {
        for (j = 0; j < multiObject.multiFields.length; ++j) {
            attributes[j][multiObject.extraFields + i] = new Array();
            attributes[j][multiObject.extraFields + i][0] = multiObject.multiFields[j].defaultDisabled;
            attributes[j][multiObject.extraFields + i][1] = multiObject.multiFields[j].defaultDisplay;
            attributes[j][multiObject.extraFields + i][2] = multiObject.multiFields[j].defaultValue;
        }
    }
    return attributes;
}

/**
 * Set the attributes of all the inputs belonging to the multi-input grid. The array to
 * set should be on the format returned by {@see #__getFieldAttributes()}.
 *
 * @param  multiObject  the object whose properties to set
 * @param  attributes   a multi-dimensional array of properties
 */
function __setFieldAttributes(multiObject, attributes) {
    var i, j;
    for (i = 0; i < attributes[0].length; ++i) {
        var elements = new Array(multiObject.multiFields.length);
        for (j = 0; j < multiObject.multiFields.length; ++j) {
            elements[j] = byId(multiObject.multiFields[j].prefix + i);
        }
        for (j = 0; j < elements.length; ++j) {
            var element = elements[j];
            if (!element) continue;
            var config = multiObject.multiFields[j];
            var values = attributes[j][i];
            var disabled = config.defaultDisabled;
            var display = config.defaultDisplay;
            var value = config.defaultValue;
            if (values) {
                disabled = values[0];
                display = values[1];
                value = values[2];
            }
            element.disabled = disabled;
            element.style.display = display;
            if (config.type == "select") {
                element.selectedIndex = value;
            } else if (config.type == "checkbox" || config.type == "radio") {
                element.checked = value;
            } else {
                element.value = value;
            }
        }
    }
}
