package devices.RTC_I2C;

import anywheresoftware.b4a.BA;
import jGPIO.I2C.I2C_BUS;
import jGPIO.I2C.I2C_BUS_Event;
import jGPIO.I2C.I2C_Device;
import jGPIO.I2C.I2C_Device_Event;

@BA.ShortName("DS3231")
@BA.Events(values= {
		"log(msg as string)"
})
public class DS3231 implements I2C_BUS_Event, I2C_Device_Event {
	private BA bax;
	private Object myobject;
	private String event;
	private boolean need_log_event = false;
	
	private int dev_address = 0x68;
	private I2C_BUS i2cbus;
	private I2C_Device i2cdev;
	
	private boolean iscorrect = false;
	
	private int controlbyte = -1;
	private int statusbyte = -1;
	
	/**
	 * Initialize DS3231 (RTC)
	 * @param caller : caller object
	 * @param eventname : event name
	 */
	public void Initialize(BA ba, Object caller, String eventname) {
		this.bax = ba;
		this.myobject = caller;
		this.event = eventname;
		if (bax!=null) {
			if (myobject!=null) {
				if (!event.isEmpty()) {
					need_log_event = bax.subExists(event+"_log");
				}
			}
		}
		
		
		 Runtime.getRuntime().addShutdownHook(new Thread() {
	            @Override
	            public void run() {
	                Close();
	            }
	        });
	}
	
	public void Close() {
		if (i2cdev!=null) {
			i2cdev.CloseDevice();
		}
		i2cdev = null;
		
		if (i2cbus!=null) {
			i2cbus.Close_Bus();
		}
		i2cbus = null;
	}
	
	/**
	 * Open DS3231 connection
	 * @param bus_name : i2c bus name
	 * @param slave_address : DS3231 address, default is 0x68
	 * @return true if opened
	 */
	public boolean Open(String bus_name, int slave_address) {
		iscorrect = false;
		if (!jGPIO.jGPIO.IsLinux) return iscorrect;
		
		i2cbus = new I2C_BUS(bus_name);
		i2cbus.SetJavaEvent(this);
		if (i2cbus.Open_Bus()) {
			i2cdev = new I2C_Device(i2cbus.I2C_Handle(), slave_address);
			i2cdev.SetJavaEvent(this);
			if (i2cdev.OpenDevice_SLAVEFORCE()) {
				iscorrect = true;
				this.dev_address = slave_address;
			}
		}
		
		if (iscorrect) {
			raise_log("DS3231 is Opened");
		} else {
			raise_log("DS3231 failed to Open");
		}
		return iscorrect;
	}
	
	/**
	 * Check associated Slave Address
	 * @return -1 if no address
	 */
	public int getSlaveAddress() {
		return this.dev_address;
	}
	
	/**
	 * Check if I2C to this DS3231 is opened
	 * @return true if opened
	 */
	public boolean getIsOpened() {
		return iscorrect;
	}
	
	/**
	 * Read date and time information from RTC
	 * @return RTC_Data class, check on IsCorrectData() to know valid or not
	 */
	public RTC_Data Read_RTC() {
		RTC_Data result = new RTC_Data();
		if (iscorrect) {
			int writeaddr_result = i2cdev.WriteBytes(new byte[] {0});	// start reading from address 0
			if (writeaddr_result==1) {
				byte[] bb = i2cdev.ReadBytes(7);
				result.Initialize_from_DS3231(bb);
			} else {
				raise_log("Read_RTC address write is failed");
			}
		}
		return result;
	}
	
	/**
	 * Write date and time information to RTC
	 * @param value : RTC_Data class. Must be initialized, and IsCorrectData must be true
	 * @return true if success
	 */
	public boolean Write_RTC(RTC_Data value) {
		if (iscorrect) {
			if (value!=null) {
				if (value.IsCorrectData()) {
					byte[] address_data = value.Get_DS3231_bytes();
					int writeresult = i2cdev.WriteBytes(address_data);
					if (writeresult==address_data.length) return true;
				}
			}
		}
		
		return false;
	}
	
	/**
	 * Read temperature
	 * @return -99 if failed
	 */
	public int Read_Temperature() {
		if (iscorrect) {
			byte[] address = new byte[] {0x11};		// temperature : integer part is 0x11, fractional part is 0x12
			int writeaddressresult = i2cdev.WriteBytes(address);
			if (writeaddressresult==1) {
				byte[] bb = i2cdev.ReadBytes(1);
				if (bb!=null) {
					if (bb.length>=1) {
						int result = bb[0] & 0x7F;
						if ((bb[0] & 0x80)!=0) result = -result;
						return result;
					}
				}
			}
		}
		return -99;
	}
	
