// ******************************************************************
// Rule.js
//
// Version 3.0
// Copyright (c) 2007 Catalog Data Solutions. All Rights Reserved.
//
// Classes implementing a rules based relationships between 
// attributes within a configurator
// ******************************************************************

cds.namespace("configurator");

// NOTE: We probably should figure out a better method for tracking id and pid 
var CONFIGURATOR_RULE_CURRENT_ID = 3;

cds.configurator.Rule = function (attributeManager) {
	this.attributeManager = attributeManager;
	this.id = CONFIGURATOR_RULE_CURRENT_ID++;
	this.pid = "+" + this.id;
};

// Creates a new rule defined by the given expression
// NOTE: The new rule must be added to the rules array of all refernced objects
cds.configurator.Rule.add = function (attributeManager, expression) {
	return null;
};

// Enforces the rule and constrains any necessary objects as a result
// 		object	the object upon which the initial event was triggered
//		result	the return value of the objects handleEvent call
// 		type	the type of the event (change, click, mouseover, mouseout)
// Returns: array of objects which were changed (to call object.display())
cds.configurator.Rule.prototype.enforce = function (object, result, type) {
	return null;
};

cds.configurator.Rule.prototype.destroy = function () {
	this.attributeManager = null;
};

cds.configurator.Rule.prototype.toString = function () {
	return "cds.configurator.Rule(" + this.id + ")";
};


// =========================================================
// Default rule
// =========================================================

cds.configurator.DefaultRule = function (attributeManager) {
	cds.configurator.DefaultRule.superclass.constructor.call(this, attributeManager);
	this.components = null;
	this.constrainedComponent = null;
	this.hoverConstrainedComponent = null;
};
cds.configurator.DefaultRule.prototype = new cds.configurator.Rule();
cds.configurator.DefaultRule.prototype.constructor = cds.configurator.DefaultRule;
cds.configurator.DefaultRule.superclass = cds.configurator.Rule.prototype;

// -------------------------------------
// Default rule add
// -------------------------------------
cds.configurator.DefaultRule.add = function (attributeManager, expression) {
	var att, c, expa, i, pos, rule, s, type, val;
	
	rule = new cds.configurator.DefaultRule(attributeManager);
	rule.components = [];

	expa = expression.split(";");
	for (i = 0; i < expa.length; i++) {
		type = null;
		att = null;
		val = null;
		
		// figure out the type
		if (expa[i].indexOf("<") != -1) {
			type = "min";
		} else if (expa[i].indexOf(">") != -1) {
			type = "max";
		} else {
			type = "set";
		}
		
		// set clauses
		if (type === "set") {
			s = expa[i].substring(expa[i].indexOf("=") + 1);
			val = attributeManager.getAttributeValue(s);
			if (val === null) {
				alert("Unable to set rule for attribute value id: " + s);
				return;
			}
			att = val.attribute;
			if (att === null) {
				alert("Unable to set rule for attribute id: " + s);
				return;
			}
			if (!att instanceof cds.configurator.SetAttribute) {
				alert("Attempt to create set rule for non-set attribute id: " + s);
				return;
			}

		// range clauses using < and >
		} else if (type === "min") {
			pos = expa[i].indexOf("<");
			if (pos !== -1) {
				s = expa[i].substring(0, pos);
				att = attributeManager.getAttribute(s);
				if (att === null) {
					alert("Unable to set rule for attribute id: " + s);
					return;
				}
				if (!att instanceof cds.configurator.RangeAttribute) {
					alert("Attempt to create min rule for non-range attribute id: " + s);
					return;
				}

				val = expa[i].substring(pos + 1);
			}

		} else if (type === "max") {
			pos = expa[i].indexOf(">");
			if (pos !== -1) {
				type = "max";
				s = expa[i].substring(0, pos);
				att = attributeManager.getAttribute(s);
				if (att === null) {
					alert("Unable to set rule for attribute id: " + s);
					return;
				}
				if (!att instanceof cds.configurator.RangeAttribute) {
					alert("Attempt to create rt rule for non-range attribute id: " + s);
					return;
				}

				val = expa[i].substring(pos + 1);
			}
		}
	
		// if we have a valid clause, add it to component list
		if (type !== null && att !== null && val !== null) {
			c = new cds.configurator.DefaultRuleComponent(type, att, val);
			rule.components.push(c);
		}
	}
	
	// if we have components add the rule to the enforced objects
	for (i = 0; i < rule.components.length; i++) {
		rule.components[i].enforcedObject.rules.push(rule);
	}
};

