/**
 * class to create object instance of theme for all widgets defined in template. It read theme XML file, and put styles into widgets during initialization these widgets.
 */
export class ThemeClass {
	/**
	 * Constructor
	 * @param {jQuery} xmlObject 	All data releated to the "Themes" loaded from XML
	 * @param {String} themeName 	Chosen theme name
	 */
	constructor(xmlObject, themeName) {
		this.name = themeName;
		
		//body
		this.body = this.createStyleValueList(xmlObject.find('general')[0].attributes);

		//other 
		this.htmlObjectsIds = 
		['alarmPoint', 'statusPoint', 'logoContainer', 'logoutButton', 'dateTime', 'topTextContainer', 'dateTimeContainer', 'imageContainer', 'numericPointsHeaderContainer', 'numericPointsHeaderLabel', 'numericPointsContainer','alarmPointsHeaderContainer', 'alarmPointsHeaderLabel', 'alarmPointsContainer',
		'buttonsContainer', 'hoursContainer', 'dayContainer1', 'dayContainer2', 'dayContainer3', 'dayContainer4', 'dayContainer5', 'dayContainer6', 'dayContainer7', 'schedulerDetailsContainer', 'selectedEventDetailsContainer', 'contextMenuContainer',
		'hoursHeaderContainer', 'dayHeaderContainer1', 'dayHeaderLabel1', 'dayHeaderContainer2', 'dayHeaderLabel2', 'dayHeaderContainer3', 'dayHeaderLabel3', 'dayHeaderContainer4', 'dayHeaderLabel4', 'dayHeaderContainer5', 'dayHeaderLabel5',
		'dayHeaderContainer6', 'dayHeaderLabel6', 'dayHeaderContainer7', 'dayHeaderLabel7', 'schedulerDetailsHeaderContainer', 'schedulerDetailsHeaderLabel', 'selectedEventDetailsHeaderContainer', 'selectedEventDetailsHeaderLabel', 
		'setPointsHeaderContainer', 'setPointsHeaderLabel', 'setPointsContainer', 'statusPointsHeaderContainer', 'statusPointsHeaderLabel', 'statusPointsContainer', 'button', 'addEventButton'];

		this.htmlObjectsIdsWithChildrensStyles = new Map([['schedulerRowHour', 'dayHour'],
													['dayHour', 'dayHour'],
													['schedulerRowDay', 'dayHour'],
													['numericPoint', 'numericPoint'],
													['booleanSetPoint', 'booleanSetPoint'],
													['numericSetPoint', 'numericSetPoint'],
													['schedulerDay', 'scheduleEvent'],
													['scheduleEvent', 'scheduleEvent'],
													['dialogWindow', 'dialogWindow'],
													['contextMenuAdd', 'contextMenu'],
													['contextMenuEdit', 'contextMenu'],
													['contextMenuCopyDay', 'contextMenu'],
													['contextMenuPasteDay', 'contextMenu'],
													['contextMenuClearAll', 'contextMenu'],
													['contextMenuClearDay', 'contextMenu'],
													['contextMenuRemoveEvent', 'contextMenu'],
													['contextMenuPasteDayToAll', 'contextMenu'],
													['contextMenuPasteDayToMonFri', 'contextMenu'],
													['currentOutput', 'schedulerDetail'],
													['defaultValue', 'schedulerDetail'],
													['nextEventValue', 'schedulerDetail'],
													['currentEventStartTime', 'eventDetail'],
													['currentEventStopTime', 'eventDetail'],
													['currentEventValue', 'eventDetail']]);
		
		this.stylesWithChildrensCollection = new Map([	['schedulerRowHour', new Map([['parent', false], ['odd', null], ['even', null], ['selected', null]])],
														['dayHour', new Map([['parent', false], ['odd', null], ['even', null], ['selected', null]])],
														['schedulerRowDay', new Map([['parent', false], ['odd', null], ['even', null], ['selected', null]])],
														['numericPoint', new Map([['parent', true], ['label', null], ['value', null]])],
														['booleanSetPoint', new Map([['parent', true], ['label', null], ['value', null]])],
														['numericSetPoint', new Map([['parent', true], ['label', null], ['value', null]])],
														['schedulerDay', new Map([['parent', false], ['odd', null], ['even', null], ['selectedOdd', null], ['selectedEven', null]])],
														['scheduleEvent', new Map([['parent', false], ['odd', null], ['even', null], ['selectedOdd', null], ['selectedEven', null]])],
														['dialogWindow', new Map([['parent', true], ['BasicInput', null], ['Label', null], ['Numeric', null], ['Enum', null], ['Unit', null], ['Boolean', null], ['Button', null]])],
														['contextMenuAdd', new Map([['parent', false], ['odd', null], ['even', null], ['selected', null], ['hover', null]])],
														['contextMenuEdit', new Map([['parent', false], ['odd', null], ['even', null], ['selected', null], ['hover', null]])],
														['contextMenuCopyDay', new Map([['parent', false], ['odd', null], ['even', null], ['selected', null], ['hover', null]])],
														['contextMenuPasteDay', new Map([['parent', false], ['odd', null], ['even', null], ['selected', null], ['hover', null]])],
														['contextMenuClearAll', new Map([['parent', false], ['odd', null], ['even', null], ['selected', null], ['hover', null]])],
														['contextMenuClearDay', new Map([['parent', false], ['odd', null], ['even', null], ['selected', null], ['hover', null]])],
														['contextMenuRemoveEvent', new Map([['parent', false], ['odd', null], ['even', null], ['selected', null], ['hover', null]])],
														['contextMenuPasteDayToAll', new Map([['parent', false], ['odd', null], ['even', null], ['selected', null], ['hover', null]])],
														['contextMenuPasteDayToMonFri', new Map([['parent', false], ['odd', null], ['even', null], ['selected', null], ['hover', null]])],
														['currentOutput', new Map([['parent', true], ['topLabel', null], ['value', null]])],
														['defaultValue', new Map([['parent', true], ['topLabel', null], ['value', null]])],
														['nextEventValue', new Map([['parent', true], ['topLabel', null], ['value', null]])],
														['currentEventStartTime', new Map([['parent', true], ['topLabel', null], ['value', null]])],
														['currentEventStopTime', new Map([['parent', true], ['topLabel', null], ['value', null]])],
														['currentEventValue', new Map([['parent', true], ['topLabel', null], ['value', null]])]]);
		this.stylesCollection = new Map();
		this.initStylesCollection(xmlObject);
	}

