// ******************************************************************
/*
 * Configurator.js
 *
 * Copyright (c) 2005 Catalog Data Solutions. All Rights Reserved.
 *
 * JavaScript classes for configurator applications made up of a set
 * of fields whose states are constrained by each other.
 */
// ******************************************************************

var CONFIGURATOR_DEBUG = false;
var CONFIGURATOR_FIELD_DATA_VALUES = ["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","J","K","L","M","N","P","Q","R","S","T","U","V","W","X","Y","Z"];
var CONFIGURATOR_FIELD_DATA_INDEXES = {"0":0,"1":1,"2":2,"3":3,"4":4,"5":5,"6":6,"7":7,"8":8,"9":9,"A":10,"B":11,"C":12,"D":13,"E":14,"F":15,"G":16,"H":17,"J":18,"K":19,"L":20,"M":21,"N":22,"P":23,"Q":24,"R":25,"S":26,"T":27,"U":28,"V":29,"W":30,"X":31,"Y":32,"Z":33};

// ******************************************************************
// ConflictSet
//
// Class containing a list of the current conflicts within a
// configurator field.
// ******************************************************************

function ConflictSet(conflictedField)
{
    this.count = 0;
    this.field = conflictedField;
}

ConflictSet.prototype.toString = function()
{
    var str = "ConflictSet of size " + this.count + " on field " + this.field;
    for (prop in this) {
        if (typeof this[prop] == "function") continue;
        if (prop != "count" && prop != "field") str += ", " + prop + "=" + this[prop];
    }
    return str;
}

// Returns true if the conflict was added
ConflictSet.prototype.add = function(source, conflictId)
{
    if (!this.propertyIsEnumerable(conflictId)) {
        this[conflictId] = source;
        this.count++;
        return true;
    }
    return false;
}

ConflictSet.prototype.isEmpty = function()
{
    return (this.count == 0);
}

// Returns true if the conflict was removed
ConflictSet.prototype.remove = function(conflictId)
{
    if (this.propertyIsEnumerable(conflictId)) {
        delete this[conflictId];
        this.count--;
        return true;
    }
    return false;
}

// ******************************************************************
// Configurator
//
// The Configurator is the set of all related fields which make up a 
// single configurator application.
// ******************************************************************

function Configurator(name)
{
    this.name = name;
    this.groupArray = new Array();
    this.groupTable = new Object();
    this.eventQueue = new Array();
    this.displayQueue = new Object();
    this.styleSet = new Object();
    this.onChangeConfiguratorField = null;
    this.onMouseOverConfiguratorField = null;
    this.onMouseOutConfiguratorField = null;
    this.onResetConfiguratorField = null;
    
    this.curEventPos = 0;
    this.eventCount = 0;
    
    this.DATA_SEPERATOR = "|";
}

Configurator.prototype.toString = function()
{
    return "Configurator " + this.name + " with " + this.groupArray.length + " groups";
}

Configurator.prototype.add = function(group)
{
    group.owner = this;
    group.index = this.groupArray.length;
    this.groupArray[this.groupArray.length] = group;
    this.groupTable[group.name] = group;
    return group;
}

// groupId and fieldId can be passed as an int index or string name
// groupId can also be passed as a group object
// if no fieldId given, return the group
Configurator.prototype.get = function(groupId, fieldId)
{
    var g = null;
    if (groupId instanceof ConfiguratorGroup) g = groupId;
    else if (typeof groupId == "string") g = this.groupTable[groupId];
    else g = this.groupArray[groupId];
    if (g == null) return null;
    if (fieldId == null) return g;
    return g.get(fieldId);
}

Configurator.prototype.getFieldByName = function(fieldId)
{
    if (typeof fieldId != "string") return null;
    for (var i = 0; i < this.groupArray.length; i++) {
        var field = this.groupArray[i].get(fieldId);
        if (field != null) return field;
    }
    return null;
}

Configurator.prototype.setElements = function()
{
    for (var i = 0; i < this.groupArray.length; i++) this.groupArray[i].setElements();
    this.data = new Array(this.groupArray.length);
}

Configurator.prototype.display = function(isPreview)
{
    for (var i = 0; i < this.groupArray.length; i++) this.groupArray[i].display(isPreview);
}

