(function() {

	'use strict';

	angular.module('uniflash').service('ufPropertyRules',ufPropertyRules);

	ufPropertyRules.$inject = ['ufPropertiesValue'];
	
	function ufPropertyRules(ufPropertiesValue) {
		let dynamicRules = [];
		let configInfo;
		
		function initialize(info:any) {
			dynamicRules = [];
			configInfo = info;
		}
		
		// parse the property object for rules to add and apply
		function addRule(property:any,isa:string,coreName:string) {
			if ( property.uniqueDefault ) {
				parseRuleTypeObj(property.uniqueDefault,'uniqueDefault',property,isa,coreName);
			}
			if ( property.disableIf ) {
				parseRuleTypeObj(property.disableIf,'disableIf',property,isa,coreName);
			}
			if ( property.enableIf ) {
				parseRuleTypeObj(property.enableIf,'enableIf',property,isa,coreName);
			}
		}
		
		// private function for handling different types of rules (uniqueDefault, disableIf, enableIf)
		function parseRuleTypeObj(typeObj:any,type:string,property:any,isa:string,coreName:string) {
			let value;
			if ( type === 'uniqueDefault' ) {
				value = convertValue(property.valueType,typeObj._value);
			}
			
			// parse the rule by getting the key and value pairs
			for (var key in typeObj) {
				if (typeObj.hasOwnProperty(key) && (key !== '_value')) {
					// if rule contains multiple different sub rules, it will be stored as an array
					if ( typeObj[key] instanceof Array ) {
						_.each( typeObj[key], function(curRuleOption) {
							if ( key === 'property' ) {
								// rules based on other properties are stored as dynamic rules
								addDynamicRule(property._id,key,curRuleOption,type,value,isa,coreName,typeObj[key].length);
							} else {
								// rules based on attributes will be parse immediately
								evaluateStaticRule(property._id,key,curRuleOption,type,value,isa,coreName);
							}
						});
					} else {
						// parsing single rules
						if ( key === 'property' ) {
							addDynamicRule(property._id,key,typeObj[key],type,value,isa,coreName,1);
						} else {
							evaluateStaticRule(property._id,key,typeObj[key],type,value,isa,coreName);
						}
					}
				}
			}
		}
		
		// private function for converting value to necessary type
		function convertValue(type:any,value:any) {
			if ( type === 'Boolean' ) {
				return (value === '1' ? true : false);
			}
			if ( type === 'Numeric' ) {
				return parseInt(value);
			}
			return value;
		}

		// private function for determining if a rule matches to the given conditions
		function evaluateStaticRule(id:string,key:string,value:any,type:string,result:any,isa:string,coreName:string) {
			let rule = {
				'id':id,
				'key':key,
				'value':value,
				'type':type,
				'result':result,
				'coreName':coreName
			};
			
			if ( ( (rule.key === 'target') && ( rule.value === isa ) ) || 				// match target rule
				 ( (rule.key === 'os') && ( rule.value === TICloudAgent.getOS() ) ) ||	// match os rule
				 ( (rule.key === 'connection') && ( configInfo.connectionXMLFile.toLowerCase().indexOf(rule.value.toLowerCase()) !== -1 ) ) ||	// match connection rule
			     matchKey(rule) )														// match key to device/connection properties 
			{
				applyMatchedRule(rule,coreName);
			}
		}
		
		// private function for determining if a rule matches to the given conditions
		function addDynamicRule(id:string,key:string,value:any,type:string,result:any,isa:string,coreName:string,addDynamicRule:number) {
			let rule = {
				'id':id,
				'key':key,
				'type':type,
				'result':result,
				'coreName':coreName,
				'dependentId':value._id,
				'dependentValue':value.__text
			};
			
			dynamicRules.push(rule);
		}
		
		// private function for determine if a key matches any of the provided config properties
		function matchKey(rule:any) {
			// try to match one of the connection properties
			if ( configInfo.connectionProperties && configInfo.connectionProperties[rule.key] && (configInfo.connectionProperties[rule.key] === rule.value) ) {
				return true;
			}
			// try to match one of the device properties
			if ( configInfo.deviceProperties && configInfo.deviceProperties[rule.key] && (configInfo.deviceProperties[rule.key].Value === rule.value) ) {
				return true;
			}
			// try to match partnum
			if ( rule.key === 'partnum' && rule.value === configInfo.partnum ) {
				return true;
			}
			// custom rule for the TargetVoltage "FROM_DEVICE" unique_default rule
			if ( (rule.key==='target_vcc_default') && isNaN(rule.result) && configInfo.deviceProperties[rule.key] ) {
				rule.result = parseInt(configInfo.deviceProperties[rule.key].Value);
				return true;
			}
			return false;
		}
		
		// after a rule has been matched, apply the logic of the rule
		function applyMatchedRule(rule:any,coreName:any) {
			let valueObj = ufPropertiesValue.getValueObj(rule.id,coreName);
			if ( rule.type === 'uniqueDefault' ) {
				valueObj.value = rule.result;
			} else if ( rule.type === 'disableIf') {
				valueObj.disabled = true;
			} else if ( rule.type === 'enableIf' ) {
				valueObj.disabled = false;
			}
		}

		// resolve all dynamic rules related to a property ID and core.
		// call this when a property value has change
		function resolvePropertyRules(id:string, coreName:string, valueObj:any) {
			_.each(dynamicRules, function(curRule) {
				if ( (id === curRule.dependentId) && (coreName === curRule.coreName) ) {
					let affectedPropertyValue = ufPropertiesValue.getValueObj(curRule.id,curRule.coreName);
					let condition = (valueObj.value === convertValue(valueObj.valueType,curRule.dependentValue));
					
					// change the disabled status
					// a matched condition means that we should apply the rule (disable if evaluating disableIf, enable if evaluating enableIf)
					// or if there are no related rules, it applies directly
					if ( condition ) {
						affectedPropertyValue.disabled = (curRule.type === 'disableIf');
					} else if ( curRule.numRelated === 1 ) {
						affectedPropertyValue.disabled = !(curRule.type === 'disableIf');
					} else {
						affectedPropertyValue.disabled = evaluateAllRelatedRules(curRule);
					}
				}
			});
		}
		
		// evaluate all related rules to see if we should disable or enable a property
		function evaluateAllRelatedRules(rule:any) {
			let matchingRelatedRules = _.filter(dynamicRules, function(curRule) { 
				if ( (curRule.id === rule.id) && (curRule.type === rule.type) && (curRule.coreName === rule.coreName) ) {
					let valueObj = ufPropertiesValue.getValueObj(curRule.dependentId,curRule.coreName);
					return (valueObj.value === convertValue(valueObj.valueType,curRule.dependentValue));
				}
			});
			
			// at least one related rule matched
			if ( matchingRelatedRules.length > 0 ) {
				return (rule.type === 'disableIf');
			} else {
				return (rule.type !== 'disableIf');
			}
		}
		
		// evaluate and resolve all 
		function resolveAllRules() {
			_.each(dynamicRules, function(curRule) {
				let curValueObj = ufPropertiesValue.getValueObj(curRule.dependentId,curRule.coreName);
				if ( curValueObj ) {
					resolvePropertyRules(curRule.dependentId,curRule.coreName,curValueObj);
				}
			});
		}
		
		return {
			initialize : initialize,
			addRule : addRule,
			resolvePropertyRules : resolvePropertyRules,
			resolveAllRules : resolveAllRules
		};
	}

})();
