package ptzcamera;

import anywheresoftware.b4a.BA;
import anywheresoftware.b4a.keywords.Bit;
import jGPIO.mycodes;

@BA.ShortName("PelcoP")
@BA.Events(values= {
		"log(msg as string)",
		"newposition(pan as int, tilt as int , zoom as int)"
})
public class PelcoP implements CamFunctions {

	private BA bax;
	private Object caller;
	private String event;
	private final Object Me = this;
	private boolean need_log_event = false;
	private boolean need_newposition_event = false;
	private int _id = 0;
	private String _name = "Cam1";
	private  PTZComPort comport;
	private int _pan_position;
	private int _tilt_position;
	private int _zoom_position;
	
	/**
	 * Initialize PelcoP Camera
	 * @param callerobject : caller objecgt
	 * @param eventname : event name
	 */
	public void Initialize(BA ba, Object callerobject, String eventname) {
		bax = ba;
		caller = callerobject;
		event = eventname.trim();
		if (bax instanceof BA) {
			if (caller != null) {
				if (!event.isEmpty()) {
					need_log_event = bax.subExists(event+"_log");
					need_newposition_event = bax.subExists(event+"_newposition");
				}
			}
		}
	}
	
	/**
	 * Setup Camera identification
	 * @param ID : ID number, start from 0 (Pelco-P) until 0x7F
	 * @param CameraName : some camera name
	 * @param targetport : PTZComPort used in communication
	 */
	public void Setup(int ID, String CameraName, PTZComPort targetport) {
		if (ID>=0) {					
			if (ID<=0x7F) {
				if (_id != ID) {
					_id = ID;
					raise_log("Camera ID changed to "+ _id);
				} else raise_log("Camera ID already "+ ID);
			} else raise_log("Invalid new ID="+ID);
		} else raise_log("Invalid new ID="+ID);
		
		if (!CameraName.isEmpty()) {
			if (!CameraName.equals(_name)) {
				_name = CameraName;
				raise_log("Camera Name changed to "+CameraName);
			} else raise_log("Camera Name already "+CameraName);
		} else raise_log("Camera Name must not empty");
		
		if (targetport instanceof PTZComPort) {
			if (targetport.IsOpened()) {
				comport = targetport;
				raise_log("TargetPort set to "+comport.getPortName());
				comport.AddToMap(this);
			} else raise_log("TargetPort is closed");
		} else raise_log("TargetPort is invalid");
	}
	
	/**
	 * Get assigned ID
	 * @return ID
	 */
	public int getID() {
		return _id;
	}
	
	/**
	 * Get assigned Name
	 * @return Name
	 */
	public String getName() {
		return _name;
	}
	
	private void raise_log(String value) {
		if (need_log_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {value});
	}
	
