import { EnumDefinition } from '/sd:enum-definition.js';
import { ModbusRequestContainer } from '/sd:modbus-request-container.js'; 

/**
 * class to create instance of float value displayer. It allows for define label text and scaled value with unit inside div container. Class keeps the pointRefreshFrequency name, and assigns value to Register of ModbusTCP Network according to the name of pointRefreshFrequency
 */
export class Point {	
	/**
	 * Constructor
	 * @param {String} type					Type of point - "boolean", "enum" or "numeric"
	 * @param {Integer} holdingRegister		Holding register address in decimal
	 * @param {Float} scale					Scale for numeric value
	 * @param {String} unit					Unit added after numeric value
	 * @param {String} floatSeparator		Using float separator for numeric values
	 * @param {String} enumName				Enum name if value isn't numeric type
	 * @param {Integer} enumsDefXmlObject	Data loaded from XML - accept enums definition part only
	 * @param {String} falseText			String text repersentation for false Boolean value
	 * @param {String} trueText				String text repersentation for true Boolean value
	 */
	constructor(type="unknown", pointRefreshFrequency=null, holdingRegister=null, scale=null, unit=null, floatSeparator="dot", enumName=null, enumsDefXmlObject=null, bitNumber="All", falseText=null, trueText=null) {
		this.falseText = (falseText == "" || falseText == null) ? "false" : falseText;
		this.trueText = (trueText == "" || trueText == null) ? "true" : trueText;
		this.enum = (enumName != null && enumsDefXmlObject != null) ? new EnumDefinition(enumsDefXmlObject, enumName) : null;
		this.floatSeparator = floatSeparator;
		this.scale = (scale == null) ? 1.0 : parseFloat(String(scale).replace(",", "."));
		this.type = type;
		this.unit = unit;
		this.value = null;	//{Object} - if numeric, then scaled Float value, if boolean, then true/false, if enum then Integer representation of enum
		this.decimalPlaces = null;
		this.bitNumber = bitNumber;
		this.holdingRegister = holdingRegister;
		this.pointRefreshFrequency = pointRefreshFrequency;
	}

	/**
	* Static method, which calculates the quantity of decimal places in scale definition
	* @param    {Float} scale   Scale value
	* @return   {Integer} 		Quantity of decimal places in scale input parameter
	*/
	static calculateDecimalPlaces(scale) {
		if(scale < 0.001) 
			return 4;
		else if(scale < 0.01) 
			return 3;
		else if(scale < 0.1) 
			return 2;
		else if(scale < 1) 
			return 1;
		else 
			return 0;
	}

	/**
	 * Returns supported separating chars
	 * @returns {Array(String)}		Strings array of supported separating chars
	 */
	static floatSeparatingChars() {
		return [",", "."];
	}
	
	/**
	 * Returns supported by XML separators names 
	 * @returns {Array(String)}		Strings array of supported by XML separators names
	 */
	static floatSeparators() {
		return ["comma", "dot"];
	}

	/**
	 * Gets bit number of boolean value
	 * @returns {String}		"All" or bit number
	 */
	getBitNumber() {
		return this.bitNumber;
	}

	/**
	 * Gets decimal places
	 * @returns {Integer}	Day number
	 */
	getDecimlPlaces() {
		return this.decimalPlaces;
	}

	/**
	 * Gets the count of all enum items (pairs of label and value)
	 * @returns {Integer}		Number of items in the enum
	 */
	getEnumItemsLength() {
		return this.enum.getItemsLength();
	}

	/**
	 * Gets one pair of enum value and label by index
	 * @param {Integer} index 	Index of the searching element
	 * @returns {Collection}	Collection {label:x, value:y}
	 */
	getEnumElByIndex(index) {
		return this.enum.getElByIndex(index);
	}

	/**
	 * Gets false text value
	 * @returns {String}	False text of the point
	 */
	getFalseText() {
		return this.falseText;
	}
	
	/**
	 * Gets starting Modbus register address of point
	 * @returns {Integer}		Register address
	 */
	getModbusRegisterAddress() {
		return this.holdingRegister;
	}

	/**
	 * Gets name of pointRefreshFrequency
	 * @returns {String}	Name of point refresh frequency tunning
	 */
	getPointRefreshFrequency() {
		return this.pointRefreshFrequency;
	}
	
	/**
	 * Gets type of the point
	 * @returns {String}		Type of the point ("numeric", "enum", "boolean" or "unknown")
	 */
	getPointType() {
		return this.type;
	}
	
	/**
	 * Gets scale of the numeric point
	 * @returns {Float}		Scale value
	 */
	getScale() {
		return this.scale;
	}

	/**
	 * Gets current state of the event
	 * @returns {Boolean}	Current state of the event
	 */
	getState() {
		return this.value;
	}
	
	/**
	 * Gets true text value
	 * @returns {String}	True text of the point
	 */
	getTrueText() {
		return this.trueText;
	}
	
	/**
	 * Gets unit of the numeric point
	 * @returns {String}	Unit of the numeric point
	 */
	getUnit() {
		return this.unit;
	}
	
	/**
	 * Gets current value of the point
	 * @returns {Object} 	if numeric, then scaled Float value, if boolean, then true/false, if enum then Integer representation of enum
	 */
	getValue() {
		return this.value;
	}
	
	/**
	 * Gets rescaled value of the numeric point for Modbus TCP
	 * @returns {Integer}	Rescaled value 
	 */
	getValueToSendOverModbusTcp() {
		return (this.type == "numeric") ? Math.round(this.getValue() / this.getScale()) : (this.type == "enum") ? Math.round(this.getValue()) : (this.getValue() === true) ? 1 : 0;
	}
	