	/**
	 * Check on Status Byte
	 * @return true if success reading
	 */
	public boolean Read_StatusByte() {
		statusbyte = -1;
		if (iscorrect) {
			byte[] address = new byte[] {0x0E};
			if (i2cdev.WriteBytes(address)==1) {
				byte[] vv = i2cdev.ReadBytes(1);
				if (vv!=null) {
					if (vv.length>=1) {
						statusbyte = (vv[0] & 0xFF);
						return true;
					}
				}
			}
		}
		return false;
	}
	
	/**
	 * Check if Oscillator is running or stopped
	 * @return 1 = running, 0 = stopped, -1 = no data (call Read_StatusByte function)
	 */
	public int Oscillator_is_Running() {
		if (statusbyte==-1) return -1;
		if ((statusbyte & 0x80)==0) 
			// running
			return 1; 
		else 
			// stopped
			return 0;
	}
	
	/**
	 * Check if Osc 32Khz Output is enabled
	 * @return 1 = enabled, 0 = disabled, -1 = no data (call Read_StatusByte function)
	 */
	public int Osc32Khz_is_Running() {
		if (statusbyte==-1) return -1;
		if ((statusbyte & 0x08)==0) {
			return 0;
		} else {
			return 1;
		}
	}
	
	
	
	/**
	 * Set Osc 32Khz Output is enabled or not
	 * @param isON : if true, then enabled
	 * @return true if success
	 */
	public boolean Set_Osc32Khz_Output(boolean isON) {
		if (iscorrect) {		
			// i2c is opened
			if (statusbyte!=-1) {	
				// statusbyte alread read before
				int temp = statusbyte;
				if (isON) {
					temp |= 8;
				} else {
					temp &= 0xF7;
				}
				byte[] address_data =  new byte[] {0x0F, (byte) temp};
				int writeresult = i2cdev.WriteBytes(address_data);
				if (writeresult==address_data.length) {
					statusbyte = temp;
					return true;
				}
			}
		}
		
		
		return false;
	}
	
	
	/**
	 * Read Control bytes
	 * @return true if success
	 */
	public boolean Read_ControlByte() {
		controlbyte = -1;
		if (iscorrect) {
			byte[] address = new byte[] {0x0F};
			if (i2cdev.WriteBytes(address)==1) {
				byte[] vv = i2cdev.ReadBytes(1);
				if (vv!=null) {
					if (vv.length>=1) {
						controlbyte = (vv[0] & 0xFF);
						return true;
					}
				}
			}
		}
		return false;
	}
	
	/**
	 * Set internal oscillator ON or OFF
	 * @param isON : if true, then oscillator running
	 * @return true if success
	 */
	public boolean Update_ControlByte_OscillatorEnable(boolean isON) {
		if (iscorrect) {
			if (controlbyte>=0) {
				int temp = controlbyte;
				if (isON) {
					temp &= 0x7F;		// if 0, then oscillator on
				} else {
					temp |= 0x80;		// if 1, then oscillator stopped
				}
				byte[] address_data = new byte[] {0x0F, (byte) temp};
				if (i2cdev.WriteBytes(address_data)==2) {
					controlbyte = temp;
					return true;
				}
			}
		}
		
		return false;
	}
	
	/**
	 * Set SquareWave output ON or OFF
	 * @param isON : set On
	 * @param freqcode : 0 = 1 Hz, 1 = 1024 Hz, 2 = 4096 Hz, 3 = 8192 Hz, default is 0
	 * @return true if success
	 */
	public boolean Update_ControlByte_SquareWaveOutput(boolean isON, int freqcode) {
		if (iscorrect) {
			if (controlbyte>=0) {
				int temp = controlbyte;
				
				if (isON) {
					// bit 6 harus 1, bit 2 harus 0
					temp |= 0x40;		// bit 6 harus 1
					temp &= 0xFB;		// bit 2 harus 0
					temp &= 0xE7;		// bit 4 = 0, bit 3 = 0
					switch(freqcode) {
					
					case 1:		// 1024 Hz
								// bit 4 = 0, bit 3 = 1
							temp |= 8;
							break;
					case 2: 	// 4096 Hz
								// bit 4 = 1, bit 3 = 0
							temp |= 0x10;
							break;
					case 3:		// 8192 Hz
								// bit 4 = 1, bit 3 = 1
							temp |= 0x18;
							break;
					
					}
				} else {
					// bit 6 harus 0, bit 2 harus 1
					temp &= 0xBF;		// bit 6 harus 0
					temp |= 4;			// bit 2 harus 1
					temp &= 0xE7;		// bit 4 = 0, bit 3 = 0;
				}
				
				byte[] address_data = new byte[] {0x0F, (byte) temp};
				if (i2cdev.WriteBytes(address_data)==2) {
					controlbyte = temp;
					return true;
				}
			}
		}
		return false;
	}
	
	@Override
	///from I2C_BUS_Event and I2C_Device_Event
	public void Log(String msg) {
		raise_log(msg);
	}

	private void raise_log(String msg) {
		if (need_log_event) {
			bax.raiseEventFromDifferentThread(myobject, null, 0, event+"_log", false, new Object[] {msg});
		}
	}
}