	/**
	 * Returns all styles (supported by ThemeClass) available to modify them
	 * @returns {Array(String)}		Array of styles names compatible with CSS standard
	 */
	static availibleStylesToChangeCss() {
		return ["background", "color", "font-size", "font-family", "font-weight", "font-style", "text-decoration"];
	}

	/**
	 * Returns all styles (supported by ThemeClass) available to modify them
	 * @returns {Array(String)}		Array of styles names compatible with Visualization_configurator.xlsm and config XML files accepted nomenclature
	 */
	static availibleStylesToChangeFromXml() {
		return ["background", "fontColor", "fontSize", "fontFamily", "fontWeight", "fontStyle", "textDecoration"];
	}

	/**
	 * function decodes styles of HTML body from general markup attributes of XML file
	 * @param {jQuery} attributes 		Attributes of general markup read from XML file
	 * @param {String} parentNode		Parent node of general markup ("site" or "schedules")
	 */
	static bodyStyleDecode(attributes, parentNode) {
		if(parentNode == "site" || parentNode == "schedulers") {
			var bodyObj = document.getElementById(parentNode);
			var availibleStylesToChangeFromXml = ThemeClass.availibleStylesToChangeFromXml();
			var availibleStylesToChangeCss = ThemeClass.availibleStylesToChangeCss();
			window.theme.setTheme(parentNode);
			//override styles
			for(var i = 0; i < availibleStylesToChangeFromXml.length; i++) {
				if(attributes[availibleStylesToChangeFromXml[i]] != undefined) {
					bodyObj.style.setProperty(availibleStylesToChangeCss[i], attributes[availibleStylesToChangeFromXml[i]].value);
				}
			}
		}
		else
			console.log("parentNode arg value (" + String(parentNode) + ") is out of range!!!");
	}
	
	/**
	 * Creates the array of XML's styles names supported by ThemeClass read from "attributes" input parameter
	 * @param {NamedNodeMap} attributes 	List of attributes of the HTML object loaded from XML
	 * @returns {Array}						Array of read (and supported only) from XML attributes values
	 */
	createStyleValueList(attributes) {
		var table = new Array();
		var availibleStylesToChangeFromXml = ThemeClass.availibleStylesToChangeFromXml();
		for(var i = 0; i < availibleStylesToChangeFromXml.length; i++) {
			table.push((attributes[availibleStylesToChangeFromXml[i]] != undefined) ? attributes[availibleStylesToChangeFromXml[i]].value : null);
		}
		return table;
	}
	
	/**
	 * Searches the attributes in XML files of the object by indicated parent node name
	 * @param {jQuery} xmlObject 		
	 * @param {String} parentNodeName 	Name of parent node
	 * @returns {NamedNodeMap}			All attributes
	 */
	static findAttributesByParentName(xmlObject, parentNodeName) {
		for(var i = 0; i < xmlObject.length; i++) {
			if(xmlObject[i].parentNode.localName == parentNodeName.toLowerCase()) {
				return xmlObject[i].attributes;
			}
		}
		return null;
	}