Configurator.prototype.reset = function()
{
    for (var i = 0; i < this.groupArray.length; i++) this.groupArray[i].reset();
}

Configurator.prototype.getData = function()
{
    var data = new Array(this.groupArray.length);
    for (var i = 0; i < this.groupArray.length; i++) {
        data[i] = this.groupArray[i].getData();
    }
    return data.join(this.DATA_SEPERATOR);
}

Configurator.prototype.setData = function(data)
{
    var a = data.split(this.DATA_SEPERATOR);
    for (var i = 0; i < this.groupArray.length; i++) {
        var results = this.groupArray[i].setData(a[i]);
        if (results != null) return results;
    }
    return null;
}

// Adds an event to the eventQueue for processing
Configurator.prototype.addEvent = function(source, action, listeners)
{
    if (this.eventQueue[this.eventCount] == null) {
        this.eventQueue[this.eventCount] = new ConfiguratorEvent(source, action, listeners);
    } else {
        this.eventQueue[this.eventCount].source = source;
        this.eventQueue[this.eventCount].action = action;
        this.eventQueue[this.eventCount].listeners = listeners;
    }
    this.eventCount++;
}

// Processes the currently queued events
var DEBUG_TOTAL_PROCESS_CALLS = 0;
Configurator.prototype.processEvents = function(isPreview, source)
{
    if (CONFIGURATOR_DEBUG) {
        DEBUG_TOTAL_PROCESS_CALLS++;
        var DEBUG_START_TIME = new Date();    
        var DEBUG_EVENT_COUNT = 0;
        var DEBUG_IND_EVENT_COUNT = 0;
        var DEBUG_DISPLAY_COUNT = 0;
    }
    
    // If any groups have their preProcessEvent defined, call them
    for (var i = 0; i < this.groupArray.length; i++) {
        if (this.groupArray[i].preProcessEvent != null) {
            this.groupArray[i].preProcessEvent(source, isPreview);
        }
    }

    if (this.eventCount > 0) {
        for (var i = 0; i < this.eventCount; i++) {
            var cur = this.eventQueue[i];
            for (var j = 0; j < cur.listeners.length; j++) {
                if (cur.listeners[j].processEvent(cur.source, cur.action)) {
                    this.displayQueue[cur.listeners[j].listeningField.name] = cur.listeners[j].listeningField;
                }
                if (CONFIGURATOR_DEBUG) DEBUG_EVENT_COUNT++;
            }
        }
    }
    this.eventCount = 0;    
    
    // If any groups have their postProcessEvent defined, call them
    for (var i = 0; i < this.groupArray.length; i++) {
        if (this.groupArray[i].postProcessEvent != null) {
            this.groupArray[i].postProcessEvent(source, isPreview);
        }
    }

    // Call display on any altered fields
    for (var field in this.displayQueue) {
        if (this.displayQueue[field] != null) {
            this.displayQueue[field].display(isPreview);
            this.displayQueue[field] = null;
            if (CONFIGURATOR_DEBUG) DEBUG_DISPLAY_COUNT++;
        }
    }
    
    if (CONFIGURATOR_DEBUG) {
        var DEBUG_END_TIME = new Date();
        this.displayDebugInformation(DEBUG_TOTAL_PROCESS_CALLS, DEBUG_EVENT_COUNT, DEBUG_IND_EVENT_COUNT, DEBUG_DISPLAY_COUNT, (DEBUG_END_TIME.getTime() - DEBUG_START_TIME.getTime()));
    }
}

// style must be a table of styleId, cssClassName pairs
Configurator.prototype.addStyle = function(name, style)
{
    this.styleSet[name] = style;
}

Configurator.prototype.getStyle = function(name, style)
{
    return this.styleSet[name][style];
}

// Shows timing information for event processing
Configurator.prototype.displayDebugInformation = function(totalCalls, totalEvents, indEvents, displays, time)
{
    document.getElementById("eventDebugTextProcessCalls").innerHTML = totalCalls;
    document.getElementById("eventDebugTextTotalEvents").innerHTML = totalEvents;
    document.getElementById("eventDebugTextIndEvents").innerHTML = indEvents;
    document.getElementById("eventDebugTextDisplays").innerHTML = displays;
    document.getElementById("eventDebugTextTime").innerHTML = time;
}

