import java.util.Base64;

/**
 * 
 * @author tcmdon@utu.fi
 *
 */
public class LoraAppMsg {

	/**
	 * Modes of operation.
	 */
	public static final short LIGHT = 1;
	public static final short TEMP_HUMID = 2;
	public static final short TEMP_HUMID_LIGHT = 3;

	/**
	 * Message lengths
	 */
	public static final short LIGHT_LEN = 1 + 3 + 1;
	public static final short TEMP_HUMID_LEN = 1 + 9 + 1;
	public static final short TEMP_HUMID_LIGHT_LEN = 1 + 12 + 1;

	/*
	 * constants for interpreting sensorError field
	 */
	public static final short SUCCESS = 0;
	public static final short HUMID_ERROR = 1;
	public static final short TEMP_ERROR = 2;
	public static final short LIGHT_ERROR = 4;

	short mode;
	double humid;
	double temp;
	double tempIndex;
	double light;
	short sensorError;// (error / success) for individual sensor readings

	public static LoraAppMsg parse(String base64Encoded) {
		LoraAppMsg appMsg = new LoraAppMsg();

		byte[] msg = Base64.getDecoder().decode(base64Encoded);

		if (msg.length > 0) {
			appMsg.mode = msg[0];
			boolean ok = checkLength(appMsg.mode, msg.length);
			if (!ok) {
				throw new RuntimeException("msg mode - msg length mismatch.");
			}
		} else {
			throw new RuntimeException("empty app msg.");
		}
		if (appMsg.mode == TEMP_HUMID) {
			appMsg.humid = readDouble(msg[1], msg[2], msg[3]);
			appMsg.temp = readDouble(msg[4], msg[5], msg[6]);
			appMsg.tempIndex = readDouble(msg[7], msg[8], msg[9]);
			appMsg.sensorError = msg[10];
		} else if (appMsg.mode == LIGHT) {
			appMsg.light = readDouble(msg[1], msg[2], msg[3]);
			appMsg.sensorError = msg[4];
		} else if (appMsg.mode == TEMP_HUMID_LIGHT) {
			appMsg.humid = readDouble(msg[1], msg[2], msg[3]);
			appMsg.temp = readDouble(msg[4], msg[5], msg[6]);
			appMsg.tempIndex = readDouble(msg[7], msg[8], msg[9]);
			appMsg.light = readDouble(msg[10], msg[11], msg[12]);
			appMsg.sensorError = msg[13];
		}
		return appMsg;
	}
	
	private static boolean checkLength(short mode, int length) {
		if (mode == TEMP_HUMID) {
			return length == TEMP_HUMID_LEN;
		} else if (mode == LIGHT) {
			return length == LIGHT_LEN;
		} else if (mode == TEMP_HUMID_LIGHT) {
			return length == TEMP_HUMID_LIGHT_LEN;
		}
		return false;
	}

	/*
	 * x,y 
	 * 6 HEX digits 
	 * 1 : sign 
	 * 2, 3, 4 : x 
	 * 5, 6 : y
	 */
	private static double readDouble(byte b1, byte b2, byte b3) {
		int sign = ((b1 >> 4) == 0) ? 1 : -1;
		b1 &= 0b00001111;
		int x = (b1 << 8) | b2;
		int y = b3;
		double d = Double.valueOf(String.valueOf(x) + "." + String.valueOf(y));
		d = d * sign;
		return d;
	}

	boolean isError() {
		return sensorError != SUCCESS;
	}

	boolean isHumidError() {
		return (sensorError & HUMID_ERROR) != SUCCESS;
	}

	boolean isTempError() {
		return (sensorError & TEMP_ERROR) != SUCCESS;
	}

	boolean isLightError() {
		return (sensorError & LIGHT_ERROR) != SUCCESS;
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append(" MOP: ");
		sb.append(mode);
		if (mode == LIGHT) {
			sb.append(" Light: ");
			sb.append(isLightError() ? "SENSOR ERROR" : light);
		} else if (mode == TEMP_HUMID) {
			sb.append(" Humidity: ");
			sb.append(isHumidError() ? "SENSOR ERROR" : humid);
			sb.append(" Temperature: ");
			sb.append(isTempError() ? "SENSOR ERROR" : temp);
			sb.append(" Heat Index: ");
			sb.append(isTempError() ? "SENSOR ERROR" : tempIndex);
		} else if (mode == TEMP_HUMID_LIGHT) {
			sb.append(" Light: ");
			sb.append(isLightError() ? "SENSOR ERROR" : light);
			sb.append(" Humidity: ");
			sb.append(isHumidError() ? "SENSOR ERROR" : humid);
			sb.append(" Temperature: ");
			sb.append(isTempError() ? "SENSOR ERROR" : temp);
			sb.append(" Heat Index: ");
			sb.append(isTempError() ? "SENSOR ERROR" : tempIndex);
		}
		return sb.toString();
	}
}