import { BooleanPoint } from '/sd:boolean-point.js'; 
import { BasicContainer } from '/sd:basic-container.js'; 
import { Debug } from '/sd:debug.js'; 
/**
* class to create object instance of Modbus TCP Master network with one pointRefreshFrequency, class collect Modbus Registers and IDs of HTML objects to update 
* values of those objects. If object is binary class collect bitNumber too.
*/
export class ModbusRequestContainer {
	/**
	* Constructor of the ModbusRequestContainer
	* @param    {String} pollName    				Unique name of polling 
	* @param    {Integer} pointRefreshFrequency    	Points refreshing frequency time [ms]
	* @param	{Integer} clockMode					Choosen clock mode 12 or 24
	*/
	constructor(pollName, pointRefreshFrequency = 1000, clockMode = 24) {
		this.pointRefreshFrequency = pointRefreshFrequency;
		this.pollName = pollName;
		this.clockMode = clockMode;
		this.requestList = new Array();
		this.valuesCollection = new Array();
		this.pollMode = true;
	}
	
	/**
	 * Searches by name the network's index in Array of ModbusRequestContainer
	 * @param	{String} networkName 							Network name
	 * @param 	{Array(ModbusRequestContainer)} modbusReader 	Array of ModbusRequestContainer, where the network has to be found
	 * @returns {Integer}										Index number of the ModbusRequestContainer with name like networkName
	 */
	static findModbusNetworkIndex(networkName, modbusReader) {
		if(modbusReader != undefined) {
			if((modbusReader instanceof Array) && (modbusReader.length > 0)) {
				if(modbusReader[0] instanceof ModbusRequestContainer) {
					if(modbusReader.length > 0) {
						for(var i=0; i < modbusReader.length; i++) 
							if(modbusReader[i].getPollName() == networkName)
								return i;
					}
					else {
						console.log(networkName + " Modbus TCP network not defined in XML file!");
						return -1;
					}
				}
				else {
					console.log("modbusReader is empty array or elements of the array aren't ModbusRequestContainer type!");
					return -2;
				}
			}
			else {
				console.log("modbusReader is not an Array!");
				return -3;
			}
		}
		else {
			console.log("modbusReader is not defined!");
			return -4;
		}
	}
	
	/**
	* Checks in the ModbusRequestContainer if Modbus Register with specified address is defined
	* @param    {ModbusRequestContainer} modbusReader    	Sum of the hours multiplied by 60 and minutes 
	* @param    {Integer} decimalAddress    				Seconds
	* @returns  {Integer}									ndex number of register or -1 if the register is not defined
	*/
	static getIsRegisterInited(modbusReader, decimalAddress) {
		for(var i = 0; i < modbusReader.requestList.length; i++) {
			if(decimalAddress == modbusReader.requestList[i]['id']) {
				return i;
			}
		}
		return -1;
	}

	/**
	 * Gets pollMode of the ModbusRequestContainer 
	 * @returns {Boolean} 	True if ModbusRequestContainer pollMode is enabled, False if it is disabled.
	 */
	getPollMode() {
		return this.pollMode;
	}
	
	/**
	* Gets poll name set for the object
	* @returns  {String}	Poll name
	*/
	getPollName() {
		return this.pollName;
	}

	/**
	* Gets actual value of the Modbus Register
	* @param	{Integer} decimalAddress	Modbus address of the register as a decimal
	* @returns  {Integer}					Actual value
	*/
	getRegisterValue(decimalAddress) {
		var indexNumber = ModbusRequestContainer.getIsRegisterInited(this, decimalAddress);
		return (indexNumber >= 0) ? this.valuesCollection[indexNumber].value : null;
	}

	/**
	* Gets actual time
	* @returns  {Integer}	Actual value in minutes
	*/
	getTime() {
		return this.valuesCollection[0].value;
	}
	