// Shows timing information for event processing
Configurator.prototype.displayDebugMessage = function(text)
{
    document.getElementById("debugMessageText").innerHTML = text;
}

// ******************************************************************
// ConfiguratorGroup
//
// A group of related fields which manages the data and events for
// the fields it contains.
// ******************************************************************

function ConfiguratorGroup(name)
{
    this.name = name;
    this.fieldArray = new Array();
    this.fieldTable = new Object();
    this.data = null;
    this.preProcessEvent = null;
    this.postProcessEvent = null;
}

ConfiguratorGroup.prototype.toString = function()
{
    return "ConfiguratorGroup " + this.name + " with " + this.fieldArray.length + " fields, data=" + this.data;
}

ConfiguratorGroup.prototype.add = function(field)
{
    field.owner = this;
    field.index = this.fieldArray.length;
    this.fieldArray[this.fieldArray.length] = field;
    this.fieldTable[field.name] = field;
    this.postAddInit(field);
    return field;
}

// Id can be passed as an int index or string name
ConfiguratorGroup.prototype.get = function(id)
{
    return (typeof id == "string") ? this.fieldTable[id] : this.fieldArray[id];
}

ConfiguratorGroup.prototype.setElements = function()
{
    for (var i = 0; i < this.fieldArray.length; i++) {
        this.fieldArray[i].setElements();
        this.postSetElementsInit(this.fieldArray[i]);
    }
}

ConfiguratorGroup.prototype.display = function(isPreview)
{
    for (var i = 0; i < this.fieldArray.length; i++) {
        this.fieldArray[i].display(isPreview);
    }
}

ConfiguratorGroup.prototype.postAddInit = function(field) {}
ConfiguratorGroup.prototype.postSetElementsInit = function(field) {}
ConfiguratorGroup.prototype.receiveInternalEvent = function(source, type) {}
ConfiguratorGroup.prototype.reset = function() {}
ConfiguratorGroup.prototype.getData = function() { return data; }
ConfiguratorGroup.prototype.setData = function(data) { this.data = data; return null; }

// ******************************************************************
// ConfiguratorField
//
// A single data field within the configurator application.
// ******************************************************************

function ConfiguratorField(name, elementId) 
{
    this.name = name;
    this.elementId = elementId;
    this.conflicts = new ConflictSet(this);
    this.data = null;
}

ConfiguratorField.prototype.toString = function()
{
    return "ConfiguratorField " + this.name + " for " + this.elementId;
}

ConfiguratorField.prototype.setElements = function()
{
    this.element = document.getElementById(this.elementId);
    if (this.element == null) return;
    this.element.controller = this;
    this.initElements();
}

ConfiguratorField.prototype.initElements = function() {}

ConfiguratorField.prototype.display = function(isPreview) {}

ConfiguratorField.prototype.sendEvent = function(type)
{
    this.owner.receiveInternalEvent(this, type);
}

ConfiguratorField.prototype.hasConflict = function()
{
    return !this.conflicts.isEmpty();
}

ConfiguratorField.prototype.addConflict = function(source, conflictId)
{
    return this.conflicts.add(source, conflictId);
}

ConfiguratorField.prototype.removeConflict = function(conflictId)
{
    return this.conflicts.remove(conflictId);
}

// ******************************************************************
// ConfiguratorEvent
//
// Object that holds a configurator application event source, action
// and all listeners registered to that event.
// ******************************************************************

function ConfiguratorEvent(source, action, listeners) 
{
    this.source = source;
    this.action = action;
    this.listeners = listeners;
}

ConfiguratorEvent.prototype.toString = function()
{
    return "ConfiguratorEvent source=" + this.source + ", action=" + this.action + ", listener count=" + this.listeners.length;
}

// ******************************************************************
// ConfiguratorListener
//
// Base class for objects which listen to events on a field within a
// configurator application and enforce the relationships with that
// field.
// ******************************************************************

function ConfiguratorListener(listeningField) 
{
    this.listeningField = listeningField;
    this.name = "ConfiguratorListener";
}

