package infrared;

import com.sun.jna.Platform;

import anywheresoftware.b4a.BA;
import jGPIO.mycodes;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.io.BufferedReader;
@BA.ShortName("InfraredReceiver")
@BA.Events(values= {
		"log(msg as string)",
		"newvalue(value as int)",
		"rawbytes(bb() as byte)"
})

public class InfraredReceiver {
	
	
	private BA ba;
	private String event;
	private Object Me;
	private boolean inited = false;
	private boolean need_log_event = false;
	private boolean need_newvalue_event = false;
	private boolean need_rawbytes_event = false;
	
	private String infrared_event0 = null;
	private boolean continue_reading = false;
	
	public void Initialize(BA ba, String eventname) {
		this.ba = ba;
		this.event = eventname;
		Me = this;
		check_events();
		check_device();
		Runtime.getRuntime().addShutdownHook(new Thread() {
			public void run() {
				System.out.println("InfraredReceiver ShutdownHook started");
				close();
			}
		});
	}
	
	/**
	 * Open infrared device with specific protocol
	 * Protocol options : nec, jvc, sony, sanyo, sharp, xmp, lirc, rc-5, rc-6, rc-5-sz
	 * If dont know, use "all" for protocol for testing, but will not raise event newvalue
	 * @param protocol : Specific protocol decoding
	 * @return true if success
	 */
	public boolean open(final String protocol) {
		if (mycodes.ValidString(protocol)) {
			if (inited) {
				try {
					FileInputStream buf = new FileInputStream(infrared_event0);
					Thread tt = new Thread(new InputEventReading(protocol,buf));
					tt.start();
					return true;
				} catch (FileNotFoundException | SecurityException e) {
					raise_log("Open failed, exception = "+e.getMessage());
				}
			} else raise_log("Open failed, must call Initialize first");
		} else raise_log("Open failed, protocol invalid");
		return false;
	}
	
	public void close() {
		continue_reading = false;
	}
	
	public boolean IsInitialized() {
		return inited;
	}
	
	private void check_device() {
		inited = false;
		infrared_event0 = null;
		final String major_start = "MAJOR=";
		final String minor_start = "MINOR=";
		final String devname_start = "DEVNAME=";
		String major = null;
		String minor = null;
		String devname = null;
		if (Platform.isLinux()) {
			File rc0 = new File("/sys/class/rc/rc0");
			if (rc0.isDirectory()) {
				File uevent_event0 = new File(rc0, "input0/event0/uevent");
				if (uevent_event0.isFile()) {
					try(BufferedReader br = new BufferedReader(new FileReader(uevent_event0))) {
						;
						String xx = null;
						do {
							xx = br.readLine();
							if (xx!=null) {
								if (xx.startsWith(major_start)) {
									major = xx.substring(major_start.length());
								} else if (xx.startsWith(minor_start)) {
									minor = xx.substring(minor_start.length());
								} else if (xx.startsWith(devname_start)) {
									devname = xx.substring(devname_start.length());
								}
							}
						} while (xx!=null);
					} catch (IOException e) {
						raise_log("InfraredReader unable to read inputx/eventx/uevent");
					}
					
					if (mycodes.ValidString(major)) {
						if (mycodes.ValidString(minor)) {
							if (mycodes.ValidString(devname)) {
								inited = true;
								infrared_event0 = "/dev/"+devname;
								raise_log(MessageFormat.format("Infrared device found at {0}", infrared_event0));
							}
						}
					}
				} else raise_log("InfraredReceiver not finding inputx/eventx/uevent");
			} else raise_log("InfraredReceiver not finding possible device");
		} else raise_log("InfraredReceiver can only used in Linux based");
	}
	
	private void check_events() {
		if (ba!=null) {
			if (mycodes.ValidString(event)) {
				need_log_event = ba.subExists(event+"_log");
				need_newvalue_event = ba.subExists(event+"_newvalue");
				need_rawbytes_event = ba.subExists(event+"_rawbytes");
			}
		}
	}
	
	private void raise_log(String msg) {
		if (need_log_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {msg});
	}
	
	private void raise_newvalue(int value) {
		if (need_newvalue_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_newvalue", false, new Object[] {value});
	}
	
	private void raise_rawbytes(byte[] bb) {
		if (need_rawbytes_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_rawbytes", false, new Object[] {bb});
	}
	
	private class InputEventReading implements Runnable{
		private final FileInputStream reader;
		private final String protocol;
		public InputEventReading(String protocol, FileInputStream br) {
			this.reader = br;
			this.protocol = protocol;
			if (this.reader!=null && mycodes.ValidString(protocol)) {
				continue_reading = true;
			}
		}
		@Override
		public void run() {
			raise_log("InputEventReading started");
			while(continue_reading) {
				if (reader==null) break;
				byte[] xx = new byte[100];
				try {
					int result = reader.read(xx);
					if (result>0) {
						byte[] raw = Arrays.copyOf(xx, result);
						raise_rawbytes(raw);
						raise_log(MessageFormat.format("Length = {0}, Hexadecimals = {1}", result, mycodes.Bytes_toHex(raw, " ")));
						if (protocol.equalsIgnoreCase("nec")) {
							if (result==32 && raw.length==result) {
								// scancode ada di byte 12 dan 13, dalam fomat little endian
								int value = raw[13] & 0xFF;
								value <<= 8;
								value |= (raw[12] & 0xFF);
								raise_newvalue(value);
							}
						}
						
					}
				} catch (IOException e) {
					raise_log("Reader exception  ="+e.getMessage());
				}
				
				
			}
			raise_log("InputEventReading finished");
			if (reader!=null) {
				try {
					reader.close();
					raise_log("Reader closed");
				} catch (IOException e) {
					raise_log("Reader Close exception = "+e.getMessage());
				}
			}
		}
		
	}
}
