package jGPIO;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import java.util.List;

import anywheresoftware.b4a.BA;
import anywheresoftware.b4a.keywords.Bit;
import anywheresoftware.b4a.keywords.DateTime;

public class mycodes {
	private static final String gpiofolder = "/sys/class/gpio";
	private static final String gpioexportfolder = gpiofolder + "/export";
	private static final String gpiounexportfolder = gpiofolder + "/unexport";
	
	
	
	/**
	 * Convert Datetime Tick to Date String, on format DD-MM-YYYY
	 * @param tick : datetime tick in long
	 * @return empty string if tick less than 1
	 */
	public static String Tick_To_DDMMYYYY(long tick) {
		if (tick>0) {
			StringBuilder str = new StringBuilder();
			int temp;
			temp = DateTime.GetDayOfMonth(tick);
			if (temp<10) str.append("0");
			str.append(temp);
			str.append("-");
			
			temp = DateTime.GetMonth(tick);
			if (temp<10) str.append("0");
			str.append(temp);
			str.append("-");
			
			str.append(DateTime.GetYear(tick));
			return str.toString();
		}
		return "";
	}
	
	/**
	 * Convert Datetime Tick to Time String, on format HH:MM:SS
	 * @param tick : datetime tick in long
	 * @return empty string if tick less than 1
	 */
	public static String Tick_To_HHMMSS(long tick) {
		if (tick>0) {
			StringBuilder str = new StringBuilder();
			int temp;
			temp = DateTime.GetHour(tick);
			if (temp<10) str.append("0");
			str.append(temp);
			str.append(":");
			
			temp = DateTime.GetMinute(tick);
			if (temp<10) str.append("0");
			str.append(temp);
			str.append(":");
			
			temp = DateTime.GetSecond(tick);
			if (temp<10) str.append("0");
			str.append(temp);
			return str.toString();
		}
		return "";
	}
	
	/**
	 * Convert Datetime Tick to String , on format DD-MM-YYYY HH:MM:SS
	 * @param tick : datetime tick in long
	 * @return empty string if tick less than 1
	 */
	public static String Tick_To_DDMMYYYY_HHMMSS(long tick) {
		if (tick>0) {
			return Tick_To_DDMMYYYY(tick) + " "+ Tick_To_HHMMSS(tick);
		} else return "";
		
	}
	
	/**
	 * Check if GPIO exist
	 * 
	 * @param pinnya : pin number
	 * @return true if exist
	 */
	public static boolean GPIO_Exist(int pinnya) {

		File ff = new File(gpiofolder + "/" + GetGPIOName(pinnya));
		if (ff != null) {
			if (ff.exists())
				return true;
		}
		return false;
	}