	/**
	 * Callback function for Map().forEach()
	 * It tries to find proper value in nested Map. It verifies if keys texts of the Maps are contanined in the arguments texts.
	 * @param {String} argument1 		Method equals this argument to key of main Map, and verifies if key is contaned in the argument
	 * @param {String} argument2 		Method equals this argument to key of nested Map, and verifies if nested key is contaned in the argument
	 * @param {Collection} collection 	Reference to {"result": null} collection - value will be return into collection.return
	 * @param {Map} value 				It is nested Map reference
	 * @param {String} key 				Key string text of the main Map
	 */
	static getChildValueIfArgumentsContainIdAndChildKey(argument1, argument2, collection, value, key, map) {
		if(argument1.indexOf(key) == 0) {
			for (const [ch_key, ch_value] of value.entries()) {
				if(argument2.indexOf(ch_key) >= 0) {
					collection.result = ch_value;
					break;
				}
			}
		}
	}
	
	/**
	 * Gets styles array (read from XML) of indicated object name
	 * @param {String} id 	HTML object id
	 * @returns {Array}		Array of the styles
	 */
	getStyleArray(id, value = null) {
		var result;
		var childrens;
		var collection = {"result": null};
		//verify if it is id of body HTML tag
		if(id == "site" || id == "schedulers") {
			result = this.body;
		}
		//if id is equal to simple HTML object id of this.stylesCollection
		else if(this.stylesCollection.has(id)) {
			//try get result from simple stylesCollection
			result = this.stylesCollection.get(id);
		}
		//if id is equal to parent HTML object id  of this.stylesWithChildrensCollection
		else if(this.stylesWithChildrensCollection.has(id)) {
			//get childrens map
			childrens = this.stylesWithChildrensCollection.get(id);
			result = childrens.get((value == null) ? 'parent' : value);
		}
		//Exception for statusPoints
		else if(id.indexOf("statusPoint") == 0 && id != "statusPointsContainer") {
			result = this.stylesCollection.get("statusPoint");
		}
		//Exception for alarmPoints
		else if(id.indexOf("alarmPoint") == 0 && id != "alarmPointsContainer") {
			result = this.stylesCollection.get("alarmPoint");
		}
		//Exception for buttons
		else if(id.indexOf("button") == 0 && id.length == 7) {
			result = this.stylesCollection.get("button");
		}
		//if id contains parent HTML object id of this.stylesWithChildrensCollection
		else {
			this.stylesWithChildrensCollection.forEach(ThemeClass.getChildValueIfArgumentsContainIdAndChildKey.bind(null, id, ((value == null) ? id : value), collection));
			result = collection.result;
		}
		return result;
	}

	/**
	 * Inits styles collections
	 * @param {jQuery} xmlObject 	XML file content
	 */
	initStylesCollection(xmlObject) {
		var parentXml;
		var styleId;
		//init styles of all HTML objects, which have a children objects
		for (const [id, children] of this.stylesWithChildrensCollection) {
			//init parent HTML object styles
			styleId = this.htmlObjectsIdsWithChildrensStyles.get(id);
			if(styleId === undefined) {
				break;
			}
			parentXml = xmlObject.find(styleId)[0];
			//init children HTML object styles
			for (const [ch_id, val] of children) {
				if(ch_id == 'parent') {
					children.set('parent', (val == true) ? this.createStyleValueList(parentXml.attributes) : null);
				}
				else {
					if(val === undefined) {
						break;
					}
					//Exception: Numeric style is implemented for Numeric and Enum objects
					children.set(ch_id, this.createStyleValueList($(parentXml).find((ch_id == "Enum") ? "Numeric" : ch_id)[0].attributes));
				}
			}
		}

		var id;
		//init all simple HTML objects styles
		for(var i = 0; i < this.htmlObjectsIds.length; i++) {
			id = this.htmlObjectsIds[i];
			//Exception: button style is implemented for button, logoutButton and addEventButton objects
			this.stylesCollection.set(id, this.createStyleValueList(xmlObject.find(((id.indexOf("button") == 0 && id.length == 7) || id == "logoutButton" || id =="addEventButton") ? "button" : id)[0].attributes));
		}
	}

	/**
	 * Writes styles to HTML element from input array
	 * @param {HTMLElement} element 	HTML element, where styless will be added
	 * @param {Array} table 			Array of the styles to set in the element
	 */
	static setTableAsStyle(element, table) {
		if(table != null) {
			var availibleStylesToChangeCss = ThemeClass.availibleStylesToChangeCss();
			for(var i = 0; i < table.length; i++) {
				if(table[i] != null) {
					element.style.setProperty(availibleStylesToChangeCss[i], table[i]);
				}
			}
		}
	}

	/**
	 * Writes to HTML object CSS styles values read from XML
	 * @param {String} id 	HTML object id
	 */
	setTheme(id) {
		var table = this.getStyleArray(id);
		if(table != null) {
			var element = document.getElementById(id);
			ThemeClass.setTableAsStyle(element, table);
		}
	}
}