package fr.insa.clavardator.client.network;

import fr.insa.clavardator.client.users.CurrentUser;
import fr.insa.clavardator.lib.errors.UsernameTakenException;
import fr.insa.clavardator.lib.network.TcpConnection;
import fr.insa.clavardator.lib.users.UserInformation;
import fr.insa.clavardator.lib.util.ErrorCallback;
import fr.insa.clavardator.lib.util.Log;

import java.net.InetAddress;
import java.net.Socket;

public class PeerHandshake {

	private TcpConnection connection;
	private UserInformation userInformation;

	public void createConnection(InetAddress ipAddr, int port, UserConnectedCallback callback, ErrorCallback errorCallback) {
		closeConnection();
		Log.v(this.getClass().getSimpleName(), "Creating new TCP connection ");

		connection = new TcpConnection(
				ipAddr,
				port,
				(thisConnection) -> init(thisConnection,
						false,
						callback,
						errorCallback),
				e -> {
					Log.e(this.getClass().getSimpleName(), "Could not create TCP connection", e);
					closeConnection();
					errorCallback.onError(e);
				});
	}

	public void acceptConnection(Socket socket, UserConnectedCallback callback, ErrorCallback errorCallback) {
		closeConnection();
		connection = new TcpConnection(socket);
		init(connection, true, callback, errorCallback);
	}

	private void init(TcpConnection thisConnection, boolean isReceiving, UserConnectedCallback callback, ErrorCallback errorCallback) {
		// Send our username
		thisConnection.send(new UserInformation(CurrentUser.getInstance()), null, e -> {
			closeConnection();
			errorCallback.onError(e);
		});

		// Receive peer's username
		thisConnection.receiveOne(msg -> {
			if (msg instanceof UserInformation) {
				UserInformation userInfo = (UserInformation) msg;
				final String receivedUsername = userInfo.getUsername();
				if (!receivedUsername.equals(CurrentUser.getInstance().getUsername())) {
					this.userInformation = userInfo;
					callback.onUserConnected(this);
				} else if (isReceiving) {
					sendUsernameTaken(thisConnection);
				} else {
					closeConnection();
					errorCallback.onError(new Exception("Tried to use same username as remote"));
				}
			} else {
				closeConnection();
				errorCallback.onError(new Exception("Did not receive remote username"));
			}
		}, e -> {
			closeConnection();
			errorCallback.onError(e);
		});
	}

	private void sendUsernameTaken(TcpConnection thisConnection) {
		Log.v(this.getClass().getSimpleName(), "Received username request using current username");
		UsernameTakenException exception = new UsernameTakenException("Username taken",
				CurrentUser.getInstance().getId(), userInformation.id);
		thisConnection.send(exception, this::closeConnection, null);
	}


	/**
	 * Close the connection to this user
	 */
	private void closeConnection() {
		if (connection != null && connection.isOpen()) {
			connection.close();
			connection = null;
		}
	}

	public TcpConnection getConnection() {
		return connection;
	}

	public UserInformation getUserInformation() {
		return userInformation;
	}

	public interface UserConnectedCallback {
		void onUserConnected(PeerHandshake handshake);
	}
}
