content: package me.tagavari.airmessage.connection;
  
  import android.content.Context;
  import android.os.Handler;
  import android.os.Looper;
  
  import androidx.annotation.NonNull;
  import androidx.annotation.Nullable;
  import androidx.core.util.Consumer;
  import io.reactivex.rxjava3.core.Observable;
  import me.tagavari.airmessage.connection.listener.CommunicationsManagerListener;
  import me.tagavari.airmessage.enums.ConnectionErrorCode;
  import me.tagavari.airmessage.enums.ConnectionFeature;
  import me.tagavari.airmessage.enums.ProxyType;
  import me.tagavari.airmessage.redux.ReduxEventAttachmentUpload;
  import me.tagavari.airmessage.util.ConnectionParams;
  import me.tagavari.airmessage.util.ConversationTarget;
  
  import java.io.File;
  import java.util.Collection;
  import java.util.List;
  
  public abstract class CommunicationsManager<Packet> {
  	//Creating the handler
  	private final Handler handler = new Handler(Looper.getMainLooper());
  	
  	//Creating the connection values
  	private final CommunicationsManagerListener listener;
  	@ProxyType private final int dataProxyType;
  	private final DataProxy<Packet> dataProxy;
  	
  	public CommunicationsManager(CommunicationsManagerListener listener, @ProxyType int proxyType) {
  		//Setting the values
  		this.listener = listener;
  		this.dataProxyType = proxyType;
  		dataProxy = getDataProxy(proxyType);
  		
  		//Setting the data proxy listener
  		dataProxy.setListener(new DataProxyListener<Packet>() {
  			@Override
  			public void handleOpen() {
  				//Handling the event
  				CommunicationsManager.this.handleOpen();
  			}
  			
  			@Override
  			public void handleClose(int reason) {
  				//Forwarding the event to the listener
  				if(listener != null) listener.onClose(reason);
  				
  				//Handling the event
  				CommunicationsManager.this.handleClose(reason);
  			}
  			
  			@Override
  			public void handleMessage(Packet packet) {
  				//Handling the event
  				CommunicationsManager.this.handleMessage(packet);
  			}
  		});
  	}
  	
  	/**
  	 * Gets a data proxy from a data proxy type
  	 * @param proxyType The data proxy type
  	 * @return The data proxy
  	 * @throws IllegalArgumentException If the proxy type is invalid or unsupported
  	 */
  	protected abstract DataProxy<Packet> getDataProxy(@ProxyType int proxyType);
  	
  	/**
  	 * Connects to the server
  	 */
  	public void connect(Context context, @Nullable ConnectionParams override) {
  		//Connecting the proxy
  		dataProxy.start(context, override);
  	}
  	
  	/**
  	 * Disconnects from the server
  	 * @param code The error code if this disconnection, or -1 for default
  	 */
  	public final void disconnect(@ConnectionErrorCode int code) {
  		dataProxy.stop(code);
  	}
  	
  	//Used in implementations
  	protected abstract void handleOpen();
  	protected abstract void handleClose(@ConnectionErrorCode int reason);
  	protected abstract void handleMessage(Packet packet);
  	
  	/**
  	 * Handles a handshake message
  	 * @param installationID The installation ID of the server
  	 * @param deviceName The device name of the server
  	 * @param systemVersion The system version of the server
  	 * @param softwareVersion The AirMessage version of the server
  	 */
  	public void onHandshake(String installationID, String deviceName, String systemVersion, String softwareVersion, String userName, boolean supportsFaceTime) {
  		//Forwarding the event to the listener
  		if(listener != null) listener.onOpen(installationID, deviceName, systemVersion, softwareVersion, userName, supportsFaceTime);
  	}
  	
  	/**
  	 * Sends a ping packet to the server
  	 *
  	 * @return Whether the message was successfully sent
  	 */
  	public abstract boolean sendPing();
  	
  	/**
  	 * Requests a message to be sent to the specified conversation
  	 *
  	 * @param requestID The ID of the request
  	 * @param conversation The conversation to send to
  	 * @param message The message to send
  	 * @return Whether the request was successfully sent
  	 */
  	public abstract boolean sendMessage(short requestID, ConversationTarget conversation, String message);
  	
  	/**
  	 * Uploads a file chunk to be sent to the specified conversation
  	 *
  	 * @param requestID The ID of the request
  	 * @param conversation The conversation to send to
  	 * @param file The file to send
  	 * @return A {@link ReduxEventAttachmentUpload} for the progress of this upload
  	 */
  	public abstract Observable<ReduxEventAttachmentUpload> sendFile(short requestID, ConversationTarget conversation, File file);
  	
  	/**
  	 * Requests the download of a remote attachment
  	 *
  	 * @param requestID The ID of the request
  	 * @param attachmentGUID The GUID of the attachment to fetch
  	 * @return Whether the request was successful
  	 */
  	public abstract boolean requestAttachmentDownload(short requestID, String attachmentGUID);
  	
  	/**
  	 * Sends a request to fetch conversation information
  	 *
  	 * @param conversations The list of conversation GUIDs to request
  	 * @return Whether the request was successfully sent
  	 */
  	public abstract boolean requestConversationInfo(Collection<String> conversations);
  	
  	/**
  	 * Requests a time range-based message retrieval
  	 *
  	 * @param timeLower The lower time range limit
  	 * @param timeUpper The upper time range limit
  	 * @return Whether the request was successfully sent
  	 */
  	public abstract boolean requestRetrievalTime(long timeLower, long timeUpper);
  	
  	/**
  	 * Requests an ID range-based message retrieval
  	 * @param idLower The ID to retrieve messages beyond (exclusive)
  	 * @param timeLower The lower time range limit
  	 * @param timeUpper The upper time range limit
  	 * @return Whether the request was successfully sent
  	 */
  	public abstract boolean requestRetrievalID(long idLower, long timeLower, long timeUpper);
  	
  	/**
  	 * Requests a mass message retrieval
  	 *
  	 * @param requestID The ID used to validate conflicting requests
  	 * @param params The mass retrieval parameters to use
  	 * @return Whether the request was successfully sent
  	 */
  	public abstract boolean requestRetrievalAll(short requestID, MassRetrievalParams params);
  	
  	/**
  	 * Requests the creation of a new conversation on the server
  	 * @param requestID The ID used to validate conflicting requests
  	 * @param members The participating members' contact addresses for this conversation
  	 * @param service The service that this conversation will use
  	 * @return Whether the request was successfully sent
  	 */
  	public abstract boolean requestChatCreation(short requestID, String[] members, String service);
  	
  	/**
  	 * Updates the server with this installation's push token
  	 * @param token The token to send
  	 * @return Whether the request was successfully sent
  	 */
  	public abstract boolean sendPushToken(String token);
  
  	/**
  	 * Installs the server update with the specified ID
  	 * @param updateID The ID of the update to install
  	 * @return Whether the request was successfully sent
  	 */
  	public abstract boolean installSoftwareUpdate(int updateID);
  
  	/**
  	 * Requests a new FaceTime link
  	 * @return Whether the request was successfully sent
  	 */
  	public abstract boolean requestFaceTimeLink();
  	
  	/**
  	 * Initiates a new outgoing FaceTime call with the specified addresses
  	 * @param addresses The list of addresses to initiate the call with
  	 * @return Whether the request was successfully sent
  	 */
  	public abstract boolean initiateFaceTimeCall(List<String> addresses);
  	
  	/**
  	 * Accepts or rejects a pending incoming FaceTime call
  	 * @param caller The name of the caller to accept or reject the call of
  	 * @param accept True to accept the call, or false to reject
  	 * @return Whether the request was successfully sent
  	 */
  	public abstract boolean handleIncomingFaceTimeCall(@NonNull String caller, boolean accept);
  	
  	/**
  	 * Tells the server to leave the FaceTime call.
  	 * This should be called after the client has connected to the call with the
  	 * FaceTime link to avoid two of the same user connected.
  	 * @return Whether the request was successfully sent
  	 */
  	public abstract boolean dropFaceTimeCallServer();
  	
  	/**
  	 * Checks if the specified communications version is applicable
  	 *
  	 * @param version The major communications version to check
  	 * @return 0 if the version is applicable, -1 if the version is too old, 1 if the version is too new
  	 */
  	public abstract int checkCommVerApplicability(int version);
  	
  	/**
  	 * Checks if the specified proxy is supported by the current protocol
  	 * @param proxyType The proxy type to check
  	 * @return Whether or not this protocol manager can handle the specified feature
  	 */
  	public abstract boolean isProxySupported(@ProxyType int proxyType);
  	
  	/**
  	 * Checks if the specified feature is supported by the current protocol
  	 * @param featureID The feature to check
  	 * @return Whether or not this protocol manager can handle the specified feature
  	 */
  	public abstract boolean isFeatureSupported(@ConnectionFeature int featureID);
  	
  	/**
  	 * Checks if this current communications setup should be kept alive
  	 * This usually entails:
  	 * - Keepalive pings
  	 * - Foreground service
  	 * If persistence is not requested, the app will NOT try to keep the connection alive,
  	 * and it is expected that the connection is re-established when necessary.
  	 * @return Whether persistence should be enabled
  	 */
  	public abstract boolean requiresPersistence();
  	
  	/**
  	 * Gets the active communications version
  	 */
  	public abstract List<Integer> getCommunicationsVersion();
  	
  	/**
  	 * Gets the handler for this communications manager
  	 */
  	public Handler getHandler() {
  		return handler;
  	}
  	
  	/**
  	 * Gets the listener for interfacing with a communications manager
  	 */
  	private CommunicationsManagerListener getListener() {
  		return listener;
  	}
  	
  	/**
  	 * Calls the provided callback function with the listener on the main thread
  	 */
  	public void runListener(Consumer<CommunicationsManagerListener> callback) {
  		handler.post(() -> callback.accept(getListener()));
  	}
  	
  	/**
  	 * Gets the data proxy type
  	 */
  	protected int getDataProxyType() {
  		return dataProxyType;
  	}
  	
  	/**
  	 * Gets the data proxy
  	 */
  	protected DataProxy<Packet> getDataProxy() {
  		return dataProxy;
  	}
  	
  	/**
  	 * Gets whether the current connection is a fallback connection
  	 */
  	public boolean isUsingFallback() {
  		if(dataProxy == null) return false;
  		return dataProxy.isUsingFallback();
  	}
  }
  