// Should return true if the event requires the listening field to be displayed
ConfiguratorListener.prototype.processEvent = function(source, action) 
{
    return false;
}

ConfiguratorListener.prototype.toString = function()
{
    return this.name + " for field " + this.listeningField;
}

// ******************************************************************
// ToggleFieldGroup
//
// A group of toggle fields only one of which can be selected at the
// same time.
// It's data representation is 0-9, A-Y for currently selected field
// index or Z for nothing selected.
// ******************************************************************

ToggleFieldGroup.prototype = new ConfiguratorGroup();
ToggleFieldGroup.prototype.constructor = ConfiguratorGroup;

function ToggleFieldGroup(name, label)
{
    this.name = name;
    this.label = label;
    this.fieldArray = new Array();
    this.fieldTable = new Object();
    this.selectedField = null;
    this.hoveredField = null;
    this.data = null;
}

ToggleFieldGroup.prototype.receiveInternalEvent = function(source, type) 
{
    if (type == "click") {
        // Click on currently selected field to deselect it
        if (this.selectedField == source) {
            this.selectedField = null;
            source.data = false;
            source.display(false);
            this.owner.addEvent(source, "deselected", source.listeners);
            this.owner.processEvents(false, source);
        } else {
            // Remove any previous selection
            if (this.selectedField != null) {
                this.selectedField.data = false;
                this.selectedField.display(false);
                this.owner.addEvent(this.selectedField, "deselected", this.selectedField.listeners);
            }
            // Click on new field to change selection
            source.data = true;
            source.display(false);
            this.selectedField = source;
            this.owner.addEvent(source, "selected", source.listeners);
            this.owner.processEvents(false, source);
        }
    } else if (type == "mouseOver") {
        source.hovered = true;
        source.display(true);
        this.hoveredField = source;
        this.owner.addEvent(source, "mouseover", source.listeners);
        this.owner.processEvents(true, source);
    } else if (type == "mouseOut") {
        source.hovered = false;
        source.display(true);
        this.hoveredField = null;
        this.owner.addEvent(source, "mouseout", source.listeners);
        this.owner.processEvents(false, source);
    }
}

ToggleFieldGroup.prototype.reset = function() 
{
    if (this.selectedField != null) {
        this.selectedField.data = false;
        this.selectedField.display(false);
        this.owner.addEvent(this.selectedField, "deselected", this.selectedField.listeners);
        this.owner.processEvents(false, this.selectedField);

        // Call any methods defined in the HTML file to execute on changes 
        // Save our selected field so we can use it below, but make selectedField null
        // ... so that hasSelected returns correct when checking view buttons
        var saveSelectedField = this.selectedField;
        this.selectedField = null;
        if (this.owner.onResetConfiguratorField != null) {
            this.owner.onResetConfiguratorField(saveSelectedField);
        }
    }
}

// Data is a single character from CONFIGURATOR_FIELD_DATA_VALUES[i] 
// where i is the selected field's index or Z if none selected
ToggleFieldGroup.prototype.getData = function() 
{ 
    return (this.selectedField != null) ? CONFIGURATOR_FIELD_DATA_VALUES[this.selectedField.index] : "Z";
}

// method updated , 8 March 2006 by CCM 
ToggleFieldGroup.prototype.setData = function(data)
{
    if (data == "Z") return this.reset();
   
    var index = CONFIGURATOR_FIELD_DATA_INDEXES[data];
   
    if (this.selectedField != null) {
        this.selectedField.data = false;
        this.selectedField.display(false);
        this.owner.addEvent(this.selectedField, "deselected", this.selectedField.listeners);
    }
    
    if (index >= this.fieldArray.length) {
        return "Attempt to set data to a nonexistant value: " + this.name + ", " + index;
    } 
    if (this.fieldArray[index] != null && this.fieldArray[index].hasConflict()) {
        return "Attempt to set data on a conflicted field: " + this.fieldArray[index].name;
    }

    if (this.fieldArray[index] != null ) {
        this.selectedField = this.fieldArray[index];
        this.selectedField.data = true;
        this.selectedField.display(false);
        this.owner.addEvent(this.selectedField, "selected", this.selectedField.listeners);
        this.owner.processEvents(false, this.selectedField);
        // Call any methods defined in the HTML file to execute on changes   
        if (this.owner.onChangeConfiguratorField != null) {
            this.owner.onChangeConfiguratorField(this.selectedField);
        }
    }
    
    return null;
}