	/**
	* Gets current value multiplied by scale fixed to decimalPlaces, with replaced floatSeparator with unit (or true/false text or Enum Label) of the event field
	* @returns {String} 	Value of the event field
	*/
	getValueWithFacets() {
		if(this.type == "numeric")
			return Point.useChosenSeparator(String((this.getValue()).toFixed(this.decimalPlaces)), this.floatSeparator) + "&nbsp;&nbsp;" + String(this.unit);
		else if(this.type == "boolean")
			return (this.value) ? this.trueText : this.falseText;
		else if(this.type == "enum")
			return this.enum.getLabelByVal(this.value);
		else
			return String(this.value);
	}

	/**
	 * Removes Modbus register address of Point
	 * @param {ModbusRequestContainer} modbusRequestContainer    	ModbusRequestContainer instance, where the register was inited 
	 */
	removeModbusRegisterAddress(modbusRequestContainer, objectBinding) {
		if(this.holdingRegister != null) {
			if(this.type == "numeric" || this.type == "enum")
				modbusRequestContainer.removeRegister(this.holdingRegister, 2, objectBinding);
			else
				modbusRequestContainer.removeRegister(this.holdingRegister, 1, objectBinding);
			this.holdingRegister = null;
		}
	}

	/**
	 * Sets decimal places
	 * @param {Integer} value 	Value between 0 and 3
	 * @returns {Boolean}		True if value was set, False if not.
	 */
	setDecimalPlaces(value) {
		if(value >= 0 && value <= 3) {
			this.decimalPlaces = value;
			if(this.decimalPlaces != Point.calculateDecimalPlaces(this.scale))
				console.log("Scale, set in Excel file for scheduler, is not consistent with DecimalPlaces slot of ModbusNumericSchedule component set in AAC20 !!!");
			return true;
		}
		else
			return false;
	}

	/**
	 * Sets starting Modbus register address of Point
	 * @param {ModbusRequestContainer} modbusRequestContainer    	ModbusRequestContainer instance, where the register will be inited 
	 * @param {Integer} address										New starting Modbus register address
	 * @param {Object} objectBinding 								if referenceType is equal to "htmlId", then name (from XML) of HTML object binded to this register; if referenceType is equal to "object", then it is reference to collection object { "name": "", "value": "" }
	 * @param {String} dataType 									"int" or "unit" type provided as a string value
	 * @param {String} bitNumber 									"all" string value for numeric values or number in range <0, 15> for boolean values
	 * @param {Function} callback 									callback function to invoke if value will be updated
	 * @param {String} referenceType 								"htmlId" or "object" type provided as a string value
	 * @returns {Boolean} 											True if new address is in the range of AAC20 Modbus register addresses; False if new address is out of range AAC20 Modbus register addresses
	 */
	setModbusRegisterAddress(modbusRequestContainer, address, objectBinding, dataType = "uint", bitNumber = "all", callback = null, referenceType = "htmlId") {
		if(this.holdingRegister != null)
			this.removeModbusRegisterAddress(modbusRequestContainer, 1, objectBinding);
		if(address >= 1000 && address <= 2999) {
			this.holdingRegister = address;
			this.bitNumber = bitNumber;
			modbusRequestContainer.initRegister(this.holdingRegister, objectBinding, dataType, bitNumber, callback, referenceType);
			return true;
		}
		else
			return false;
	}

	/**
	 * Sets new scheduler type information for point
	 * @param {String} type 		New type of scheduler - it mus be equal to "numeric", "boolean", "enum" or "unknown"
	 * @returns {Boolean} 			True if type was "numeric", "boolean", "enum" or "unknown"; False in other cases; 
	 */
	setPointType(type) {
		if(type == "numeric" || type == "boolean" || type == "enum" || type == "unknown") {
			this.type = type;
			return true;
		}
		else	
			return false;
	}
	
	/**
	 * Sets state - it is saved in current state value
	 * @param {Boolean} value 	Value to set
	 */
	setState(value) {
		this.value = (value == 0x0000) ? false : true;
	}
	
	/**
	 * Sets unit to the numeric point
	 * @param {String} value 	Unit
	 */
	setUnit(value) {
		this.unit = value;
	}
	
	/**
	 * Sets value - it is saved in current value
	 * @param {Object} value 	Value to set
	 */
	setValue(value) {
		if(this.type == "numeric")
			this.value = parseFloat(value);
		else if(this.type == "enum")
			this.value = Math.round(value);
		else //if(this.type == "boolean")
			this.value = value;
	}

	/**
	 * Converts input value to chosen separator type (dot or comma)
	 * @param {Float} value 			Input value
	 * @param {String} floatSeparator 	Name of chosen float separator
	 * @returns {String}				Converted value to chosen separator
	 */
	static useChosenSeparator(value, floatSeparator) {
		const floatSeparatorIndex = (element) => element == floatSeparator;
		var floatSeparators = Point.floatSeparators();
		var floatSeparatingChars = Point.floatSeparatingChars();
		var excludedIndex = floatSeparators.findIndex(floatSeparatorIndex);
		value = String(value);
		for(var i = 0; i < floatSeparatingChars.length; i++) {
			if(i != excludedIndex) {
				value = value.replace(floatSeparatingChars[i], floatSeparatingChars[excludedIndex]);
				break;
			}
		}
		return value;
	}
}