	/**
	 * Inits the Modbus Register in this instannce of the ModbusRequestContainer
	 * @param {Integer} decimalAddress 	Decimal address of the Modbus Register
	 * @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
	 */
	initRegister(decimalAddress, objectBinding, dataType = "uint", bitNumber = "all", callback = null, referenceType = "htmlId") {
		if((decimalAddress < 1000 || decimalAddress > 2999) && decimalAddress != "time") {
			console.log("Modbus register address: " + String(decimalAddress) + " is out of range <1000; 2999>");
		} else if (!(dataType == "int" || dataType == "uint")) {
			console.log("Unknown dataType. Acceptable dataType are `int` or `uint`!");
		} else if (!(referenceType == "htmlId" || referenceType == "object")) {
			console.log("Unknown referenceType. Acceptable referenceType are `htmlId` or `object`!");
		}
		else {
			var arrayItem = ModbusRequestContainer.getIsRegisterInited(this, decimalAddress);
			if(arrayItem < 0) {
				arrayItem = this.valuesCollection.length;
				if(decimalAddress == "time") {
					this.requestList.push({action: "get", what: "int", id: "time"});
					if(this.clockMode == 24)
						this.valuesCollection.push({"value": "1970.01.01 00:00:00", "objectsIds": [], "refType": referenceType});
					else if (this.clockMode == 12)
						this.valuesCollection.push({"value": "1970.01.01 12:00:00 AM", "objectsIds": [], "refType": referenceType});
					else
						console.log("Clock mode(" + String(this.clockMode) + ") is not valid!!!")
				}
				else {
					this.requestList.push({"action": "get", "what": "mbt", "id": String(decimalAddress), "type": dataType});
					this.valuesCollection.push({"value": null, "objectsIds": [], "bitNumbers": new Array(), "callback": callback, "refType": referenceType});
				}
				if(bitNumber == "all")
					this.valuesCollection[arrayItem].objectsIds.push(objectBinding);
			}
			if((Math.round(bitNumber) >= 0) && (Math.round(bitNumber) <= 15)) {
				this.valuesCollection[arrayItem].objectsIds.push(objectBinding);
				this.valuesCollection[arrayItem].bitNumbers.push(Math.round(bitNumber));
			}
			else if(bitNumber != "all") {
				console.log("BitNumber out of range! Proper value is <0; 15> or all: " + String(bitNumber));
			}
		}
	}

	/**
	 * Removes (from valuesCollection) Modbus register, which was inited before 
	 * @param {Integer} decimalAddress 	Modbus register address in range <1000, 2999>
	 * @param {Integer} length			Count of registers addresses after decimalAddress to remove 
	 */
	removeRegister(decimalAddress, length=1, objectBinding=null) {
		if(decimalAddress != null) {
			var arrayItem;
			var log = "";
			if((decimalAddress < 1000 || decimalAddress > 2999) && decimalAddress != "time") {
				console.log("Modbus register address: " + String(decimalAddress) + " is out of range <1000; 2999>");
			} 
			else if((decimalAddress + length - 1) > 2999) {
				console.log("Last Modbus register addresses: " + String(decimalAddress + length - 1) + " is out of range <1000; 2999>");
			} 
			else {
				for(var i = 0; i < length; i++) {
					arrayItem = ModbusRequestContainer.getIsRegisterInited(this, decimalAddress + i);
					if(arrayItem >= 0 && this.valuesCollection[i] != undefined) {
						for(var j = 0; j < this.valuesCollection[i].objectsIds.length; j++) {
							if(this.valuesCollection[i].objectsIds[j] == objectBinding) {
								this.valuesCollection[i].objectsIds.splice(objectBinding, 1);
								break;
							}
						}
						if((objectBinding == null) || (this.valuesCollection[i].objectsIds.length == 0)) {
							this.valuesCollection.splice(arrayItem, 1);
							this.requestList.splice(arrayItem, 1);
						}
					}
					else
						log = log + String(decimalAddress + length - 1) + ", ";
				}
			}
		}
	}

	/**
	 * Sends AJAX request to the device
	 * @param {Array(Collection)} actionsArray 		Array of action collection - it is array of requests
	 * @param {Function} callBackFunction 			Function callback - it is needed if request has to be repeated frequently
	 * @param {Integer} repeatFrequency 			Time in ms - it is needed if request has to be repeated frequently
	 * @param {String} errorConsoleText 			String text, wich will be printed in console if request has an error
	 * @param {String} successConsoleText 			String text, wich will be printed in console if request was succesfull
	 * @param {Boolean} asyncMode 					Async value of AJAX query, as a default it is false
	 */
	static sendRequestToDevice(actionsArray, callBackFunction, repeatFrequency, errorConsoleText, successConsoleText, asyncMode=false) {
		//read all points in this polling configuration
		$.ajax({
			url:'do',
			type:'POST',
			data: {
				actions: actionsArray
			},
			success: function(res) {
				if(successConsoleText != undefined && successConsoleText != null) {
					//on success print predefined text in console
					console.log(successConsoleText);
				}	
				if(repeatFrequency != undefined && repeatFrequency != null && callBackFunction != undefined && callBackFunction != null) {
					//on success retry after defined time
					setTimeout(callBackFunction, repeatFrequency);	
				}
				return res;
			},//end success
			error : function() {
				if(errorConsoleText != undefined && errorConsoleText != null) {
					//on error print predefined text in console
					console.log(errorConsoleText);
				}
				if(repeatFrequency != undefined && repeatFrequency != null && callBackFunction != undefined && callBackFunction != null) {
					//on error retry after defined time
					setTimeout(callBackFunction, repeatFrequency);	
				}
			},//end error
			async: asyncMode
		});//end AJAX
	}
	