// -------------------------------------
// Default rule enforce
/*

if mouseout
	remove hover constraint if there
	return

if mouseover
	add hover constraint if all but one component true

if click or change
	remove click constraint if there - think about this more
	* remove hover constraint if there - should not have to do this for change, think about this more
	add click constraint if all but one component true

*/
// -------------------------------------
cds.configurator.DefaultRule.prototype.enforce = function (object, result, type) {
	var c, componentToConstrain, hoverComponentToConstrain, i, robj;
	
	if (type === "mouseout") {
		if (this.hoverConstrainedComponent !== null) {
			c = this.hoverConstrainedComponent;
			c.unconstrain(this.pid);
			this.hoverConstrainedComponent = null;
			return [c.enforcedObject];
		} else {
			return null;
		}
	}
	
	// handle this first since we need to check for components to constrain
	// differently (have to determine hover and non-hover seperately)
	if (type === "change") {
		// if we are already constrained, not possible to change that
		if ((this.constrainedComponent !== null && this.constrainedComponent.enforcedObject === object) ||
			(this.hoverConstrainedComponent !== null && this.hoverConstrainedComponent.enforcedObject === object)) {
			return null;
		}
		
		// find any non-hover components which must be constrained
		componentToConstrain = null;
		for (i = 0; i < this.components.length; i++) {
			if (!this.components[i].enforce(false)) {
				// if this is the first false, save it
				if (componentToConstrain === null) {
					componentToConstrain = this.components[i];
				// if this is the second false, nothing to constrain
				} else {
					componentToConstrain = null;
					break;
				}
			}
		}
		// find any hover components which must be constrained
		hoverComponentToConstrain = null;
		for (i = 0; i < this.components.length; i++) {
			if (!this.components[i].enforce(true)) {
				// if this is the first false, save it
				if (hoverComponentToConstrain === null) {
					hoverComponentToConstrain = this.components[i];
				// if this is the second false, nothing to constrain
				} else {
					hoverComponentToConstrain = null;
					break;
				}
			}
		}

		robj = [];

		// if we have any current constraints, remove them
		if (this.constrainedComponent !== null) {
			this.constrainedComponent.unconstrain(this.id);
		}
		if (this.hoverConstrainedComponent !== null) {
			this.hoverConstrainedComponent.unconstrain(this.pid);
		}

		// push any changed objects or object to constrain next into return
		if (componentToConstrain !== null) {
			robj.push(componentToConstrain.enforcedObject);
		}
		if (hoverComponentToConstrain !== null &&
				hoverComponentToConstrain !== componentToConstrain) {
			robj.push(hoverComponentToConstrain.enforcedObject);
		}
		if (this.constrainedComponent !== null &&
				this.constrainedComponent !== componentToConstrain &&
				this.constrainedComponent !== hoverComponentToConstrain) {
			robj.push(this.constrainedComponent.enforcedObject);
		}
		if (this.hoverConstrainedComponent !== null &&
				this.hoverConstrainedComponent !== componentToConstrain &&
				this.hoverConstrainedComponent !== hoverComponentToConstrain &&
				this.hoverConstrainedComponent !== this.constrainedComponent) {
			robj.push(this.hoverConstrainedComponent.enforcedObject);
		}

		this.constrainedComponent = null;
		this.hoverConstrainedComponent = null;

		// if needed, add new constraints
		if (componentToConstrain !== null) {
			this.constrainedComponent = componentToConstrain;
			this.constrainedComponent.constrain(this.id, false);
		}
		if (hoverComponentToConstrain !== null) {
			this.hoverConstrainedComponent = hoverComponentToConstrain;
			this.hoverConstrainedComponent.constrain(this.pid, true);
		}

		return robj;
	}
	
	componentToConstrain = null;
	for (i = 0; i < this.components.length; i++) {
		if (!this.components[i].enforce()) {
			// if this is the first false, save it
			if (componentToConstrain === null) {
				componentToConstrain = this.components[i];
			// if this is the second false, nothing to constrain
			} else {
				componentToConstrain = null;
				break;
			}
		}
	}

	if (type === "mouseover") {
		if (this.hoverConstrainedComponent !== null) {
			alert("Hover CC is not null on mouseover event for rule: " + this + "\nComponent: " +
					this.hoverConstrainedComponent);
		}

		if (componentToConstrain !== null) {
			this.hoverConstrainedComponent = componentToConstrain;
			componentToConstrain.constrain(this.pid, true);
			return [componentToConstrain.enforcedObject];
		} else {
			return null;
		}
	}

	if (type === "click") {
		robj = [];
		
		// if we have any constraints, remove them
		if (this.constrainedComponent !== null) {
			this.constrainedComponent.unconstrain(this.id);
		}
		if (this.hoverConstrainedComponent !== null) {
			this.hoverConstrainedComponent.unconstrain(this.pid);
		}

		// push any changed objects or object to constrain next into return
		if (componentToConstrain !== null) {
			robj.push(componentToConstrain.enforcedObject);
		}
		if (this.constrainedComponent !== null &&
				this.constrainedComponent !== componentToConstrain) {
			robj.push(this.constrainedComponent.enforcedObject);
		}
		if (this.hoverConstrainedComponent !== null &&
				this.hoverConstrainedComponent !== componentToConstrain &&
				this.hoverConstrainedComponent !== this.constrainedComponent) {
			robj.push(this.hoverConstrainedComponent.enforcedObject);
		}

		this.constrainedComponent = null;
		this.hoverConstrainedComponent = null;
		
		// if needed, add the new one
		if (componentToConstrain !== null) {
			this.constrainedComponent = componentToConstrain;
			this.constrainedComponent.constrain(this.id, false);
		}

		return robj;
	}

	return null;
};