ToggleFieldGroup.prototype.hasSelection = function() { return (this.selectedField != null); }

// ******************************************************************
// ToggleFieldMultiSelectGroup
//
// A group of toggle fields which can have more than one selected at
// the same time.
// ******************************************************************

ToggleFieldMultiSelectGroup.prototype = new ConfiguratorGroup();
ToggleFieldMultiSelectGroup.prototype.constructor = ConfiguratorGroup;

function ToggleFieldMultiSelectGroup(name, label)
{
    this.name = name;
    this.label = label;
    this.fieldArray = new Array();
    this.fieldTable = new Object();
    this.data = null;
}

ToggleFieldMultiSelectGroup.prototype.receiveInternalEvent = function(source, type) 
{
    if (type == "click") {
        // Click on currently selected field to deselect it
        source.data = !source.data;
        source.display(false);
        this.owner.addEvent(source, (source.data) ? "selected" : "deselected", source.listeners);
        this.owner.processEvents(false, source);
    } else if (type == "mouseOver") {
        source.hovered = true;
        source.display(true);
        this.owner.addEvent(source, "mouseover", source.listeners);
        this.owner.processEvents(true, source);
    } else if (type == "mouseOut") {
        source.hovered = false;
        source.display(true);
        this.owner.addEvent(source, "mouseout", source.listeners);
        this.owner.processEvents(false, source);
    }
}

ToggleFieldMultiSelectGroup.prototype.reset = function() 
{
    var found = false;
    for (var i = 0; i < this.fieldArray.length; i++) {
        if (this.fieldArray[i].data) {
            this.fieldArray[i].data = false;
            this.fieldArray[i].display(false);
            this.owner.addEvent(this.fieldArray[i], "deselected", this.fieldArray[i].listeners);
            this.owner.processEvents(false, this.fieldArray[i]);
            found = true;
        }
        
        // Call any methods defined in the HTML file to execute on changes    
        if (this.owner.onResetConfiguratorField != null) {
            this.owner.onResetConfiguratorField(this.selectedField);
        }
    }
}

// Returns bitmap of 1 for selected or 0 for deselected for each field
ToggleFieldMultiSelectGroup.prototype.getData = function() 
{
    var s = "";
    for (var i = 0; i < this.fieldArray.length; i++) s += ((this.fieldArray[i].data) ? "1" : "0");
    return s;
}

ToggleFieldMultiSelectGroup.prototype.setData = function(data) 
{
    if (data == null) {
        this.reset();
        return null;
    }
    
    var found = false;
    var changedFields = new Array();
    for (var i = 0; i < this.fieldArray.length; i++) {
        if (this.fieldArray[i].data && data.charAt(i) == "0") {
            this.fieldArray[i].data = false;
            this.fieldArray[i].display(false);
            this.owner.addEvent(this.fieldArray[i], "deselected", this.fieldArray[i].listeners);
            found = true;
            changedFields[changedFields.length] = this.fieldArray[i];
        } else if (!this.fieldArray[i].data && data.charAt(i) == "1") {
            if (this.fieldArray[i].hasConflict()) return "Attempt to set data on a conflicted field: " + this.fieldArray[i].name;
            this.fieldArray[i].data = true;
            this.fieldArray[i].display(false);
            this.owner.addEvent(this.fieldArray[i], "selected", this.fieldArray[i].listeners);
            found = true;
            changedFields[changedFields.length] = this.fieldArray[i];
        }
    }
    
    if (found) {
        this.owner.processEvents(false, this);
        // Call any methods defined in the HTML file to execute on changes    
        if (this.owner.onChangeConfiguratorField != null) {
            for (var i = 0; i < changedFields.length; i++) {
                this.owner.onChangeConfiguratorField(changedFields[i]);
            }
        }
    }
    return null;
}

// ******************************************************************
// ToggleField
//
// A configurator field which can only have a value of on or off.
// ******************************************************************

