package SIP;

import java.net.SocketException;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import Audio.BassFileReader;
import Audio.BassFileReaderListener;
import Audio.BassSoundManager;
import Audio.BassSoundManagerListener;
import code.common;

import lombok.Getter;
import org.pmw.tinylog.Logger;
import peers.media.MediaManager;
import peers.media.MediaMode;
import peers.sip.Utils;
import peers.sip.core.useragent.SipListener;
import peers.sip.core.useragent.UserAgent;
import peers.sip.syntaxencoding.SipUriSyntaxException;
import peers.sip.transactionuser.Dialog;
import peers.sip.transactionuser.DialogManager;
import peers.sip.transport.SipRequest;
import peers.sip.transport.SipResponse;

import static code.common.*;

@SuppressWarnings({"unused", "UnusedReturnValue"})
public class jSIPClient {
	
	public jSIPClient(Properties config) {
		load_config(config);
		Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            raise_log_event("SIP.jSIPClient ShutdownHook called");
            HangUp();
            Disconnect();
        }));
	}

	@Getter private String serverAddress;
	@Getter private String Username;
	@Getter private String Password;
	

	public CustomConfig cc = new CustomConfig();
	private EventManager em;


	
	private javaSipEvents jse;
	private BassFileReaderListener bfrl;
	
	private final ExecutorService exec = Executors.newSingleThreadExecutor();
	

	/**
	 * Connect to SIP Server
	 * @return true if parameter completed
	 */
	public boolean Connect() {
		Disconnect();
		if (cc==null) {
			System.out.println("Creating Custom Config");
			cc = new CustomConfig();
		} else System.out.println("Custom Config already created");
		cc.setDomain(serverAddress);
		cc.setUserPart(Username);
		cc.setPassword(Password);
		cc.SetLocalInetAddress("0.0.0.0");
		em = new EventManager(cc);
		
		return false;
	}
	
	public void SetJavaSipEvent(javaSipEvents xx) {
		this.jse = xx;
	}
	
	public void SetBassFileReaderListener(BassFileReaderListener xx) {
		this.bfrl = xx;
	}
	
	/**
	 * Set Input Volume
	 * @param value 0 - 100
	 */
	public void setInputVolume(int value) {
		if (em!=null) em.setInputVolume(value);
	}
	
	/**
	 * Get Input Volume
	 * @return 0 - 100
	 */
	public int getInputVolume() {
		if (em!=null) {
			return em.getInputVolume();
		} else return 0;
	}
	
	/**
	 * Set Output Volume
	 * @param value 0 - 100
	 */
	public void setOutputVolume(int value) {
		if (em!=null) em.setOutputVolume(value);
	}
	
	
	/**
	 * Get Output Volum
	 * @return 0 - 100
	 */
	public int getOutputVolume() {
		if (em!=null) {
			return em.getOutputVolume();
		} else return 0;
	}
	
	/**
	 * Disconnect from SIP Server
	 */
	public void Disconnect() {
		if (em!=null) {
			em.Close();
			em = null;
		}
	}
	
	/**
	 * HangUp current call
	 * @return true if success
	 */
	public boolean HangUp() {
		if (em!=null) {
			if (em.IsCreated()) {
				if (em.CallingInProgress()) {

					em.HangUp();
					return true;
				} else raise_log_event("HangUp failed, no call made");
			} else raise_log_event("HangUp failed, EventManager not created");
		} else raise_log_event("HangUp failed, Connect first");
		return false;
	}
	
	/**
	 * Stream an audiofile to somebody
	 * @param target target to call
	 * @param filename file to stream
	 * @return true if can be streamed
	 */
	public boolean StreamFile(String target, String filename) {
		if (em!=null) {
			if (em.IsCreated()) {
				if (cc!=null) {
					cc.setMediaFile(filename);
					cc.setMediaMode(MediaMode.file);
					em.Call(target);
					em.AddListener(bfrl);
					return true;
				} else raise_log_event("StreamFile failed, Custom Config not created");
			} else raise_log_event("StreamFile failed, EventManager not created");
		} else raise_log_event("StreamFile failed, Connect first");
		return false;
	}
	
	/**
	 * Call somebody
	 * @param target target to call
	 * @return true if can be called
	 */
	public boolean Call(String target) {
		if (em!=null) {
			if (em.IsCreated()) {
				if (cc!=null) {
					cc.setMediaMode(MediaMode.captureAndPlayback);
					em.Call(target);
					return true;
				} else raise_log_event("Call failed, Custom Config not created");
			} else raise_log_event("Call failed, EventManager not created");
		} else raise_log_event("Call failed, Connect first");
		return false;
	}
	
	/**
	 * Accept Incoming Call
	 * @param req SIP_Request object
	 * @return true if call can be accepted
	 */
	public boolean AcceptIncomingCall(SIP_Request req) {
		if (em!=null) {
			if (em.IsCreated()) {
				if (req!=null) {
					cc.setMediaMode(MediaMode.captureAndPlayback);
					em.AcceptCall(req.getReq());
					return true;
				} else raise_log_event("AcceptIncomingCall failed, SIP_Request is null");
			} else raise_log_event("AcceptIncomingCall failed, EventManager not created");
		} else raise_log_event("AcceptIncomingCall failed, Connect first");
		return false;
	}
	
	/**
	 * Send DTMF code
	 * @param code code to send
	 * @return true if can be send
	 */
	public boolean SendDTMF(final char code) {
		if (em!=null) {
			if (em.IsCreated()) {
				em.DTMF(code);
				return true;
			} else raise_log_event("SendDTMF failed, EventManager not created");
		} else raise_log_event("SendDTMF failed, Connect first");
		return false;
	}
	
	/**
	 * Reject Incoming Call
	 * @param req SIP_Request to reject
	 * @return true if can be rejected
	 */
	public boolean RejectIncomingCall(SIP_Request req) {
		if (em!=null) {
			if (em.IsCreated()) {
				if (req!=null) {
					em.RejectCall(req.getReq());
					return true;
				} else raise_log_event("RejectIncomingCall failed, SIP_Request is null");
			} else raise_log_event("RejectIncomingCall failed, EventManager not created");
		} else raise_log_event("RejectIncomingCall failed, Connect first");
		return false;
	}
	
	
	
	/////////// Class Implementation ////////////////////
	
	public class EventManager implements SipListener{
		private UserAgent user_agent;
		private SipRequest sip_request;
		private boolean all_success;
		//JavaxSoundManager jsm;
		BassSoundManager jsm;

		public EventManager(final CustomConfig xx) {
			all_success = false;
			//Logger lg = new FileLogger(null);
			Logger lg = null;
//			jsm = new JavaxSoundManager(false, lg, null);
			jsm = new BassSoundManager(1,0, new BassSoundManagerListener() {

				@Override
				public void Opened() {
					raise_log_event("BassSoundManager Opened");
					raise_soundchannelopened_event();
				}

				@Override
				public void Closed() {
					raise_log_event("BassSoundManager Closed");
					raise_soundchannelclosed_event();
				}

				@Override
				public void log_info(String msg) {
					raise_log_event("BassSoundManager info : "+msg);
				}

				@Override
				public void log_error(String msg) {
					raise_log_event("BassSoundManager error : "+msg);

				}

				@Override
				public void WriteFromRTP(int length, byte[] pcmdata) {
					// buat debugging aja, kalau dikeluarin, spamming log
					//raise_log_event("BassSoundmanager WriteFromRTP "+length+" bytes");
					raise_outputchannelpcmdata_event(length, pcmdata);
				}

				@Override
				public void ReadToRTP(int length, byte[] pcmdata) {
					// buat debugging aja, kalau dikeluarin, spamming log
					//raise_log_event("BassSoundManager ReadToRTP "+length+" bytes");
					raise_inputchannelpcmdata_event(length, pcmdata);
				}

				@Override
				public void DeviceFailure(String msg) {
					raise_log_event("BassSondManager DeviceFailure : "+msg);
					if (msg.contains("OutputChannel"))
						raise_outputchanneldevicefailure_event();
					else if (msg.contains("InputChannel"))
						raise_inputchanneldevicefailure_event();
				}

				@Override
				public void BufferingInformation(String msg) {
					raise_log_event("BassSoundManager BufferingInformation : "+msg);
					if (msg.contains("OutputChannel"))
						raise_outputchannelbuffering_event(msg);
					else if (msg.contains("InputChannel"))
						raise_inputchannelbuffering_event(msg);
				}

				@Override
				public void ChannelLevel(boolean is_input, int value) {
					if (is_input)
						raise_inputchannellevel_event(value);
					else
						raise_outputchannellevel_event(value);

				}

			});

			try {
				user_agent = new UserAgent(this, xx, jsm);
				exec.submit(() -> {
                    try {
                        user_agent.register();
                        all_success = true;
                    } catch (SipUriSyntaxException e) {
                        raise_log_event("EventManager register exception = "+e.getMessage());
                    }
                });

			} catch (SocketException e) {
				raise_log_event("EventManager create exception = "+e.getMessage());
			}

		}

		public void setInputVolume(int value) {
			if (jsm!=null) jsm.setInputChannelVolume(value);
		}

		public int getInputVolume() {
			if (jsm!=null) {
				return jsm.getInputChannelVolume();
			} else return 0;
		}

		public void setOutputVolume(int value) {
			if (jsm!=null) jsm.setOutputChannelVolume(value);
		}

		public int getOutputVolume() {
			if (jsm!=null) {
				return jsm.getOutputChannelVolume();
			} else return 0;
		}

		/**
		 * Check if EventManager succesfully created
		 * @return true if success
		 */
		public boolean IsCreated() {
			return all_success;
		}

		/**
		 * Call
		 * @param targetsip Target SIP
		 */
		public void Call(final String targetsip) {
			if (user_agent!=null) {
				exec.submit(() -> {
                    sip_request = null;
                    try {
                        sip_request = user_agent.invite(targetsip, null);
                    } catch (SipUriSyntaxException e) {
                        raise_log_event("Call exception = "+e.getMessage());
                    }
                });

			}
		}

		/**
		 * Accept Incoming Call
		 * @param req SipRequest to accept
		 */
		public void AcceptCall(final SipRequest req) {
			if (user_agent!=null) {
				if (req!=null) {
					exec.submit(() -> {
                        // source : https://github.com/ymartineau/peers/blob/master/peers-gui/src/main/java/net/sourceforge/peers/gui/EventManager.java
                        String callId = Utils.getMessageCallId(req);
                        DialogManager dialogManager = user_agent.getDialogManager();
                        Dialog dialog = dialogManager.getDialog(callId);
                        user_agent.acceptCall(req, dialog);
                    });

				}
			}
		}

		/**
		 * Add Listener for File Streaming
		 * @param event BassFileReaderListener
		 * @return true if can be added
		 */
		public boolean AddListener(BassFileReaderListener event) {
			if (user_agent!=null) {
				MediaManager mm = user_agent.getMediaManager();
				if (mm!=null) {
					BassFileReader bfr = mm.getFileReader();
					if (bfr!=null) {
						bfr.AddListener(event);
						return true;
					}
				}
			}
			return false;
		}



		public void DTMF(final char code) {
			if (user_agent!=null) {
				exec.submit(() -> {
                    MediaManager mm = user_agent.getMediaManager();
                    mm.sendDtmf(code);
                });


			}
		}

		/**
		 * Reject Incoming Call
		 * @param req SipRequest to reject
		 */
		public void RejectCall(final SipRequest req) {
			if (user_agent!=null) {
				if (req!=null) {
					exec.submit(() -> user_agent.rejectCall(req));

				}
			}
		}

		/**
		 * Check if Calling in Progress
		 * @return true if in progress
		 */
		public boolean CallingInProgress() {
			return sip_request!=null;
		}


		/**
		 * Hang-Up current call
		 */
		public void HangUp() {
			if (sip_request!=null) {
				if (user_agent!=null) {
					exec.submit(() -> {

                        user_agent.terminate(sip_request);
                        sip_request = null;
                    });

				} else raise_log_event("HangUp failed, user_agent not available");
			} else raise_log_event("HangUp failed, sip_request not available");
		}

		/**
		 * Close Connection to SIP Server
		 */
		public void Close() {
			if (user_agent!=null) {
				user_agent.close();
			}
		}

		@Override
		public void registering(SipRequest sipRequest) {
			SIP_Request req = new SIP_Request(sipRequest);
			raise_registering_event(req);
		}

		@Override
		public void registerSuccessful(SipResponse sipResponse) {
			SIP_Response resp = new SIP_Response(sipResponse);
			raise_registersuccesfull_event(resp);
		}

		@Override
		public void registerFailed(SipResponse sipResponse) {
			SIP_Response resp = new SIP_Response(sipResponse);
			raise_registerfailed_event(resp);
		}

		@Override
		public void incomingCall(SipRequest sipRequest, SipResponse provResponse) {
			SIP_Request req = new SIP_Request(sipRequest);
			SIP_Response resp = new SIP_Response(provResponse);
			raise_incomingcall_event(req, resp);
		}

		@Override
		public void remoteHangup(SipRequest sipRequest) {
			SIP_Request req = new SIP_Request(sipRequest);
			raise_remotehangup_event(req);
		}

		@Override
		public void ringing(SipResponse sipResponse) {
			SIP_Response resp = new SIP_Response(sipResponse);
			raise_ringing_event(resp);
		}

		@Override
		public void calleePickup(SipResponse sipResponse) {
			SIP_Response resp = new SIP_Response(sipResponse);
			raise_calleepickup_event(resp);
		}

		@Override
		public void error(SipResponse sipResponse) {
			raise_log_event("sip error, response="+common.gson.toJson(sipResponse));

		}

	}
	

	

	

	
	
	////////////  private functions ///////////////
	

	
	private void raise_log_event(String msg) {
		Logger.info(msg);
	}
	
	private void raise_registering_event(SIP_Request req) {
		if (jse!=null) jse.Registering(req);
	}
	
	private void raise_registersuccesfull_event(SIP_Response resp) {
		if (jse!=null) jse.RegisterSuccesful(resp);
	}
	
	private void raise_registerfailed_event(SIP_Response resp) {
		if (jse!=null) jse.RegisterFailed(resp);
	}
	
	private void raise_incomingcall_event(SIP_Request req, SIP_Response resp) {
		if (jse!=null) jse.IncomingCall(req, resp);
	}
	
	private void raise_remotehangup_event(SIP_Request req) {
		if (jse!=null) jse.RemoteHangUp(req);
	}
	
	private void raise_ringing_event(SIP_Response resp) {
		if (jse!=null) jse.Ringing(resp);
	}
	
	private void raise_calleepickup_event(SIP_Response resp) {
		if (jse!=null) jse.CalleePickup(resp);
	}
	
	private void raise_inputchanneldevicefailure_event(){

		//if (need_inputchanneldevicefailure_event) ba.raiseEventFromDifferentThread(Me, null, 0,event+"_inputchanneldevicefailure", false,null );
	}
	
	private void raise_inputchannelbuffering_event(String msg) {
		//if (need_inputchannelbuffering_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_inputchannelbuffering", false, new Object[] {msg});
	}
	
	private void raise_inputchannelpcmdata_event(int length, byte[] bb) {
		//if (need_inputchannelpcmdata_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_inputchannelpcmdata", false, new Object[] {length, bb});
	}
	
	private void raise_outputchanneldevicefailure_event(){
		//if (need_outputchanneldevicefailure_event) ba.raiseEventFromDifferentThread(Me, null, 0,event+"_outputchanneldevicefailure", false,null );
	}
	
	private void raise_outputchannelbuffering_event(String msg) {
		//if (need_outputchannelbuffering_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_outputchannelbuffering", false, new Object[] {msg});
	}
	
	private void raise_outputchannelpcmdata_event(int length, byte[] bb) {
		//if (need_outputchannelpcmdata_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_outputchannelpcmdata", false, new Object[] {length, bb});
	}
	
	private void raise_soundchannelclosed_event() {
		//if (need_soundchannelclosed_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_soundchannelclosed", false, null);
	}
	
	private void raise_soundchannelopened_event() {
		//if (need_soundchannelopened_event) ba.raiseEventFromDifferentThread(Me, null, 0,event+"_soundchannelopened", false, null);
	}
	
	private void raise_inputchannellevel_event(int value) {
		//if (need_inputchannellevel_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_inputchannellevel", false, new Object[] {value});
	}
	
	private void raise_outputchannellevel_event(int value) {
		//if (need_outputchannellevel_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_outputchannellevel", false, new Object[] {value});
	}
	


	private void load_config(Properties prop) {
		serverAddress =  GetProperties_StringValue(prop,"SipServer","rdkartono.ddns.me");
		Username = GetProperties_StringValue(prop,"SipUsername","101");
		Password = GetProperties_StringValue(prop,"SipPassword","password101");
		Logger.info("SipServer: "+serverAddress);
		Logger.info("SipUsername: "+Username);
		Logger.info("SipPassword: "+Password);
	}
}