	/**
	 * Sends value over Modbus TCP on the AAC20 
	 * @param {Integer} decimalAddress 	Decimal address of the Modbus Register
	 * @param {Integer} value 			Value, which has to be sent
	 */
	static sendValueOverModbusTcp(decimalAddress, value) {
		//send value over Modbus TCPv
		value = Math.round(value);
		
		Debug.traceTable(["value", "decimalAddress"], [value, decimalAddress], "Send value " + String(value) + " into Holding Register " + String(decimalAddress));
		
		ModbusRequestContainer.sendRequestToDevice([{action: "set", what: "mbt", id: decimalAddress, val: value}], null, null, "Send value " + String(value) + " into Holding Register " + String(decimalAddress) + " error", null, true);
	}

	/**
	 * Sends multiple values over Modbus TCP on the AAC20 
	 * @param {Array(Integer)} decimalAddresses 	Array of decimal address of the Modbus Register
	 * @param {Array(Integer)} values 				Array of values, which have to be sent values will be send to decimal addreses in array index of both valiables
	 * @returns {Boolean}							False if lenght of both Arrays is different; True if not
	 */
	static sendValuesOverModbusTcp(decimalAddresses, values) {
		//send value over Modbus TCPv
		if(decimalAddresses.length != values.length) {
			Debug.traceLog("Lengths of decimalAddresses and values are different - action rejected", "Wrong arguments");
			return false;
		}
		var request = new Array();
		console.log("Multiple registers writing start");
		for(var i=0; i<decimalAddresses.length; i++) {
			request.push({action: "set", what: "mbt", id: decimalAddresses[i], val: values[i]});
		}
		Debug.traceTable(["value", "decimalAddress"], [values, decimalAddresses], "Send " + String(decimalAddresses.length) + "x values into Holding Registers");
		ModbusRequestContainer.sendRequestToDevice(request, null, null, "Sending " + String(decimalAddresses.length) + "x values into Holding Registers error", null, true);
		console.log("Multiple registers writing stop");
		return true;
	}

	/**
	 * Sets pollMode value
	 * @param {Boolean} value 	If true, then enabled pollingMode, if false then disabling it
	 * @returns {Boolean}		True if pollMode was changed, false if wasn't changed
	 */
	setPollMode(value) {
		if(value == undefined || value == null) {
			console.log("Undefined or null value in argument of ModbusRequestContainer.setPollMode method");
			return false;
		}
		else {
			this.pollMode = (value == false || value == 0) ? false : true;
			return true;
		}
	}
	
	/**
	 * Method need to be invoked if AAC20 response on the request. It decodes $.ajax resource clollection, and then save it internal registers 
	 * @param {Collection} element 	Collection gets from AAC20 represents data of the single Modbus Register
	 */
	setRegisterValue(element) {
		for(var i = 0; i < this.requestList.length; i++) {
			if(this.requestList[i].id == element.id && this.valuesCollection[i].value != element.val) {
				this.valuesCollection[i].value = element.val;
				if(this.valuesCollection[i].objectsIds.length == 0)
					console.log("Holding Regiser " + String(this.requestList[i].id) + " doesn't have defined html object ID!");
				else {
					if(this.valuesCollection[i].refType == "htmlId") {
						if(this.valuesCollection[i].bitNumbers == undefined) { //Time
							var strTemporary = String(element.val);
							var date = strTemporary.substring(0, 11);
							var hour = Math.trunc(strTemporary.substring(11, 13));
							var minute = Math.trunc(strTemporary.substring(14, 16));
							var second = strTemporary.substring(17, 19);
							var dateTimeString = date + BasicContainer.convertIntTimeToString(hour * 60 + minute, second, this.clockMode);
						
							for(var j = 0; j < this.valuesCollection[i].objectsIds.length; j++)
								this.valuesCollection[i].objectsIds[j].setTextValue(dateTimeString);
						}
						else if(this.valuesCollection[i].bitNumbers.length == 0) { //Numeric point or Numeric setpoint
							var objPointer = this.valuesCollection[i].objectsIds[0];
							objPointer.setValue(parseFloat(element.val) * objPointer.getScale());
							objPointer.setTextValue();
						}
						else if(this.valuesCollection[i].bitNumbers.length > 0) { //Boolean point or Boolean setpoint
							for(var j = 0; j < this.valuesCollection[i].objectsIds.length; j++) {
								this.valuesCollection[i].objectsIds[j].setState(BooleanPoint.readBitValue(element.val, this.valuesCollection[i].objectsIds[j].bitNumber));
							}
						}
					}
					else if(this.valuesCollection[i].refType == "object") {
						if(this.valuesCollection[i].bitNumbers.length == 0) {	//16-bit Integer value
							this.valuesCollection[i].objectsIds[0].value = element.val;
						}
						else if(this.valuesCollection[i].bitNumbers.length > 0) {	//boolean value in 16-bits register
							for(var j = 0; j < this.valuesCollection[i].objectsIds.length; j++) {
								this.valuesCollection[i].objectsIds[j].value = BooleanPoint.readBitValue(element.val, this.valuesCollection[i].objectsIds[j].bitNumber);
							}
						}
					}
					if(this.valuesCollection[i].callback != null)
						this.valuesCollection[i].callback();
				}
			}
		}		
	}
}