package gps;

import java.util.ArrayList;
import java.util.List;

import com.fazecast.jSerialComm.SerialPort;
import com.fazecast.jSerialComm.SerialPortEvent;
import com.fazecast.jSerialComm.SerialPortInvalidPortException;
import com.fazecast.jSerialComm.SerialPortMessageListener;

import anywheresoftware.b4a.BA;

@BA.ShortName("GpsReceiver")
@BA.Events(values= {
		"log(msg as string)",
		"newgpstime(value as string)",
		"newgpsdate(value as string)",
		"newgpslatitude(value as double)",
		"newgpslongitude(value as double)",
		
		"rawdata(gpsmsg as string)",
		"ggadata(data as GpsGPGGA)",
		"glldata(data as GpsGPGLL)",
//		"gsadata(data as GpsGPGSA)",
//		"vtgdata(data as GpsGPVTG)",
		"rmcdata(data as GpsGPRMC)"
})

//@BA.DependsOn(values = { "jSerialComm-2.6.2" })

public class GpsReceiver {
	
	private BA bax;
	private String event = "";
	private Object caller;
	private final Object Me = this;
	private boolean need_log_event = false;
	private boolean need_rawdata_event = false;
	private boolean need_ggadata_event = false;
	private boolean need_newgpstime_event = false;
	private boolean need_newgpsdate_event = false;
	private boolean need_newgpslatitude_event = false;
	private boolean need_newgpslongitude_event = false;
	
	private boolean need_glldata_event = false;
	//private boolean need_gsadata_event = false;		// tidak terlalu penting
	private boolean need_rmcdata_event = false;
	//private boolean need_vtgdata_event = false;		// tidak terlalu penting
	
	private SerialPort myport;
	private boolean initialized = false;
	private long rxcount = 0;
	
	private String last_gps_time = "";
	private String last_gps_date = "";
	private double last_gps_latitude = 0;
	private double last_gps_longitude = 0;
	private boolean gga_fixed = false;
	private boolean gll_fixed = false;
	private boolean rmc_fixed = false;
	private int last_gps_day = 0;
	private int last_gps_month = 0;
	private int last_gps_year = 0;
	private int last_gps_hour = 0;
	private int last_gps_minute = 0;
	private int last_gps_second = 0;
	
	
	/**
	 * Initialize GpsReceiver
	 * @param eventname : eventname string
	 */
	public void Initialize(BA ba, Object caller, String eventname ) {
		bax = ba;
		event = eventname;
		this.caller = caller;
		
		if (bax!=null) {
			if (this.caller!=null) {
				if (!event.isEmpty()) {
					need_log_event = bax.subExists(event+"_log");
					need_rawdata_event = bax.subExists(event+"_rawdata");
					need_ggadata_event = bax.subExists(event+"_ggadata");
					
					need_glldata_event = bax.subExists(event+"_glldata");
					//need_gsadata_event = bax.subExists(event+"_gsadata");
					need_rmcdata_event = bax.subExists(event+"_rmcdata");
					//need_vtgdata_event = bax.subExists(event+"_vtgdata");
					
					need_newgpstime_event = bax.subExists(event+"_newgpstime");
					need_newgpsdate_event = bax.subExists(event+"_newgpsdate");
					need_newgpslatitude_event = bax.subExists(event+"_newgpslatitude");
					need_newgpslongitude_event = bax.subExists(event+"_newgpslongitude");
				}
			}
		}
		
		
		if (SerialPort.getVersion()!="") initialized = true; else initialized = false;
		
		 Runtime.getRuntime().addShutdownHook(new Thread() {
	            @Override
	            public void run() {
	                ClosePort();
	            }
	        });
	}
	
	
	
	/**
	 * Check if GpsReceiver is initialized
	 * @return true if initialized
	 */
	public boolean IsInitialized() {
		return initialized;
	}
	