ToggleField.prototype = new ConfiguratorField();
ToggleField.prototype.constructor = ConfiguratorField;

function ToggleField(name, elementId, style, label)
{
    this.name = name;
    this.label = label;
    this.elementId = elementId;
    this.conflicts = new ConflictSet(this);
    this.data = false;
    this.style = style;
    this.hovered = false;
    this.listeners = new Array();
}

ToggleField.prototype.initElements = function()
{
    this.element.onclick = this.onClick;
    this.element.onmouseover = this.onMouseOver;
    this.element.onmouseout = this.onMouseOut;
}

ToggleField.prototype.display = function(isPreview)
{
    if (this.element != null) {
        var newStyle = null;
        if (this.hasConflict()) newStyle = (isPreview) ? "conflicted" : "disabled";
        else if (this.data) newStyle = "selected";
        else if (isPreview && this.hovered) newStyle = "mouseOver";
        else newStyle = "default";
        var newClass = this.owner.owner.getStyle(this.style, newStyle);
        if (newClass != null) this.element.className = newClass;
    }
}

ToggleField.prototype.addConflict = function(source, conflictId)
{
    this.data = false;
    return this.conflicts.add(source, conflictId);
}

ToggleField.prototype.onClick = function()
{
    if (!this.controller.hasConflict()) this.controller.sendEvent("click");
    // Call any methods defined in the HTML file to execute on changes    
    if (this.controller.owner.owner.onChangeConfiguratorField != null) {
        this.controller.owner.owner.onChangeConfiguratorField(this.controller);
    }
}

ToggleField.prototype.onMouseOver = function()
{
    if (!this.controller.hasConflict()) this.controller.sendEvent("mouseOver");
    // Call any methods defined in the HTML file to execute on changes    
    if (this.controller.owner.owner.onMouseOverConfiguratorField != null) {
        this.controller.owner.owner.onMouseOverConfiguratorField(this.controller);
    }
}

ToggleField.prototype.onMouseOut = function()
{
    if (!this.controller.hasConflict()) this.controller.sendEvent("mouseOut");
    // Call any methods defined in the HTML file to execute on changes    
    if (this.controller.owner.owner.onMouseOutConfiguratorField != null) {
        this.controller.owner.owner.onMouseOutConfiguratorField(this.controller);
    }
}

// ******************************************************************
// ConflictFieldGroup
//
// A group of conflict fields. In data, conflicted = 0, not 
// conflicted = 1. The valid states table is a list of all possible 
// state combinations with values of the array of listeners to that 
// state position.
// ******************************************************************

ConflictFieldGroup.prototype = new ConfiguratorGroup();
ConflictFieldGroup.prototype.constructor = ConfiguratorGroup;

function ConflictFieldGroup(name, validStatesTable, label)
{
    this.name = name;
    this.label = label;
    this.statesTable = validStatesTable;
    this.changeListeners = new Array();
    this.fieldArray = new Array();
    this.fieldTable = new Object();
    this.selectedField = null;
    this.data = "";
}

// Do not need to propogate events because the only way they can be added is in Configurator.processEvents()
// We are not sending immediate events since only the start and end status matter. The events are now handled
// within the configurator.processEvents
ConflictFieldGroup.prototype.updateFieldState = function(field) 
{
    this.updateData(field, (field.hasConflict()) ? "0" : "1");
    return;
    /*
    var oldData = this.data;
    this.updateData(field, (field.hasConflict()) ? "0" : "1");
    if (oldData == this.data) return;
    if (this.statesTable[oldData].length > 0) {
        this.owner.addEvent(this, "deselected", this.statesTable[oldData]);
    }
    if (this.statesTable[this.data].length > 0) {
        this.owner.addEvent(this, "selected", this.statesTable[this.data]);
    }
    if (this.changeListeners.length > 0) {
        this.owner.addEvent(this, "changed", this.changeListeners);
    }
    */
}

// Currently only works for groups of 10 or less members (0 - 9)
ConflictFieldGroup.prototype.getData = function() 
{ 
    for (var i = 0; i < this.data.length; i++) {
        if (this.data.charAt(i) == "1") return i;
    }
    return "Z";
}

