package syslog;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

import java.net.SocketException;


import anywheresoftware.b4a.BA;
import anywheresoftware.b4a.objects.collections.Map;

@BA.ShortName("SyslogServer")
@BA.Events(values= {
		"log(msg as string)",
		"serverstatus(running as boolean)",
		"newdata(fromip as string, fromport as int, bb() as byte)"
})

/**
 * Syslog Server
 * By default, Syslog will listen at UDP Port 514
 * @author rdkartono
 *
 */
public class SyslogServer {

	private BA ba;
	private Object caller;
	private String event;
	private final Object Me = this;
	private boolean need_log_event = false;
	private boolean need_serverstatus_event = false;
	private boolean need_newdata_event = false;
	private int syslogport = 514;
	private boolean listening = false;
	private Map plugmap = new Map();
	
	/**
	 * Initialize Syslog Server
	 * @param callerobject : callback object
	 * @param eventname : event name
	 */
	public void Initialize(BA bax, Object callerobject, String eventname) {
		ba = bax;
		caller = callerobject;
		event = eventname.trim();
		plugmap.Initialize();
		if (ba instanceof BA) {
			if (caller != null) {
				if (!event.isEmpty()) {
					need_log_event = ba.subExists(event+"_log");
					need_serverstatus_event = ba.subExists(event+"_serverstatus");
					need_newdata_event = ba.subExists(event+"_newdata");
				}
			}
		}
	}
	
	/**
	 * Open Syslog Server 
	 * Will raise serverstatus event
	 * @param port : valid port number (1 - 65535), or default to 514
	 * @return true if success
	 */
	public boolean Open(int port) {
		if (port>0) {
			if (port < 65535) {
				syslogport = port;
			}
		}
		
		Close();
		
		try {
			DatagramSocket udp = new DatagramSocket(syslogport);
			Thread tx = new Thread(new ListeningRunnable(udp));
			tx.start();
			return true;
		} catch (SocketException | SecurityException e) {
			raise_log("Open at Port="+port+" failed, Msg : "+e.getMessage());
		}
		raise_serverstatus(false);	/// gagal
		return false;
	}
	
	private class ListeningRunnable implements Runnable {
		private final DatagramSocket udpsock;
		public ListeningRunnable(DatagramSocket sock) {
			udpsock = sock;
		}
		@Override
		public void run() {
			if (udpsock instanceof DatagramSocket) {
				listening = true;
				raise_serverstatus(true);
				while(true) {
					if (!listening) break;
					if (!(udpsock instanceof DatagramSocket)) break;
					if (udpsock.isClosed()) break;
					byte[] buf = new byte[1500];
					DatagramPacket p = new DatagramPacket(buf, buf.length);
					
					try {
						udpsock.receive(p);		// tungguin di sini sampe dapat data
						
					} catch (IOException e) {
						// gagal receive data
						raise_log("Error Receive DatagramPacket, Msg : "+e.getMessage());
						break;
					}
					
					// sampai sini, bisa receive data
					if (p instanceof DatagramPacket) {
						if (p.getLength()>0) {
							String fromip = p.getAddress().getHostAddress();
							int fromport = p.getPort();
							byte[] result = new byte[p.getLength()];
							
							// event dari SyslogServer
							raise_newdata(fromip, fromport, result);
							
							// check di plugmap
							if (plugmap instanceof Map) {
								// plugmap valid
								if (plugmap.getSize()>0) {
									// ada isinya
									for(int ii=0;ii<plugmap.getSize();ii++) {
										Object xx = plugmap.GetValueAt(ii);
										if (xx instanceof SyslogEvent) {
											SyslogEvent se = (SyslogEvent) xx;
											se.newdata(fromip, fromport, result);
										}
									}
								}
							}
						}	
					}
					
					
				}
				
				// keluar dari while , dah gak perlu DatagramSocket lagi
				if (udpsock instanceof DatagramSocket) {
					udpsock.close();
				}
				
				raise_serverstatus(false);
			} else {
				raise_serverstatus(false);
			}
		}
		
	}
	
	/**
	 * Close Syslog Server
	 */
	public void Close() {
		listening = false;
	}
	
	/**
	 * Add a syslog data consumer
	 * @param consumer : syslog consumer
	 * @param identifier : spesific identifier
	 */
	public void Add_Syslog_Consumer(Object consumer, String identifier) {
		if (consumer instanceof SyslogEvent) {
			if (plugmap instanceof Map) {
				Object prev = plugmap.Put(identifier, consumer);
				if (prev instanceof SyslogEvent) {
					raise_log(identifier+" added to Syslog_Consumer Plugmap, replacing old Syslog_Consumer");
				} else raise_log(identifier+" added to Syslog_Consumer Plugmap, as new item");
			} else raise_log("Failed to add "+identifier+" as Syslog_Consumer, Plugmap invalid");
		} else raise_log("Failed to add "+identifier+" as Syslog_Consumer");
	}
	
	/**
	 * Remove a Syslog Data Consumer
	 * @param identifier : spesific identifier
	 */
	public void Remove_Syslog_Consumer(String identifier) {
		if (plugmap instanceof Map) {
			if (plugmap.ContainsKey(identifier)) {
				plugmap.Remove(identifier);
				raise_log("Success removing "+identifier+" from Plugmap");
			} else raise_log(identifier+" not exist in Plugmap, nothing to remove");
		} else raise_log("Plugmap invalid, nothing to Remove");
	}
	
	private void raise_log(String value) {
		if (need_log_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {value});
	}

	private void raise_serverstatus(boolean value) {
		if (need_serverstatus_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_serverstatus", false, new Object[] {value});
	}
	
	
	private void raise_newdata(String fromip, int fromport, byte[] bb) {
		if (need_newdata_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_newdata", false, new Object[] {fromip, fromport, bb});
	}
}