	/**
	 * Get all available port names in this system
	 * @return List of Serial Port Names
	 */
	public List<String> GetPortNames(){
		List<String> result = new ArrayList<String>();
		if (initialized) {
			for(SerialPort xx:SerialPort.getCommPorts()) {
				if (xx != null) {
					String portname = xx.getSystemPortName();
					if (portname!="") {
						result.add(portname);
					}
				}
			}
		} else {
			raise_log("GetPortNames failed, SerialPort Library not ready");
		}
		return result;
	}
	
	/**
	 * Close GPS Receiving Port
	 */
	public void ClosePort() {
		if (myport instanceof SerialPort) {
			myport.removeDataListener();
			
			myport.closePort();
			myport = null;
		}
		
	}
	
	/**
	 * How many GPS data received
	 * @return how many gps data received
	 */
	public long getRXCounter() {
		return rxcount;
	}
	
	public double GetLatitude_rounded(int fractiondigits) {
		if (fractiondigits<1) fractiondigits = 1;
		if (fractiondigits>4) fractiondigits = 4;
		switch(fractiondigits) {
		case 1 :
			return Math.round(last_gps_latitude * 10.0) / 10.0;
		case 2 :
			return Math.round(last_gps_latitude * 100.0) / 100.0;
		case 3 :
			return Math.round(last_gps_latitude * 1000.0) / 1000.0;
		default :
			return Math.round(last_gps_latitude * 10000.0) / 10000.0;
		}
	}
	
	public double GetLongitude_rounded(int fractiondigits) {
		if (fractiondigits<1) fractiondigits = 1;
		if (fractiondigits>4) fractiondigits = 4;
		switch(fractiondigits) {
		case 1 :
			return Math.round(last_gps_longitude * 10.0) / 10.0;
		case 2 :
			return Math.round(last_gps_longitude * 100.0) / 100.0;
		case 3 :
			return Math.round(last_gps_longitude * 1000.0) / 1000.0;
		default :
			return Math.round(last_gps_longitude * 10000.0) / 10000.0;
		}
	}
	
	/**
	 * Get last GPS Latitude
	 * @return Latitude value in double
	 */
	public double getLatitude() {
		return last_gps_latitude;
	}
	
	/**
	 * Get Last GPS Longitude
	 * @return Longitude value in double
	 */
	public double getLongitude() {
		return last_gps_longitude;
	}
	
	/**
	 * Get Last GPS Time
	 * @return time in format hh:mm:ss
	 */
	public String getTime_hhmmss() {
		return last_gps_time;
	}
	
	/**
	 * Get Last GPS Date
	 * @return date in format dd-MM-yyyy
	 */
	public String getDate_ddMMyyyy() {
		return last_gps_date;
	}
	
	/**
	 * Get GPS Fixed status
	 * @return true if GPS is fixed
	 */
	public boolean getGPS_Fixed() {
		return (gga_fixed || gll_fixed || rmc_fixed);
	}
	
	public int getUTC_Day() {
		return last_gps_day;
	}
	
	public int getUTC_Month() {
		return last_gps_month;
	}
	
	public int getUTC_Year() {
		return last_gps_year;
	}
	
	public int getUTC_Hour() {
		return last_gps_hour;
	}
	
	public int getUTC_Minute() {
		return last_gps_minute;
	}
	
	public int getUTC_Second() {
		return last_gps_second;
	}
	
	private void setUTC_Time(int hour, int minute, int second) {
		last_gps_hour = hour;
		last_gps_minute = minute;
		last_gps_second = second;
	}
	
	private void setUTC_Date(int day, int month, int year) {
		last_gps_day = day;
		last_gps_month = month;
		last_gps_year = year;
	}
	
