(function () {

	'use strict';

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

	ufPropertiesDB.$inject = ['$q', '$http', 'x2js', 'ufPropertiesValue', 'ufSearch', 'menuContribSrv', 'ufTargetConfig', 'ufPropertyRules', 'ufMemoryRanges'];

	function ufPropertiesDB($q, $http, x2js, ufPropertiesValue, ufSearch, menuContribSrv, ufTargetConfig: uniflash.target_config.TargetConfig, ufPropertyRules, ufMemoryRanges) {
		let propertiesJSON = [];
		let curConfigInfo;
		let includeGeneric = true;
		let init = false;
		let showID = false;
		let curFilterStr = '';
		let partnumLC = '';
		let flashPropertiesTranslatorJSON;

		let generatedCategoryList = [];

		function getPartnum() {
			return partnumLC;
		}
		// query for the properties data, given the device, connection, and configuration information
		function getPropertiesData(configInfo) {
			let deferred = $q.defer();

			ufPropertyRules.initialize(configInfo, this);
			if (!flashPropertiesTranslatorJSON) {
				ufTargetConfig.getFileContent("ccs_base/DebugServer/propertyDB/FlashPropertiesTranslator.xml")
					.then((content: any) => {
						if (content instanceof Blob) {
							var reader = new FileReader();
							reader.onload = function (readContent: any) {
								flashPropertiesTranslatorJSON = x2js.xml_str2json(readContent.currentTarget.result); // convert XML to JSON
								getPropertiesDataInternal(configInfo, deferred);
							};
							return reader.readAsText(content);
						} else {
							flashPropertiesTranslatorJSON = x2js.xml_str2json(content); // convert XML to JSON
							getPropertiesDataInternal(configInfo, deferred);
						}
					});
			} else {
				getPropertiesDataInternal(configInfo, deferred);
			}

			menuContribSrv.editMenu('Save Session', false);
			return deferred.promise;
		}

		function getPropertiesDataInternal(configInfo, deferred) {
			curConfigInfo = configInfo;

			let url = '';
			partnumLC = curConfigInfo.partnum.toLowerCase();

			if (curConfigInfo.connectionXMLFile.indexOf('j-link') !== -1) {
				// don't need to get the flash properties for j-link; we should be checking 'disableInternalFlashManager', but we are not currently getting the driver properties right now
				propertiesJSON = [];
				deferred.resolve();
				return;
			}

			// special case for certain msp430 g2 devices
			if ((partnumLC === 'f20x1_g2x0x_g2x1x') || (partnumLC === 'f20x1_g2x0x_g2x1x')) {
				partnumLC = 'msp430';
			}

			// look up the property file name
			let matchedPropertyXML: any = matchPartToPropertiesXML(partnumLC, curConfigInfo.connectionXMLFile);

			if (matchedPropertyXML.type === 'internal') {
				url = 'flash_properties/data/' + matchedPropertyXML.propertyFileName;

				$http.get(url).then(function (response) {
					initPropertiesData(response.data);
					init = true;
					deferred.resolve();
				}, function (error) {
					console.log(url + ': ' + error.data);
					propertiesJSON = [];
					deferred.resolve();
				});
			} else {
				ufTargetConfig.getFileContent('ccs_base/DebugServer/propertyDB/' + matchedPropertyXML.propertyFileName)
					.then((content: any) => {
						if (content instanceof Blob) {
							var reader = new FileReader();
							reader.onload = function (readContent: any) {
								if (readContent.currentTarget.result !== 'File not found') {
									initPropertiesData(readContent.currentTarget.result);
									init = true;
									deferred.resolve();
								} else {
									console.log('ccs_base/DebugServer/propertyDB/' + matchedPropertyXML.propertyFileName + ': File not found');
									propertiesJSON = [];
									deferred.resolve();
								}
							};
							return reader.readAsText(content);
						} else {
							initPropertiesData(content);
							init = true;
							deferred.resolve();
						}
					}, function (error) {
						console.log(url + ': ' + error.data);
						propertiesJSON = [];
						deferred.resolve();
					});
			}
		}

		function matchPartToPropertiesXML(partnumLC, connectionXML) {
			let rtnVal = { type: '', propertyFileName: '' };

			// internal matching first, these are special XML files that were modified for UniFlash used.
			// These will not be needed when all of the existing XML files are converted.
			rtnVal.type = 'internal';
			if ((partnumLC.indexOf('bootloader') === -1) &&
				((partnumLC.indexOf('msp430') !== -1) ||
					(partnumLC.indexOf('rf430') !== -1) ||
					(partnumLC.indexOf('cc430') !== -1) ||
					((partnumLC.indexOf('msp432') !== -1) && (connectionXML.indexOf('TIMSP430-USB') !== -1)))
			) {
				rtnVal.propertyFileName = 'PropertiesDB.xml';
				includeGeneric = false;
			}

			if (!rtnVal.propertyFileName) {
				// get the property XML from ticloudagent
				rtnVal.type = 'ticloudagent';

				let matchedFlashPropertyRule = _.find(flashPropertiesTranslatorJSON.FlashPropertiesTranslator.FlashProperties, function (curFlashPropertyRules: any) {
					if (curFlashPropertyRules.partnum._beginsWith || curFlashPropertyRules.partnum._endsWith || curFlashPropertyRules.partnum._contains || curFlashPropertyRules.partnum._regex) {
						// single <partnum> rule available. Check if the current partnum matches the rule
						return matchRule(curFlashPropertyRules.partnum, partnumLC);
					} else {
						// multiple <partnum> rule available. Check if any of the rules matched
						let curPartnumRule = _.find(curFlashPropertyRules.partnum, function (curPartnum: any) {
							return matchRule(curPartnum, partnumLC);
						});

						return curPartnumRule;
					}
				});

				if (matchedFlashPropertyRule) {
					// if a rule matched, use the matching 'name' of the XML file to use
					rtnVal.propertyFileName = matchedFlashPropertyRule._name + '_FlashProperties.xml';
				} else if ((partnumLC.indexOf('msp430') !== -1) ||
					(partnumLC.indexOf('rf430') !== -1) ||
					(partnumLC.indexOf('cc430') !== -1) ||
					((partnumLC.indexOf('msp432') !== -1) && (connectionXML.indexOf('TIMSP430-USB') !== -1))) {
					// special case for MSP430, use the PropertiesDB.xml
					rtnVal.propertyFileName = 'PropertiesDB.xml';
				} else {
					// no rules matches, just use the name of the partnum
					rtnVal.propertyFileName = curConfigInfo.partnum + '_FlashProperties.xml';
				}
			}

			return rtnVal;
		}

		// return true if rule matches the partnum
		function matchRule(rule, partnum) {
			let partnumToMatch;
			let ruleToMatch;
			if (rule._regex) {
				return partnum.match(new RegExp(rule._regex, 'gi'));
			} else {
				if (rule._beginsWith) {
					ruleToMatch = rule._beginsWith.toLowerCase();
					partnumToMatch = partnum.substring(0, rule._beginsWith.length);
				} else if (rule._endsWith) {
					ruleToMatch = rule._endsWith.toLowerCase();
					partnumToMatch = partnum.substring(partnum.length - rule._endsWith.length, partnum.length);
				}

				// wildcard not supported in 'contains' rules yet
				if (ruleToMatch.indexOf('*') !== -1) {
					for (let i = 0; i < partnumToMatch.length; i++) {
						if ((ruleToMatch.charAt(i) !== '*') && (partnumToMatch.charAt(i) !== ruleToMatch.charAt(i))) {
							return false;
						}
					}
					return true;
				} else {
					return ((rule._beginsWith && (partnumToMatch === ruleToMatch) && checkMSP432FETRule(rule, partnum)) ||
						(rule._endsWith && (partnumToMatch === ruleToMatch) && checkMSP432FETRule(rule, partnum)) ||
						(rule._contains && (partnum.indexOf(rule._contains.toLowerCase()) !== -1) && checkMSP432FETRule(rule, partnum)));
				}
			}
		}

		// handle msp432fet rule in FlashPropertiesTranslator.xml
		// return true if rule does not exist, or if exist and we are currently using MSP432+FET
		function checkMSP432FETRule(rule, partnum) {
			if (rule._msp432fet) {
				return isMSP432FET(partnum, curConfigInfo.connectionXMLFile);
			}
			return true;
		}

		// parse the XML content coming from the selected properties XML file
		function initPropertiesData(content) {
			propertiesJSON = [];
			generatedCategoryList = [];
			ufPropertiesValue.reset();
			ufMemoryRanges.clear();

			angular.forEach(curConfigInfo.cores, function (core) {
				if (core.isa === 'CS_DAP' || core.isa === 'CS_DAP_PC' || core.isa === 'CS_ETB' || core.isa === 'ETB11' || core.isa === 'ICEPICK_B' || core.isa === 'ICEPICK_C' || core.isa === 'ICEPICK_D' || core.isa === 'cs_child') { return; }

				if (content !== '') {
					let curPropertiesJSON = x2js.xml_str2json(content);	// convert XML to JSON
					let filteredPropertiesList = [];
					let curISA = translateToISA(core.isa);

					_.each(curPropertiesJSON.PropertyList.property, function (curProperty) {
						if (curProperty.target === 'generic' || ((typeof curProperty.target === 'string') && (curProperty.target === curISA))) {
							filteredPropertiesList.push(curProperty);
						} else {
							// target is an array instead of string (multiple ISAs given), need to find a match in the array
							let match = _.find(curProperty.target, function (curTarget: any) {
								return (curTarget === curISA);
							});
							if (match !== undefined) {
								filteredPropertiesList.push(curProperty);
							}
						}
					});

					// add custom properties (ie; FlashVerbose)
					filteredPropertiesList.push({
						'_id': 'FlashVerboseMode',
						'defaultValue': '0',
						'name': 'Verbose Output',
						'target': 'generic',
						'valueType': 'Boolean'
					});

					// update the properties list
					curPropertiesJSON.PropertyList.property = filteredPropertiesList;

					// initialize the value list
					ufPropertiesValue.initialize(curPropertiesJSON, curConfigInfo, core);

					_.each(curPropertiesJSON.PropertyList.property, function (property) {
						let curValueObj = ufPropertiesValue.getValueObj(property._id, core.name);

						if (_.contains(property._id, "MemoryRange")) {
							const dashIndex = curValueObj.value.indexOf("-");
							const startAddress = curValueObj.value.substring(0, dashIndex);
							const endAddress = curValueObj.value.substring(dashIndex + 1);

							ufMemoryRanges.addMemoryRange({
								address: startAddress,
								length: parseInt(endAddress) - parseInt(startAddress) + 1,
								name: property.name,
							});
						} else {
							// change boolean strings to values
							if (property.valueType === 'Boolean') {
								curValueObj.value = (curValueObj.value === '1' ? true : false);
							}
							// change numeric strings to int
							if (property.valueType === 'Numeric') {
								curValueObj.value = parseInt(curValueObj.value);
							}

							// if enableIf rules or hidden tag exist, the property is disabled by default
							if (property.enableIf || (property.hidden === '')) {
								curValueObj.disabled = true;
							}

							// parse each property and apply the rules (enable/disable/uniquDefault)
							ufPropertyRules.addRule(property, curISA, core.name);
						}
					});

					propertiesJSON.push({ 'name': core.name, 'properties': curPropertiesJSON });

					// search through groups and nodes to tag property parents
					var mainNodeChildrenList = [];

					if ((partnumLC.indexOf('bootloader') === -1) &&
						((partnumLC.indexOf('msp430') !== -1) ||
							(partnumLC.indexOf('rf430') !== -1) ||
							(partnumLC.indexOf('cc430') !== -1) ||
							isMSP432FET(partnumLC, curConfigInfo.connectionXMLFile)
						)
					) {
						var mainNode = _.find(curPropertiesJSON.PropertyList.node, function (curNode: any) { return (curNode._id === 'MSP430PropertiesNode'); });
						mainNodeChildrenList = mainNode.children.child;
					} else {
						mainNodeChildrenList = curPropertiesJSON.PropertyList.node.children.child;
					}

					// parse the Nodes and fill out the parent information
					_.each(mainNodeChildrenList, function (id: any) {
						var curProp = getProperty(id, core.name);
						if (curProp) {
							curProp.parentId = curPropertiesJSON.PropertyList.node._id;

							if (!curProp.categories) {
								curProp.categories = {};
								curProp.categories.category = curPropertiesJSON.PropertyList.node.name;
							}
							if (curProp.categories.category && !_.contains(generatedCategoryList, curProp.categories.category)) {
								generatedCategoryList.push(curProp.categories.category);
							}
						}
					});

					// parse the Groups and fill out the parent information
					_.each(curPropertiesJSON.PropertyList.group, function (group: any) {
						if (group.children.child instanceof Array) {
							_.each(group.children.child, function (id: any) {
								var curProp = getProperty(id, core.name);
								if (curProp) {
									curProp.parentId = group._id;

									if (!curProp.categories) {
										curProp.categories = {};
										curProp.categories.category = group.name;
									}
									if (curProp.categories.category && !_.contains(generatedCategoryList, curProp.categories.category)) {
										generatedCategoryList.push(curProp.categories.category);
									}
								}
							});
						} else {
							var curProp = getProperty(group.children.child, core.name);
							if (curProp) {
								curProp.parentId = group._id;

								if (!curProp.categories) {
									curProp.categories = {};
									curProp.categories.category = group.name;
								}
								if (curProp.categories.category && !_.contains(generatedCategoryList, curProp.categories.category)) {
									generatedCategoryList.push(curProp.categories.category);
								}
							}
						}

					});

					init = true;
				}
			});
			ufPropertyRules.resolveAllRules();
		}

		// if device contains MSP432 and connection is TIMSP430-USB, we are using MSP432+FET
		function isMSP432FET(partnum, connectionXML) {
			return (partnum.indexOf('msp432') !== -1) && (connectionXML.indexOf('TIMSP430-USB') !== -1);
		}

		// return the full properties list (Note: no nodes or groups)
		function getPropertiesList(filtered: boolean, core: string) {
			var properties = getPropertyByCore(core);
			if (properties && properties.PropertyList) {
				if (filtered) {
					var matchedProperties = _.filter(properties.PropertyList.property, function (prop: any) {
						var idMatched = (prop._id.toLowerCase().indexOf(curFilterStr.toLowerCase()) !== -1);
						var nameMatched = (prop.name.toLowerCase().indexOf(curFilterStr.toLowerCase()) !== -1);

						return (idMatched && nameMatched);
					});
					return matchedProperties;
				} else {
					properties.PropertyList.property;
				}
			} else {
				return [];
			}
		}

		// return properties list by 1 or more categories
		function getPropertiesListByCategory(categories: any, includeButton: any, core: string, searchable: boolean) {
			var properties = getPropertyByCore(core);

			if (properties && properties.PropertyList && (properties.PropertyList.categories || generatedCategoryList.length > 0)) {
				var matchedParentIDs = [];

				if (searchable) {
					_.each(properties.PropertyList.property, function (prop: any) {
						if ((prop.hidden !== '') &&
							((prop._id.toLowerCase().indexOf(curFilterStr.toLowerCase()) !== -1) ||
								(prop.name.toLowerCase().indexOf(curFilterStr.toLowerCase()) !== -1))) {
							matchedParentIDs.push(prop.parentId);
						}
					});
				}

				var matchedProperties = _.filter(properties.PropertyList.property, function (prop: any) {
					prop.matchedId = false;

					// custom code to remove the Flash Validate properties from the UI for now
					if (prop._id.indexOf('FlashProfileValidate') !== -1 || (prop._id === 'Separator_Text_Prop') || (prop.hidden === '')) {
						return false;
					}

					if (!includeButton && (prop.valueType === 'Button')) { return false; }

					var matched = _.find(categories, function (category) {
						return matchCategory(prop, category);
					});

					var matched2 = true;
					var idMatched = false;
					var nameMatched = false;
					var valueMatchedList = [];
					var parentMatched = false;

					if (searchable) {
						if (curFilterStr !== '') {

							idMatched = (prop._id.toLowerCase().indexOf(curFilterStr.toLowerCase()) !== -1);
							nameMatched = (prop.name.toLowerCase().indexOf(curFilterStr.toLowerCase()) !== -1);

							if (prop.valueType === 'StringList' || prop.valueType === 'ChoiceList') {
								valueMatchedList = _.filter(prop.values.value, function (curValue: any) {
									return (curValue.toLowerCase().indexOf(curFilterStr.toLowerCase()) !== -1);
								});
							}

							var foundProp = _.find(matchedParentIDs, function (parentId) {
								return (parentId === prop.parentId);
							});
							parentMatched = (foundProp !== undefined);

							matched2 = (idMatched || nameMatched || parentMatched || valueMatchedList.length > 0);
						}

						if (matched2 && (prop._id.toLowerCase().indexOf(curFilterStr.toLowerCase()) !== -1) &&
							(prop.name.toLowerCase().indexOf(curFilterStr.toLowerCase()) === -1) &&
							(valueMatchedList.length === 0)) {
							prop.matchedId = true;
						}
					}

					let curValueObj = ufPropertiesValue.getValueObj(prop._id, core);

					return (matched !== undefined) && matched2 && (!curValueObj || !curValueObj.disabled);
				});
				return matchedProperties;
			} else {
				return [];
			}
		}

		function getFilter() {
			return curFilterStr;
		}
		function setFilter(filterStr: string) {
			curFilterStr = filterStr;
		}

		function getCategoryList(core: string) {
			var properties = getPropertyByCore(core);

			if (properties && properties.PropertyList.categories) {
				if (typeof getPropertyByCore(core).PropertyList.categories.category === 'string') {
					var rtnArr = [];
					rtnArr.push(getPropertyByCore(core).PropertyList.categories.category);
					return rtnArr;
				} else {
					return getPropertyByCore(core).PropertyList.categories.category;
				}
			} else {
				if (generatedCategoryList.length > 0) {
					return generatedCategoryList;
				} else {
					return ['All'];	// default category array
				}
			}
		}

		function getCategoryName(category: any) {
			if (typeof category === 'string') {
				return category;
			} else {
				return category.__text;
			}
		}

		function getCategoryToggle(category: any) {
			if (typeof category === 'string') {
				return false;
			} else {
				return true;
			}
		}

		function getProperty(propID: string, core: string) {
			if (getPropertyByCore(core)) {
				return _.find(getPropertyByCore(core).PropertyList.property, function (curProp: any) { return (curProp._id.toLowerCase() === propID.toLowerCase()); });
			}
			return null;
		}

		function matchTarget(prop: any, targetToMatch: string) {
			if (typeof prop.target === 'string') {
				return ((targetToMatch === prop.target) || (includeGeneric && (prop.target === 'generic')));
			} else {
				var match = _.find(prop.target, function (curTarget: any) {
					return ((targetToMatch === curTarget) || (includeGeneric && (curTarget === 'generic')));
				});
				if (match !== undefined) { return true; }
			}
			return false;
		}

		function matchCategory(prop: any, catToMatch: any) {
			if (prop.categories) {
				if (typeof prop.categories.category === 'string') {
					return (catToMatch.toLowerCase() === prop.categories.category.toLowerCase());
				} else {
					var match = _.find(prop.categories.category, function (curCat: any) {
						return (catToMatch.toLowerCase() === curCat.toLowerCase());
					});
					if (match !== undefined) { return true; }
				}
			}
			return false;
		}

		function getPropertyByCore(coreName: string) {
			var coreProperties = _.find(propertiesJSON, function (coreProperties) { return (coreProperties.name === coreName); });

			if (coreProperties) {
				return coreProperties.properties;
			} else {
				return null;
			}
		}

		function hasCategory(category: string, core: string) {
			return _.find(getCategoryList(core), function (curCategory: any) {
				if (curCategory === undefined) {
					var here = true;
				} else if (curCategory.__text) {
					// category has attributes; extract the string from __text
					return (curCategory.__text === category);
				} else {
					return (curCategory === category);
				}
			});
		}

		function translateToISA(coreID) {
			coreID = coreID.toUpperCase();
			if (coreID.indexOf('MSP430') !== -1) {
				return 'MSP430';
			} else if (coreID === 'CORTEX_M0' ||
				coreID === 'CORTEX_M0P' ||
				coreID === 'CORTEX_M3' ||
				coreID === 'CORTEX_M4' ||
				coreID === 'CORTEX_M4_CC' ||
				coreID === 'CORTEX_M4_432') {
				return 'TMS470REX';
			} else if (coreID === 'CORTEX_R4') {
				return 'TMS470RDX';
			} else if (coreID === 'CORTEX_R5') {
				return 'TMS470RDX';
			} else if (coreID === 'TMS320C28XX') {
				return 'TMS320C28XX';
			} else if (coreID === 'ARM7') {
				return 'TMS470R1X';
			}
			return coreID;
		}

        /* code for adding entries to the global search
        function updateSearchList(sStr) {
            var properties = getPropertyByCore(core);

            if (properties) {
                _.each(getPropertyByCore(core).PropertyList.property, function(prop: any) {
                    if ((prop.valueType === 'Button') &&
                        ((prop._id.toLowerCase().indexOf(sStr.toLowerCase()) !== -1) ||
                            (prop.name.toLowerCase().indexOf(sStr.toLowerCase()) !== -1))) {
                        ufSearch.addSearchEntry('Action', prop.name, 'propertiesActionSrv.addAction(\'' + prop.action + '\')');
                    }
                });
            }
        }
        ufSearch.registerObserverCallback(updateSearchList);
        */

		return {
			getPartnum: getPartnum,
			getPropertiesData: getPropertiesData,
			getPropertiesList: getPropertiesList,
			getPropertiesListByCategory: getPropertiesListByCategory,
			getCategoryList: getCategoryList,
			getCategoryName: getCategoryName,
			getCategoryToggle: getCategoryToggle,
			getProperty: getProperty,
			matchTarget: matchTarget,
			matchCategory: matchCategory,
			setFilter: setFilter,
			getFilter: getFilter,
			showID: showID,
			init: init,
			hasCategory: hasCategory
		};
	}

})();