ConflictFieldGroup.prototype.postAddInit = function(field) { this.data += "1"; }

ConflictFieldGroup.prototype.updateData = function(field, data)
{
    this.data = this.data.substring(0, field.index) + data + this.data.substring(field.index + 1);
}

// ******************************************************************
// ConflictField
//
// A configurator field which can only have a value of conflicted (0)
// or not conflicted (1).
// ******************************************************************

ConflictField.prototype = new ConfiguratorField();
ConflictField.prototype.constructor = ConfiguratorField;

function ConflictField(name, elementId, style, label)
{
    this.name = name;
    this.label = label;
    this.elementId = elementId;
    this.conflicts = new ConflictSet(this);
    this.data = null;
    this.style = style;
}

ConflictField.prototype.display = function(isPreview)
{
    if (this.element != null) {
        var newStyle = null;
        if (this.hasConflict()) newStyle = (isPreview) ? "conflicted" : "disabled";
        else newStyle = "default";
        var newClass = this.owner.owner.getStyle(this.style, newStyle);
        if (newClass != null) this.element.className = newClass;
    }
}

ConflictField.prototype.addConflict = function(source, conflictId)
{
    var result = this.conflicts.add(source, conflictId);
    if (result) this.owner.updateFieldState(this);
    return result;
}

ConflictField.prototype.removeConflict = function(conflictId)
{
    var result = this.conflicts.remove(conflictId);
    if (result) this.owner.updateFieldState(this);
    return result;
}

// ******************************************************************
// ToggleFieldListener
//
// A listener for changes in state within a toggle field.
// Actions: selected, deselected, mouseover, mouseout
// ******************************************************************

ToggleFieldListener.prototype = new ConfiguratorListener();
ToggleFieldListener.prototype.constructor = ConfiguratorListener;

function ToggleFieldListener(listeningField)
{
    this.listeningField = listeningField;
    this.name = "ToggleFieldListener";
}

ToggleFieldListener.prototype.processEvent = function(eventSource, action)
{
    var conflictId = this.name + ":" + eventSource.name;
    
    if (action == "selected") {
        return this.listeningField.addConflict(eventSource, conflictId);
    } else if (action == "deselected") {
        return this.listeningField.removeConflict(conflictId);
    } else if (action == "mouseover") {
        return this.listeningField.addConflict(eventSource, conflictId + ":preview");
    } else if (action == "mouseout") {
        return this.listeningField.removeConflict(conflictId + ":preview");
    }
}

// Both fields will add listeners to the other
ToggleFieldListener.add = function(field1, field2, addField1Only)
{
    if (!(field1 instanceof ToggleField)) {
        alert("Error: Attempt to add a ToggleFieldListener on a non-ToggleField: " + field1 + ", " + field2);
        return;
    }
    
    field1.listeners[field1.listeners.length] = new ToggleFieldListener(field2);
    if ((addField1Only == null || !addField1Only) && field2 instanceof ToggleField) {
        field2.listeners[field2.listeners.length] = new ToggleFieldListener(field1);
    }
}

// ******************************************************************
// ToggleFieldExclusiveListener
//
// A listener for changes in state within a group of toggle fields
// where not all may be selected at once.
// 
// Actions: selected, deselected, mouseover, mouseout
// ******************************************************************

ToggleFieldExclusiveListener.prototype = new ConfiguratorListener();
ToggleFieldExclusiveListener.prototype.constructor = ConfiguratorListener;

function ToggleFieldExclusiveListener(listeningField, conflictingFields)
{
    this.listeningField = listeningField;
    this.conflictingFields = conflictingFields;
    this.name = "ToggleFieldExclusiveListener";
    for (var i = 0; i < conflictingFields.length; i++) {
if (this.name == null || conflictingFields[i].name == null) alert('hello');        
        this.name += ":" + conflictingFields[i].name;
    }
}