	/**
	 * Export GPIO pin
	 * 
	 * @param pinnya : pin number
	 * @return true if success
	 * @throws IOException
	 */
	public static boolean GPIO_Export(int pinnya) throws IOException {
		File ff = new File(gpioexportfolder);
		if (ff != null) {
			if (ff.exists()) {
				if (ff.canWrite()) {
					FileOutputStream fos = new FileOutputStream(ff);
					fos.write(String.valueOf(pinnya).getBytes());
					fos.close();
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Un-Export GPIO pin
	 * 
	 * @param pinnya : pin number
	 * @return true if success
	 * @throws IOException
	 */
	public static boolean GPIO_UnExport(int pinnya) throws IOException {
		File ff = new File(gpiounexportfolder);
		if (ff != null) {
			if (ff.exists()) {
				if (ff.canWrite()) {
					FileOutputStream fos = new FileOutputStream(ff);
					fos.write(String.valueOf(pinnya).getBytes());
					fos.close();
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Get GPIO direction
	 * 
	 * @param pinnya : pin number
	 * @return empty string if failed, "in" = Input, "out" = Output
	 * @throws IOException
	 */
	public static String GPIO_GetDirection(int pinnya) throws IOException {
		File ff = new File(gpiofolder + "/" + GetGPIOName(pinnya) + "/direction");
		if (ff != null) {
			if (ff.exists()) {
				if (ff.canRead()) {

					FileInputStream fis = new FileInputStream(ff);

					byte[] buf = new byte[5];
					int xx = -1;
					int ii = 0;
					while (true) {
						xx = fis.read();
						if (xx == -1)
							break;
						if (xx == 13)
							continue; // skip CR
						if (xx == 10)
							continue; // skip LF
						buf[ii] = (byte) xx;
						if (ii < buf.length - 1)
							ii += 1;

					}
					fis.close();
					return new String(buf);
				}
			}
		}
		return "";
	}

	/**
	 * Set GPIO direction
	 * 
	 * @param pinnya   : pin number
	 * @param asOutput : true for Output pin, and false for Input pin
	 * @return true if success
	 * @throws IOException
	 */
	public static boolean GPIO_SetDirection(int pinnya, boolean asOutput) throws IOException {
		File ff = new File(gpiofolder + "/" + GetGPIOName(pinnya) + "/direction");
		if (ff != null) {
			if (ff.exists()) {
				if (ff.canWrite()) {
					FileOutputStream fos = new FileOutputStream(ff);
					if (asOutput) {
						fos.write("out".getBytes());
					} else {
						fos.write("in".getBytes());
					}
					fos.close();
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Get Value from GPIO
	 * 
	 * @param pinnya : pin number
	 * @return 0 = low, 1 = high, -1 = unknown / failed
	 * @throws IOException
	 */
	public static int GPIO_GetValue(int pinnya) throws IOException {
		int result = -1;
		File ff = new File(gpiofolder + "/" + GetGPIOName(pinnya) + "/value");
		if (ff != null) {
			if (ff.exists()) {
				if (ff.canRead()) {
					FileInputStream fis = new FileInputStream(ff);
					int vv = fis.read();
					fis.close();
					if (vv == '0') {
						result = 0;
					} else if (vv == '1') {
						result = 1;
					}

				}
			}
		}
		return result;
	}

	/**
	 * Set Value to GPIO
	 * 
	 * @param pinnya : pin number
	 * @param isON   : true = high, false = low
	 * @return true if success
	 * @throws IOException
	 */
	public static boolean GPIO_SetValue(int pinnya, boolean isON) throws IOException {
		File ff = new File(gpiofolder + "/" + GetGPIOName(pinnya) + "/value");
		if (ff != null) {
			if (ff.exists()) {
				if (ff.canWrite()) {
					FileOutputStream fos = new FileOutputStream(ff);
					if (isON) {
						fos.write('1');
					} else {
						fos.write('0');
					}
					fos.close();
					return true;
				}
			}
		}
		return false;
	}

	

	/**
	 * Translate to correct gpio name
	 * 
	 * @param pinnya : pin number
	 * @return
	 */
	private static String GetGPIOName(int pinnya) {
		return "gpio" + pinnya;
	}

	public static String printbytes(byte[] bb) {
		if (bb == null)
			return "";
		if (bb.length < 0)
			return "";
		StringBuilder result = new StringBuilder();
		for (byte xx : bb) {
			if (result.length() > 0)
				result.append(" , ");
			result.append(Bit.ToHexString(xx & 0xFF));
		}
		return result.toString();
	}

	/**
	 * Change format of GPS Latitude String for ddmm.mmmm to dd.ddddd
	 * 
	 * @param vv     : string in format ddmm.mmmm
	 * @param result : array double , length 1 , untuk passing hasilnya
	 * @return false kalau gagal
	 */
	public static boolean Gps_Latitude_formatchange(String vv, double[] result) {
		if (result == null)
			return false;
		if (result.length < 1)
			return false; // gak ada tempatnya
		if (vv.length() < 6)
			return false; // minimal ddmm.m --> 6 karakter
		if (vv.indexOf('.') != 4)
			return false; // tidak ada titik nya

		try {

			double dd = Double.parseDouble(vv.substring(0, 2)); // dd
			double mm = Double.parseDouble(vv.substring(2)); // mm.mmmm

			result[0] = Gps_Latitude_Round(dd + (mm / 60));
			return true;
		} catch (IndexOutOfBoundsException | NumberFormatException | NullPointerException e) {
			return false;
		}
	}

	/**
	 * Change format of GPS Longitude String from dddmm.mmmm to dd.dddddd
	 * 
	 * @param vv     : String in format dddmm.mmmmm
	 * @param result : array double, length 1, untuk passing hasil
	 * @return false kalau gagal
	 */
	public static boolean Gps_Longitude_formatchange(String vv, double[] result) {
		if (result == null)
			return false;
		if (result.length < 1)
			return false; // gak ada tempatnya
		if (vv.length() < 7)
			return false; // minimal dddmm.m --> 7 karakter
		if (vv.indexOf('.') != 5)
			return false; // tidak ada titiknya

		try {
			double dd = Double.parseDouble(vv.substring(0, 3)); // ddd
			double mm = Double.parseDouble(vv.substring(3)); // mm.mmmmm
			result[0] = Gps_Longitude_Round(dd + (mm / 60));
			return true;
		} catch (IndexOutOfBoundsException | NumberFormatException | NullPointerException e) {
			return false;
		}
	}

	/**
	 * Convert GPS Time String to array of byte
	 * 
	 * @param vv     : GPS time String (hhmmss.ss or hhmmss)
	 * @param result : array of byte, length = 3, {hour, minute, second}
	 * @return true if conversion success
	 */
	public static boolean Gps_UTCTime(String vv, byte[] result) {
		if (result == null)
			return false;
		if (result.length < 3)
			return false; // gak ada tempatnya
		if (vv.length() < 6)
			return false; // minimal hhmmss

		int indextitik = vv.indexOf("."); // kalau ada desimal second
		if (indextitik != -1) {
			vv = vv.substring(0, indextitik); // potong aja
		}

		try {
			result[0] = Byte.parseByte(vv.substring(0, 2));
			result[1] = Byte.parseByte(vv.substring(2, 4));
			result[2] = Byte.parseByte(vv.substring(4));
			return true;
		} catch (IndexOutOfBoundsException | NumberFormatException e) {
			BA.Log("Error getting GPS_UTCTime, Msg : " + e.getMessage() + ", Caused : " + e.getCause());
			return false;
		}
	}

	/**
	 * Convert GPS Date String to array of byte
	 * 
	 * @param vv     : GPS Date String (ddmmyy)
	 * @param result : array of byte, length = 3, {day, month, year}
	 * @return true if conversion success
	 */
	public static boolean Gps_UTCDate(String vv, byte[] result) {
		if (result == null)
			return false;
		if (result.length < 3)
			return false; // gak ada tempatnya
		if (vv.length() < 6)
			return false; // harus ddmmyy

		try {
			result[0] = Byte.parseByte(vv.substring(0, 2));
			result[1] = Byte.parseByte(vv.substring(2, 4));
			result[2] = Byte.parseByte(vv.substring(4));
			return true;
		} catch (IndexOutOfBoundsException | NumberFormatException e) {
			BA.Log("Error getting GPS_UTCDate, Msg : " + e.getMessage() + ", Caused : " + e.getCause());
			return false;
		}
	}

	/**
	 * Get Longitude Polarity
	 * 
	 * @param vv : W = negative, E = positive, other = 0
	 * @return 1 / -1 / 0
	 */
	public static int Longitude_Polarity(String vv) {
		if (vv != null) {
			if (vv.length() > 0) {
				byte[] xx = vv.getBytes();
				if (xx[0] == 87) // W
					return -1;
				else if (xx[0] == 69) // E
					return 1;
				else
					return 0; // other
			}
		}
		return 0; // invalid
	}

	/**
	 * Convert GPS String to Double
	 * 
	 * @param vv     : string in format xx.xxxx
	 * @param result : array of double, length = 1
	 * @return true if conversion success
	 */
	public static boolean Gps_Double(String vv, double[] result) {
		if (result == null)
			return false;
		if (result.length < 1)
			return false;

		try {
			double xx = Double.parseDouble(vv);
			result[0] = xx;
			return true;
		} catch (NumberFormatException e) {
			return false;
		}
	}

	/**
	 * Convert GPS String to Integer
	 * 
	 * @param vv     : String in integer
	 * @param result : array of integer, length = 1
	 * @return true if conversion success
	 */
	public static boolean Gps_Int(String vv, int[] result) {
		if (result == null)
			return false;
		if (result.length < 1)
			return false;
		try {
			int xx = Integer.parseInt(vv);
			result[0] = xx;
			return true;
		} catch (NumberFormatException e) {
			return false;
		}

	}

	/**
	 * Get first Char value from a GPS String
	 * 
	 * @param vv : GPS String
	 * @return char value
	 */
	public static char Gps_Char(String vv) {
		if (vv == null)
			return (char) 0;
		if (vv.length() < 1)
			return (char) 0;
		return vv.charAt(0);
	}

	/**
	 * Force a value become Postive
	 * 
	 * @param vv : value
	 * @return positive value from vv
	 */
	public static double SetPositive(double vv) {
		if (vv < 0)
			return -vv;
		else
			return vv;
	}

	/**
	 * Force a value become Negative
	 * 
	 * @param vv : value
	 * @return negative value from vv
	 */
	public static double SetNegative(double vv) {
		if (vv > 0)
			return -vv;
		else
			return vv;
	}

	/**
	 * Convert GPS Error List to a String, separated by CRLF
	 * 
	 * @param err : error list
	 * @return String
	 */
	public static String Gps_ErrList_to_String(List<String> err) {
		if (err == null)
			return "";
		if (err.size() < 1)
			return "";

		StringBuilder str = new StringBuilder();

		for (String xx : err) {
			if (str.length() > 0)
				str.append("\r\n");
			str.append(xx.trim());
		}

		return str.toString();
	}

	/**
	 * Make Latitude fractions to 4 digits
	 * 
	 * @param vv : Latitude in original
	 * @return Latitude in dd.dddd
	 */
	public static double Gps_Latitude_Round(double vv) {
		return Math.round(vv * 10000) / 10000.0;
	}

	/**
	 * Make Longitude fractions to 4 digits
	 * 
	 * @param vv : Longitude in original
	 * @return Longitude in ddd.dddd
	 */
	public static double Gps_Longitude_Round(double vv) {
		return Math.round(vv * 10000) / 10000.0;
	}
	
	/**
	 * Make 16bit value from 2 bytes
	 * @param Hbyte : high byte
	 * @param Lbyte : low byte
	 * @return value in 16bit
	 */
	public static int Make_Int16(byte Hbyte, byte Lbyte) {
		int result = Bit.And(Hbyte, 0xFF);
		result = result << 8;
		result = result + Bit.And(Lbyte, 0xFF);
		return result;
	}
	
	/**
	 * Low byte of a 16bit value
	 * @param value16bit : value to get
	 * @return low byte of value16bit
	 */
	public static byte LowByte(int value16bit) {
		return (byte) value16bit;
	}
	
	/**
	 * low byte of a 16bit value, in unsigned value
	 * @param value16bit  : value to get
	 * @return low byte of value16bit
	 */
	public static int LowByte_Unsigned(int value16bit) {
		return Bit.And(value16bit, 0xFF);
	}
	
	/**
	 * High byte of a 16bit value
	 * @param value16bit  value to get
	 * @return high byte of value16bit
	 */
	public static byte HighByte(int value16bit) {
		return (byte) (value16bit >> 8);
	}
	
	/**
	 * High byte of a 16bit value, in unsigned value
	 * @param value16bit : value to get
	 * @return high byte of value16bit
	 */
	public static int HighByte_Unsigned(int value16bit) {
		int result = Bit.And(value16bit, 0xFF00);
		return Bit.ShiftRight(result, 8);
	}
	
	/**
	 * Check if Value is a valid string
	 * @param vv value to check
	 * @return true if valid
	 */
	public static boolean ValidString(String vv) {
		if (vv!=null) {
			if (vv.length()>0) {
				return true;
			}
		}
		return false;
	}
	
	public static byte[] ToByteArray(int... data) {
		if (data!=null) {
			if (data.length>0) {
				byte[] result = new byte[data.length];
				for(int ii=0;ii<result.length;ii++) result[ii] = (byte)data[ii];
				return result;
			}
		}
		return null;
	}
	
	public static short To16bits(byte H, byte L) {
		int result = H & 0xFF;
		result <<= 8;
		result |= L & 0xFF;
		return (short) (result & 0xFFFF);
	}
	
	public static String ToHex(int value) {
		return String.format("%X", value);
	}
	
	public static String Bytes_toHex(byte[] xx, String separator) {
		StringBuilder str = new StringBuilder();
		if (xx!=null && xx.length>0) {
			for(byte vv : xx) {
				if (mycodes.ValidString(separator)) {
					if (str.length()>0) {
						str.append(separator);	
					}	
				}
				int value = vv & 0xFF;
				str.append(String.format("%02X", value));
				
			}
		}
		return str.toString();
	}
	
	public static String Bytes_toHex(String separator, byte... value) {
		StringBuilder str = new StringBuilder();
		if (value!=null && value.length>0) {
			for(byte vv : value) {
				if (mycodes.ValidString(separator)) {
					if (str.length()>0) {
						str.append(separator);
					}
				}
				int vvv = vv & 0xFF;
				str.append(String.format("%02X", vvv));
			}
		}
		return str.toString();
	}
	
	public static String Bytes_toString(byte[] xx) {
		StringBuilder str = new StringBuilder();
		if (xx!=null && xx.length>0) {
			for(byte vv : xx) {
				str.append((char) vv);
			}
		}
		return str.toString();
	}
	
	public static boolean IsByteArray_and_have_values(byte[] xx ) {
		if (xx != null) {
			if (xx.length>0) {
				return true;
			}
		}
		return false;
	}
	
	public static int ToInt(byte xx) {
		return xx & 0xFF;
	}
	
	public static byte[] ConcatBytes(byte[]... ba ) {
		if (ba!=null) {
			if (ba.length>0) {
				int length = 0;
				for(byte[] xx : ba) {
					if (xx!=null) {
						if (xx.length>0) {
							length+=xx.length;
						}
					}
				}
				
				if (length>0) {
					byte[] result = new byte[length];
					int ii = 0;
					for(byte[] xx : ba) {
						for(int jj = 0; jj<xx.length;jj++) {
							result[ii] = xx[jj];
							ii++;
						}
					}
					return result;
				}
			}
		}
		return null;
	}
}