cds.configurator.DefaultRule.prototype.destroy = function () {
	var i;
	
	cds.configurator.DefaultRule.superclass.destroy.call(this);
	for (i = 0; i < this.components.length; i++) {
		if (typeof this.components[i].destroy === "function") {
			this.components[i].destroy();
		}
	}
	this.components = null;
	this.constrainedComponent = null;
	this.hoverConstrainedComponent = null;
};

cds.configurator.DefaultRule.prototype.toString = function () {
	return "cds.configurator.DefaultRule(" + this.id + ")";
};


// -------------------------------------
// Default rule component object
// Types: set, min, max
// -------------------------------------

cds.configurator.DefaultRuleComponent = function (type, attribute, value) {
	this.type = type;
	this.attribute = attribute;
	this.value = value;
	this.enforcedObject = (this.type === "set") ? this.value : this.attribute;
	this.isEnforcedRange = (this.enforcedObject instanceof cds.configurator.RangeAttribute);
	this.constraintValue = null;

	if (this.type === "min") {
		this.value = parseFloat(this.value);
		this.constraintValue = [this.value, null];
	} else if (this.type === "max") {
		this.value = parseFloat(this.value);
		this.constraintValue = [null, this.value];
	}
	
	// returns true if the associated clause in the original expression is true
	// argument to enforce, checkHover if true, enforces only isHovered set
	// states, if false, only checks isSelected set states.  If this is null
	// both states are checked
	this.enforce = null;
	if (type === "set") {
		this.enforce = function (checkHover) {
			if (checkHover === true) {
				return this.value.isHovered;
			} else if (checkHover === false) {
				return this.value.isSelected;
			} else {
				return (this.value.isSelected || this.value.isHovered);
			}
		};
	} else if (type === "min") {
		this.enforce = function (checkHover) {
			return this.attribute.value !== null && this.attribute.value < this.value;
		};
	} else if (type === "max") {
		this.enforce = function (checkHover) {
			return this.attribute.value !== null && this.attribute.value > this.value;
		};
	}

	this.unconstrain = function (id) {
		this.enforcedObject.constraints.remove(id);
		if (this.isEnforcedRange) {
			this.enforcedObject.setMin();
			this.enforcedObject.setMax();
		}
	};
	
	this.constrain = function (id, isMouseEvent) {
		this.enforcedObject.constraints.add(new cds.configurator.Constraint(id, this.constraintValue, isMouseEvent));
		if (this.isEnforcedRange) {
			this.enforcedObject.setMin();
			this.enforcedObject.setMax();
		}
	};
	
	this.destroy = function () {
		this.attribute = null;
		this.value = null;
		this.enforcedObject = null;
		this.constraintValue = null;
	};

	this.toString = function () {
		var val;
		val = this.value;
		if (typeof this.value === "object") {
			val = this.value.id;
		}
		return "cds.configurator.DefaultRuleComponent(" + this.type + "," + this.attribute.id + "," + val + ")";
	};
};