ToggleFieldExclusiveListener.prototype.processEvent = function(eventSource, action)
{
    var conflictId = this.name;

    if (action == "deselected") {
        return this.listeningField.removeConflict(conflictId);
    } else if (action == "mouseout") {
        return this.listeningField.removeConflict(conflictId + ":preview");
    } else {
        var conflicted = true;
        for (var i = 0; i < this.conflictingFields.length; i++) {
            if (!this.conflictingFields[i].data && !this.conflictingFields[i].hovered) {
                conflicted = false;
                break;
            }
        }
        if (action == "selected") {
            if (conflicted) return this.listeningField.addConflict(eventSource, conflictId);
            else return this.listeningField.removeConflict(conflictId);
        } else if (action == "mouseover") {
            if (conflicted) return this.listeningField.addConflict(eventSource, conflictId + ":preview");
            else return this.listeningField.removeConflict(conflictId + ":preview");
        }
    }
}

ToggleFieldExclusiveListener.add = function(fieldList)
{
    for (var i = 0; i < fieldList.length; i++) {
        for (var j = 0; j < fieldList.length; j++) {
            if (i != j) {
                var a = new Array();
                for (var k = 0; k < fieldList.length; k++) {
                    if (j != k) a[a.length] = fieldList[k];
                }
                if (!(fieldList[i] instanceof ToggleField)) {
                    alert("Error: Attempt to add a ToggleFieldExclusiveListener on a non-ToggleField: " + fieldList[i]);
                    return;
                }
                fieldList[i].listeners[fieldList[i].listeners.length] = new ToggleFieldExclusiveListener(fieldList[j], a);
            }
        }
    }
}

// ******************************************************************
// ToggleFieldRequiredListener
//
// A listener for changes in state within a toggle field.  Field 1
// is always disabled unless Field 2 is selected
// Actions: selected, deselected, mouseover, mouseout
// ******************************************************************

ToggleFieldRequiredListener.prototype = new ConfiguratorListener();
ToggleFieldRequiredListener.prototype.constructor = ConfiguratorListener;

function ToggleFieldRequiredListener(listeningField)
{
    this.listeningField = listeningField;
    this.name = "ToggleFieldRequiredListener";
}

ToggleFieldRequiredListener.prototype.processEvent = function(eventSource, action)
{
    var conflictId = this.name + ":" + eventSource.name;
    
    if (action == "selected") {
        return this.listeningField.removeConflict(conflictId);
    } else if (action == "deselected") {
        return this.listeningField.addConflict(eventSource, conflictId);
    } else if (action == "mouseover") {
        return this.listeningField.removeConflict(conflictId);
    } else if (action == "mouseout") {
        if (!eventSource.data) return this.listeningField.addConflict(eventSource, conflictId);
    }
    return false;
}

// Both fields will add listeners to the other
ToggleFieldRequiredListener.add = function(field1, field2)
{
    var conflictId = "ToggleFieldRequiredListener:" + field2.name;
    field2.listeners[field2.listeners.length] = new ToggleFieldRequiredListener(field1);
    field1.addConflict(field2, conflictId);
}

// ******************************************************************
// ConflictFieldListener
//
// A listener for changes in state in a conflict field group
// Actions: selected, deselected
// ******************************************************************

ConflictFieldListener.prototype = new ConfiguratorListener();
ConflictFieldListener.prototype.constructor = ConfiguratorListener;

function ConflictFieldListener(listeningField)
{
    this.listeningField = listeningField;
    this.name = "ConflictFieldListener";
}

ConflictFieldListener.prototype.processEvent = function(eventSource, action, isPreview)
{
    var conflictId = this.name + ":" + eventSource.name;

    if (action == "selected") {
        return this.listeningField.addConflict(eventSource, conflictId);
    } else if (action == "deselected") {
        return this.listeningField.removeConflict(conflictId);
    }
}

// State is the binary string of a state in the conflict field group.
// Fields is an array of fields conflicted with this state
ConflictFieldListener.add = function(group, state, fields)
{
    if (!(group instanceof ConflictFieldGroup)) {
        alert("Error: Attempt to add a ConflictFieldListener on wrong group");
        return;
    }

    for (var i = 0; i < fields.length; i++) {
        if (!(fields[i] instanceof ToggleField)) {
            alert("Error: Attempt to add a ConflictFieldListener on a non-ToggleField");
            return;
        }
        group.statesTable[state][group.statesTable[state].length] = new ConflictFieldListener(fields[i]);
    }
}