	private void raise_newposition(int pan, int tilt, int zoom) {
		if (need_newposition_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_newposition", false, new Object[] {pan, tilt, zoom});
	}
	
	private byte[] prepare_databytes() {
		byte[] data = new byte[8];			// Pelco-P data length = 8
		data[0] = (byte) 0xA0;				// STX
		data[1] = (byte) _id;				// camera ID
		data[6] = (byte) 0xAF;				// ETX
		return data;
	}
	
	private void make_checksum(byte[] bb) {
		if (bb != null) {
			if (bb.length == 8) {
				int result = 0;
				for(int ii=0;ii<8;ii++) {		// byte 1 - 7 di - XOR, jadi checksum
					result = Bit.Xor(result, Bit.And(0xFF, bb[ii]));
				}
				bb[7]  = (byte) result;
			}
		}
	}

	@Override
	/**
	 * Set Current Position to Preset Number
	 * @param presetnumber : start from 1 until 0xFF
	 * @return true if success
	 */
	public boolean SetPreset(int presetnumber) {
		
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				data[2] = 0;		
				data[3] = 3;		
				data[4] = 0;
				data[5] = (byte) presetnumber;
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}

	@Override
	/**
	 * Go To Preset Number
	 * @param presetnumber : start from 1 until 0xFF
	 * @return true if success
	 */
	public boolean GoToPreset(int presetnumber) {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				data[2] = 0;
				data[3] = 7;
				data[4] = 0;
				data[5] = (byte) presetnumber;
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}

	@Override
	/**
	 * Start Camera Movement 
	 * @param pan : 0 = no pan, 1 = pan right, -1 = pan left
	 * @param tilt : 0 = no tilt, 1 = tilt up, -1 = tilt down
	 * @param zoom : 0 = no zoom, 1 = zoom in, -1 = zoom out
	 * @param speed : 0 until 0x30
	 * @return true if success
	 */
	public boolean Start_PanTiltZoom(int pan, int tilt, int zoom, int speed) {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				data[2] = 0;
				// data[3] : bit 7    6     5     4    3     2      1     0
				//               0  Z-out Z-in T-Down T-Up P-Left P-Right 0
				byte zoom_mask = 0;
				byte tilt_mask = 0;
				byte pan_mask = 0;
				if (zoom!=0) {
					if (zoom>0) 
						zoom_mask = (byte)(0x20);		// zoom IN
					else
						zoom_mask = (byte)(0x40);		// zoom Out
				}
				if (tilt!=0) {
					if (tilt>0) 
						tilt_mask = (byte)(8);			// Tilt Up
					else 
						tilt_mask = (byte)(0x10);		// Tilt Down
				}
				if (pan != 0) {
					if (pan>0)
						pan_mask = (byte)(2);			// pan right
					else
						pan_mask = (byte)(4);			// pan lefts
					
				}
				data[3] = (byte)((zoom_mask | tilt_mask | pan_mask) & 0x7E);	// di-AND 7E supaya bit 7 = 0 , bit 0 = 0
				
				data[4] = (byte) speed;		// pan speed
				data[5] = (byte) speed;		// tilt speed
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}

	@Override
	/**
	 * Clear Preset position 
	 * @param presetnumber : start from 1 until 0xFF
	 * @return true if success
	 */
	public boolean ClearPreset(int presetnumber) {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				data[2] = 0;
				data[3] = 5;
				data[4] = 0;
				data[5] = (byte) presetnumber;
				
				
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}

	@Override
	/**
	 * Stop PTZ Movement
	 */
	public boolean StopMoving() {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				// gak ngapa-ngapain di sini 
				
				
				
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}

	@Override
	/**
	 * Pan to Left
	 * @param speed : 0 - 0x3F
	 * @return true if success
	 */
	public boolean Left(int speed) {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				data[2] = 0;
				data[3] = 4;			// left
				data[4] = (byte) speed; // pan speed
				data[5] = 0;
				
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}

	@Override
	/**
	 * Pan to Right
	 * @param speed : 0 - 0x3F
	 * @return true if success
	 */
	public boolean Right(int speed) {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				data[2] = 0;
				data[3] = 2;			// right
				data[4] = (byte) speed; // pan speed
				data[5] = 0;
				
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}

	@Override
	public boolean Up(int speed) {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				data[2] = 0;
				data[3] = 8;			// up
				data[4] = 0; 
				data[5] = (byte) speed; // tilt speed
				
				
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}

	@Override
	public boolean Down(int speed) {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				data[2] = 0;
				data[3] = 0x10;			// down
				data[4] = 0; 
				data[5] = (byte) speed; // tilt speed
				
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}

	@Override
	public boolean UpLeft(int speed) {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				data[2] = 0;
				data[3] = 0x0C;			// up dan left
				data[4] = (byte) speed; // pan speed
				data[5] = (byte) speed; // tilt speed
				
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}

	@Override
	public boolean UpRight(int speed) {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				data[2] = 0;
				data[3] = 0x0A;			// up and right
				data[4] = (byte) speed; // pan speed 
				data[5] = (byte) speed; // tilt speed
				
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}

	@Override
	public boolean DownLeft(int speed) {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				data[2] = 0;
				data[3] = 0x14;			// down and left
				data[4] = (byte) speed; // pan speed 
				data[5] = (byte) speed; // tilt speed
				
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}

	@Override
	public boolean DownRight(int speed) {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				data[2] = 0;
				data[3] = 0x12;			// down and right
				data[4] = (byte) speed; // pan speed 
				data[5] = (byte) speed; // tilt speed
				
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}

	@Override
	public boolean ZoomIn() {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				data[2] = 0;
				data[3] = 0x20;			// zoom in
				data[4] = 0;  
				data[5] = 0; 
				
				
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}

	@Override
	public boolean ZoomOut() {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				data[2] = 0;
				data[3] = 0x40;			// zoom out
				data[4] = 0;  
				data[5] = 0; 
				
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}

	@Override
	public boolean FocusNear() {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				data[2] = 1;			// focus near
				data[3] = 0;			
				data[4] = 0;  
				data[5] = 0; 
				
				
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}

	@Override
	public boolean FocusFar() {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				data[2] = (byte)0x80;			// focus far
				data[3] = 0;			
				data[4] = 0;  
				data[5] = 0; 
				
				
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}

	@Override
	public boolean Get_PanPosition() {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				data[2] = 0;			
				data[3] = 0x51;			// request PAN position
				data[4] = 0;  
				data[5] = 0; 
				
				
				make_checksum(data);
				return comport.SendData(data);
				
				// expect result : 0xA0	Address	0x00	0x59	Value_High_Byte	Value_Low_Byte	0xAF	XOR
			}
		}
		return false;
	}

	@Override
	public boolean Get_TiltPosition() {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				data[2] = 0;			
				data[3] = 0x53;			// request TILT position
				data[4] = 0;  
				data[5] = 0; 
				
				
				make_checksum(data);
				return comport.SendData(data);
				
				// expect result : 0xA0	Address	0x00	0x5B	Value_High_Byte	Value_Low_Byte	0xAF	XOR
			}
		}
		return false;
	}

	@Override
	public boolean Get_ZoomPosition() {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				data[2] = 0;			
				data[3] = 0x55;			// request PAN position
				data[4] = 0;  
				data[5] = 0; 
				
				make_checksum(data);
				return comport.SendData(data);
				
				// expect result : 0xA0	Address	0x00	0x5D	Value_High_Byte	Value_Low_Byte	0xAF	XOR
			}
		}
		return false;
	}

	@Override
	public boolean Set_Tracking(boolean ON) {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				//data[2] = 0;			
				data[3] = ON ? (byte)0x65 : (byte)0x67;		
				//data[4] = 0;	
				//data[5] = 0;	
				
				
				
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}

	@Override
	public boolean Trigger_Tracking() {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				//data[2] = 0;			
				data[3] = (byte)0x63;	
				//data[4] = 0;	
				//data[5] = 0;	
				
				
				
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}

	@Override
	public boolean Set_WOL(boolean ON) {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				//data[2] = 0;			
				data[3] = ON ? (byte)0x69 : (byte)0x6B;		
				//data[4] = 0;	
				//data[5] = 0;	
				
				
				
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}

	@Override
	public boolean Read_Profile(int profileID) {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				//data[2] = 0;			
				data[3] = (byte) 0x6D;	
				//data[4] = 0;	
				data[5] = (byte)profileID;	
				
				
				
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}

	@Override
	public boolean Save_Profile(int profileID) {
		if (comport instanceof PTZComPort) {
			if (comport.IsOpened()) {
				byte[] data = prepare_databytes();
				//data[2] = 0;			
				data[3] = (byte) 0x6F;	
				//data[4] = 0;	
				data[5] = (byte)profileID;	
				
				
				
				make_checksum(data);
				return comport.SendData(data);
			}
		}
		return false;
	}
	
	@BA.Hide
	public static boolean data_valid(byte[] bb) {
		if (bb!=null) {
			if (bb.length==8) {
				if (Bit.And(bb[0], 0xFF)==0xA0) {
					if (Bit.And(bb[6], 0xFF)==0xAF) {
						int ck = 0;
						for(int ii=0;ii<7;ii++) {
							ck = Bit.Xor(ck, Bit.And(bb[ii], 0xFF));
						}
						if (bb[7]==(byte)ck) {
							return true;
						}
					}
				}
			}
		}
		return false;
	}
	
	@BA.Hide
	public void DataReceivedFromPTZComPort(byte[] bb) {
		// sampe sini dah valid, dah dicek pake data_valid
		// tinggal pakai aja 
		
		int replycode = Bit.And(bb[3], 0xFF);
		int pos = mycodes.Make_Int16(bb[4], bb[5]);
		
		boolean need_raising_newposition = false;
		
		switch(replycode) {
			case 0x59 :
				// pan position
				if (pos != _pan_position) {
					_pan_position = pos;
					need_raising_newposition = true;
				}
				break;
			case 0x5B :
				// tilt position
				if (pos != _tilt_position) {
					_tilt_position = pos;
					need_raising_newposition = true;
				}
				break;
			case 0x5D :
				// zoom position
				if (pos != _zoom_position) {
					_zoom_position = pos;
					need_raising_newposition = true;
				}
				break;
		}
		
		if (need_raising_newposition) raise_newposition(_pan_position, _tilt_position, _zoom_position);
	}
	
	/**
	 * Get Last Pan Position
	 * @return Pan Position
	 */
	public int PanPosition() {
		return _pan_position;
	}
	
	/**
	 * Get Last Tilt Position
	 * @return Tilt Position
	 */
	public int TiltPosition() {
		return _tilt_position;
	}
	
	/**
	 * Get Last Zoom Position
	 * @return Zoom Position
	 */
	public int ZoomPosition() {
		return _zoom_position;
	}
}
