package devices.PZEM004;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import anywheresoftware.b4a.BA;
import anywheresoftware.b4a.keywords.Bit;
import jGPIO.SerialPort.SerialComm;
import jGPIO.SerialPort.jSerialPort_Event;

@BA.ShortName("PZEM004_V3")
@BA.Events(values= {
		"log(msg as string)",
		"measurement(value as PZEM004_MeasurenmentData)",
})

@SuppressWarnings("unused")
public class PZEM004 implements jSerialPort_Event {
	private BA bax;
	private Object caller;
	private String event;
	private final Object Me = this;
	private boolean need_log_event = false;
	private boolean need_measurement_event = false;
	
	private SerialComm sercom;
	private boolean inited = false;
	
	private ExecutorService executor = Executors.newSingleThreadExecutor();
	

	private int txcount = 0;
	private int txfail = 0;
	private int rxcount = 0;
	private int rxfail = 0;
	
	private final byte[] address = {(byte)0xC0, (byte)0xA8, (byte)0x1, (byte)0x1, (byte)0};
	private final byte[] read_voltage_cmd = {(byte)0xB0, (byte)0x1A};
	
	private final byte[] result_voltage_cmd = {(byte)0xA0, (byte) 0x88};
	private final byte[] read_current_cmd = {(byte)0xB1, (byte) 0x1B};
	private final byte[] result_current_cmd = {(byte) 0xA1, (byte) 0xD2};
	private final byte[] read_power_cmd = {(byte) 0xB2, (byte)0x1C};
	private final byte[] result_power_cmd = {(byte) 0xA2, (byte) 0x42};
	private final byte[] read_energy_cmd = {(byte) 0xB3, (byte) 0x1D};
	private final byte[] result_energy_cmd = {(byte) 0xA3, (byte) 0xC9};
	private final byte[] set_address_cmd = {(byte) 0xB4, (byte) 0x1E};
	
	
	
	/**
	 * Initialize PZEM-004 class
	 * @param callerobject : callback object
	 * @param eventname : event name
	 */
	public void Initialize(BA ba, Object callerobject, String eventname) {
		bax = ba;
		caller = callerobject;
		event = eventname.trim();
		
		if (bax!=null) {
			if (caller!=null) {
				if (!event.isEmpty()) {
					if (bax.subExists(event+"_log")) need_log_event = true;
					if (bax.subExists(event+"_measurement")) need_measurement_event = true;
					
				}
			}
		}
		
		 Runtime.getRuntime().addShutdownHook(new Thread() {
	            @Override
	            public void run() {
	                Close();
	            }
	        });
		 
		inited = true;
	}
	
	public void Close() {
		if (executor!=null) {
			executor.shutdown();
		}
		executor = null;
		if (sercom!=null) {
			sercom.ClosePort();
		}
		sercom = null;
	}
	
	/**
	 * Check if class already initalized
	 * @return true if initialized
	 */
	public boolean IsInitialized() {
		return inited;
	}
	
	/**
	 * Set SerialComm for communication
	 * targetcom must be initialized and opened before
	 * @param targetcom : SerialComm object
	 * @return true if targetcom is initialized and opened
	 */
	public boolean SetSerialComm(SerialComm targetcom) {
		if (targetcom!=null) {
			if (targetcom.PortIsReady()) {
				sercom = targetcom;
				// untuk dapetin event log dan newdata
				sercom.SetJavaEvent(this);
				
				byte[] cmd = make_command(set_address_cmd, address);
				sercom.Write(cmd);
				printbytes("Initial address : ",cmd);
				return true;
			}
		}
		return false;
	}
	
	public void Read_Measurement() {
		
		executor.execute(new serthread());
		
		
	}
	
	private byte[] make_command(byte[] cmd, byte[] address) {
		byte[] result = new byte[cmd.length+address.length];
		int length = result.length;
		
		result[0] = cmd[0];
		result[length-1] = cmd[1];
		for(int ii=0;ii<address.length;ii++) result[1+ii] = address[ii];
		return result;
	}
	
	private class serthread implements Runnable {

		@Override
		public void run() {
			PZEM004_MeasurementData result = new PZEM004_MeasurementData();
			result.voltage = read_voltage();
			result.current = read_current();
			result.power = read_power();
			result.energy = read_energy();
			
			raise_measurement(result);
		}
		
		private boolean check_sercom() {
			if (sercom!=null) {
				if (sercom.PortIsReady()) {
					return true;
				}
			}
			return false;
		}
		
		
		
		private double read_voltage() {
			if (check_sercom()) {
				byte[] cmd = make_command(read_voltage_cmd, address);
				sercom.Write(cmd);
				printbytes("read voltage : ",cmd);
				if (txcount< Integer.MAX_VALUE) txcount++; else txcount = 0;
			} else {
				if (txfail < Integer.MAX_VALUE) txfail++; else txfail = 0;
			}
			return -1;
		}
		
		private double read_current() {
			if (check_sercom()) {
				byte[] cmd = make_command(read_current_cmd,address);
				sercom.Write(cmd);
				printbytes("read current : ",cmd);
				if (txcount< Integer.MAX_VALUE) txcount++; else txcount = 0;
			}else {
				if (txfail < Integer.MAX_VALUE) txfail++; else txfail = 0;
			}
			return -1;
		}
		
		private double read_power() {
			if (check_sercom()) {
				byte[] cmd = make_command(read_power_cmd, address);
				sercom.Write(cmd);
				printbytes("read power : ",cmd);
				if (txcount< Integer.MAX_VALUE) txcount++; else txcount = 0;
			}else {
				if (txfail < Integer.MAX_VALUE) txfail++; else txfail = 0;
			}
			return -1;
		}
		
		private double read_energy() {
			if (check_sercom()) {
				byte[] cmd = make_command(read_energy_cmd,address);
				sercom.Write(cmd);
				printbytes("read energy : ",cmd);
				if (txcount< Integer.MAX_VALUE) txcount++; else txcount = 0;
			}else {
				if (txfail < Integer.MAX_VALUE) txfail++; else txfail = 0;
			}
			return -1;
		}
	}
	
	
	private void raise_log(String msg) {
		if (need_log_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {msg});
	}

	private void raise_measurement(PZEM004_MeasurementData value) {
		if (need_measurement_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_measurement", false, new Object[] {value});
	}
	
	private void printbytes(String prefix, byte[] bb) {
		StringBuilder str = new StringBuilder();
		str.append(prefix).append(" ");
		for(byte xx: bb) {
			str.append(Bit.ToHexString(xx & 0xFF)).append(" ");
		}
		BA.Log(str.toString());
	}
	
	/////// from SerialComm event
	
	@Override
	public void Log(String msg) {
		BA.Log(msg);
		
	}

	@Override
	public void newdata(byte[] bb) {
		
		printbytes("new data : ",bb);
	}
}