	/**
	 * Open Serial Port for Gps Communication
	 * @param serialname : serialport name
	 * @param baudrate : default is 9600
	 * @return true if success
	 */
	public boolean OpenPort(String serialname, int baudrate) {
		rxcount = 0;
		if (baudrate==0) baudrate = 9600;	// default baudrate
		if (initialized) {
			try {
				myport = SerialPort.getCommPort(serialname);
				if (myport.openPort()) {
					myport.setBaudRate(baudrate);
					if (myport.addDataListener(new SerialPortMessageListener() {

						@Override
						public int getListeningEvents() {
							return SerialPort.LISTENING_EVENT_DATA_RECEIVED;
						}

						@Override
						public void serialEvent(SerialPortEvent arg0) {
							if (arg0==null) return;
							byte[] bb = arg0.getReceivedData();
							if (bb==null) return;
							if (bb.length<1) return;
							String msg = new String(bb);
							
							int indexmulai = msg.indexOf("$GP");
							if (indexmulai<0) return;
							
							String vv = msg.substring(indexmulai);
							if (vv.startsWith("$GPGSV")) return;		// ignore GPGSV
							raise_rawdata(vv);
							process_data(vv);
							rxcount+=1;
							
						}

						@Override
						public boolean delimiterIndicatesEndOfMessage() {
							// delimiter ada di belakang
							return true;
						}

						@Override
						public byte[] getMessageDelimiter() {
							// delimiter adalah CR LF
							return new byte[] {(byte) 13, (byte) 10};
						}
						
					}))
					return true;
				}
			} catch(SerialPortInvalidPortException e) {
				raise_exception("Failed to GetCommPort",e);
			}
		}
		
		return false;	
	}
	
	/**
	 * Check if SerialPort is opened and ready
	 * @return true if opened and ready
	 */
	public boolean PortIsReady() {
		if (myport instanceof SerialPort) {
			return myport.isOpen();
		}
		return false;
	}
	
	private void process_data(String vv) {
		String[] gps_split = vv.split("\\*");
		if (gps_split.length!=2) return;
		String gpsmsg = gps_split[0];
		
		if (gpsmsg.startsWith("$GPGGA")) {
			GpsGPGGA datagga = new GpsGPGGA(gpsmsg);	
			// supaya kalau udah fix, ada UTC Latitude, Longitude, UTC Time , dah bisa raise event
			gga_fixed = datagga.PositionFixed;
			if (gga_fixed) 
			{
				raise_ggadata(datagga);
				String time = datagga.UTC_Time_hhmmss();
				if (last_gps_time.compareTo(time)!=0) {
					last_gps_time = time;
					setUTC_Time(datagga.UTC_Hour, datagga.UTC_Minute, datagga.UTC_Second);
					raise_newgpstime(last_gps_time);
				}
				if (last_gps_latitude != datagga.Latitude) {
					last_gps_latitude = datagga.Latitude;
					raise_newgpslatitude(last_gps_latitude);
				}
				if (last_gps_longitude != datagga.Longitude) {
					last_gps_longitude = datagga.Longitude;
					raise_newgpslongitude(last_gps_longitude);
				}
			} 
			return;
		}

		// ini tidak perlu, gak penting
		/*
		 * if (gpsmsg.startsWith("$GPGSA")) { GpsGPGSA datagsa = new GpsGPGSA(gpsmsg);
		 * if (datagsa.isValid()) { raise_gsadata(datagsa); } else {
		 * BA.Log(datagsa.invalidmsg); } return; }
		 */

		if (gpsmsg.startsWith("$GPGLL")) {
			GpsGPGLL datagll = new GpsGPGLL(gpsmsg);
			// supaya kalau udah fix, ada UTC Latitude, Longitude, UTC Time, dah bisa raise event
			gll_fixed = datagll.PositionFixed;
			if (gll_fixed) 
			{
				raise_glldata(datagll);
				String time = datagll.UTC_Time_hhmmss();
				if (last_gps_time.compareTo(time)!=0) {
					last_gps_time = time;
					setUTC_Time(datagll.UTC_Hour, datagll.UTC_Minute, datagll.UTC_Second);
					raise_newgpstime(last_gps_time);
				}
				if (last_gps_latitude != datagll.Latitude) {
					last_gps_latitude = datagll.Latitude;
					raise_newgpslatitude(last_gps_latitude);
				}
				if (last_gps_longitude != datagll.Longitude) {
					last_gps_longitude = datagll.Longitude;
					raise_newgpslongitude(last_gps_longitude);
				}
			} 
			return;
		}

		if (gpsmsg.startsWith("$GPRMC")) {
			GpsGPRMC datarmc = new GpsGPRMC(gpsmsg);
			// GPRMC Groud Course, Magnetic Variation, dan Magnetic Polarity sering loss
			// jadi tidak pakai isValid, tapi pakai PositionFix aja
			rmc_fixed = datarmc.PositionFix;
			if (rmc_fixed) 
			{
				raise_rmcdata(datarmc);
				String time = datarmc.UTC_Time_hhmmss();
				if (last_gps_time.compareTo(time)!=0) {
					last_gps_time = time;
					setUTC_Time(datarmc.UTC_Hour, datarmc.UTC_Minute, datarmc.UTC_Second);
					raise_newgpstime(last_gps_time);
				}
				
				String date = datarmc.UTC_Date_ddMMyyyy();
				if (last_gps_date.compareTo(date)!=0) {
					last_gps_date = date;
					setUTC_Date(datarmc.UTC_Day, datarmc.UTC_Month, datarmc.UTC_Year);
					raise_newgpsdate(last_gps_date);
				}
				if (last_gps_latitude != datarmc.Latitude) {
					last_gps_latitude = datarmc.Latitude;
					raise_newgpslatitude(last_gps_latitude);
				}
				if (last_gps_longitude != datarmc.Longitude) {
					last_gps_longitude = datarmc.Longitude;
					raise_newgpslongitude(last_gps_longitude);
				}
			} 
			return;
		}
		
		// ini tidak perlu, gak penting
		/*
		 * if (gpsmsg.startsWith("$GPVTG")) { GpsGPVTG datavtg = new GpsGPVTG(gpsmsg);
		 * if (datavtg.isValid()) { raise_vtgdata(datavtg); } else {
		 * BA.Log(datavtg.invalidmsg); } return; }
		 */
	}
	
	private void raise_exception(String prefix, Exception e) {
		raise_log(prefix+", Msg : "+e.getMessage()+", Caused : "+e.getCause());
	}
	
	private void raise_log(String msg) {
		if (need_log_event) bax.raiseEventFromDifferentThread(Me,  null, 0, event+"_log", false, new Object[] {msg});
	}
	
	private void raise_rawdata(String vv) {
		if (need_rawdata_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_rawdata", false, new Object[] {vv});
	}
	
	private void raise_ggadata(GpsGPGGA vv) {
		if (need_ggadata_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_ggadata", false, new Object[] {vv});
	}
	
	
	
	private void raise_glldata(GpsGPGLL vv) {
		if (need_glldata_event) bax.raiseEventFromDifferentThread(Me,  null, 0, event+"_glldata", false, new Object[] {vv});
	}
	
	
	
	private void raise_rmcdata(GpsGPRMC vv) {
		if (need_rmcdata_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_rmcdata", false, new Object[] {vv});
	}
	
	
	
	private void raise_newgpstime(String value) {
		if (need_newgpstime_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_newgpstime", false, new Object[] {value});
	}
	
	private void raise_newgpsdate(String value) {
		if (need_newgpsdate_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_newgpsdate", false, new Object[] {value});
	}
	
	private void raise_newgpslatitude(double value) {
		if (need_newgpslatitude_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_newgpslatitude", false, new Object[] {value});
	}
	
	private void raise_newgpslongitude(double value) {
		if (need_newgpslongitude_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_newgpslongitude", false, new Object[] {value});
	}
}
