package org.to2mbn.jmccc.auth.yggdrasil;

import java.io.Serializable;
import java.util.Objects;
import java.util.UUID;
import org.to2mbn.jmccc.auth.AuthInfo;
import org.to2mbn.jmccc.auth.AuthenticationException;
import org.to2mbn.jmccc.auth.Authenticator;
import org.to2mbn.jmccc.auth.yggdrasil.core.Agent;
import org.to2mbn.jmccc.auth.yggdrasil.core.AuthenticationService;
import org.to2mbn.jmccc.auth.yggdrasil.core.GameProfile;
import org.to2mbn.jmccc.auth.yggdrasil.core.Session;
import org.to2mbn.jmccc.auth.yggdrasil.core.util.UUIDUtils;
import org.to2mbn.jmccc.auth.yggdrasil.core.yggdrasil.YggdrasilAuthenticationService;

public class YggdrasilAuthenticator implements Authenticator, Serializable {

	public static interface PasswordProvider {

		String getUsername() throws AuthenticationException;

		String getPassword() throws AuthenticationException;

		CharacterSelector getCharacterSelector();

	}
	
	public static YggdrasilAuthenticator token(String accessToken, String clientToken) throws AuthenticationException {
		return token(accessToken, new YggdrasilAuthenticationService(clientToken, Agent.MINECRAFT));
	}

	public static YggdrasilAuthenticator token(String accessToken, AuthenticationService service) throws AuthenticationException {
		YggdrasilAuthenticator auth = new YggdrasilAuthenticator(service);
		auth.refreshWithToken(accessToken);
		return auth;
	}

	public static YggdrasilAuthenticator password(String username, String password) throws AuthenticationException {
		return password(username, password, null);
	}
	
	public static YggdrasilAuthenticator password(String username, String password, CharacterSelector characterSelector) throws AuthenticationException {
		return password(username, password, characterSelector, UUIDUtils.unsign(UUID.randomUUID()));
	}

	public static YggdrasilAuthenticator password(String username, String password, CharacterSelector characterSelector, String clientToken) throws AuthenticationException {
		return password(username, password, characterSelector, new YggdrasilAuthenticationService(clientToken, Agent.MINECRAFT));
	}

	public static YggdrasilAuthenticator password(final String username, final String password, final CharacterSelector characterSelector, AuthenticationService service) throws AuthenticationException {
		YggdrasilAuthenticator auth = new YggdrasilAuthenticator(service);
		auth.refreshWithPassword(new PasswordProvider() {

			@Override
			public String getUsername() throws AuthenticationException {
				return username;
			}

			@Override
			public String getPassword() throws AuthenticationException {
				return password;
			}

			@Override
			public CharacterSelector getCharacterSelector() {
				return characterSelector;
			}
		});
		return auth;
	}

	private static final long serialVersionUID = 1L;

	private AuthenticationService authenticationService;
	private volatile Session authResult;

	public YggdrasilAuthenticator() {
		this(UUIDUtils.unsign(UUID.randomUUID()));
	}

	public YggdrasilAuthenticator(String clientToken) {
		this(new YggdrasilAuthenticationService(clientToken, Agent.MINECRAFT));
	}

	public YggdrasilAuthenticator(AuthenticationService sessionService) {
		Objects.requireNonNull(sessionService);
		this.authenticationService = sessionService;
	}

	@Override
	public synchronized AuthInfo auth() throws AuthenticationException {
		GameProfile selectedProfile = session().getSelectedProfile();
		if (selectedProfile == null) {
			throw new AuthenticationException("no profile is available");
		}
		return new AuthInfo(selectedProfile.getName(), authResult.getAccessToken(), UUIDUtils.unsign(selectedProfile.getUUID()), authResult.getProperties(), authResult.getUserType().getName());
	}

	public synchronized Session session() throws AuthenticationException {
		if (authResult == null || !authenticationService.validate(authResult.getAccessToken())) {
			refresh();
		}
		if (authResult == null) {
			throw new AuthenticationException("no authentication is available");
		}
		return authResult;
	}

	public synchronized void refresh() throws AuthenticationException {
		if (authResult == null) {
			// refresh operation is not available
			PasswordProvider passwordProvider = tryPasswordLogin();
			if (passwordProvider == null) {
				throw new AuthenticationException("no more authentication methods to try");
			}
			refreshWithPassword(passwordProvider);
		} else {
			try {
				refreshWithToken(authResult.getAccessToken());
			} catch (AuthenticationException e) {
				// token login failed
				PasswordProvider passwordProvider = tryPasswordLogin();
				if (passwordProvider == null) {
					throw e;
				}

				try {
					refreshWithPassword(passwordProvider);
				} catch (AuthenticationException e1) {
					e1.addSuppressed(e);
					throw e1;
				}
			}
		}
	}

	public synchronized void refreshWithPassword(PasswordProvider passwordProvider) throws AuthenticationException {
		Objects.requireNonNull(passwordProvider);
		authResult = authenticationService.login(passwordProvider.getUsername(), passwordProvider.getPassword());
		if (authResult.getSelectedProfile() == null) {
			// no profile is selected
			// let's select one
			CharacterSelector selector = passwordProvider.getCharacterSelector();
			if (selector == null) {
				selector = new DefaultCharacterSelector();
			}
			GameProfile[] profiles = authResult.getProfiles();
			if (profiles == null || profiles.length == 0) {
				throw new AuthenticationException("no profile is available");
			}
			GameProfile selectedProfile = selector.select(profiles);
			authResult = authenticationService.selectProfile(authResult.getAccessToken(), selectedProfile.getUUID());
		}
	}

	public synchronized void refreshWithToken(String accessToken) throws AuthenticationException {
		Objects.requireNonNull(accessToken);
		authResult = authenticationService.refresh(accessToken);
	}

	public synchronized void clearToken() {
		authResult = null;
	}

	public synchronized Session getCurrentSession() {
		return authResult;
	}

	public synchronized void setCurrentSession(Session session) {
		this.authResult = session;
	}

	public AuthenticationService getAuthenticationService() {
		return authenticationService;
	}

	protected PasswordProvider tryPasswordLogin() throws AuthenticationException {
		return null;
	}

}

@@ -60,24 +60,76 @@
 rializable;%0A
+import java.util.Collections;%0Aimport java.util.Map;%0A
 import java.
@@ -3563,32 +3563,169 @@
 vailable%22);%0A%09%09%7D%0A
+%0A%09%09Map%3CString, String%3E properties = authResult.getProperties();%0A%09%09if (properties == null) %7B%0A%09%09%09properties = Collections.emptyMap();%0A%09%09%7D%0A%0A
 %09%09return new Aut
@@ -3831,31 +3831,17 @@
 UID()), 
-authResult.getP
+p
 ropertie
@@ -3841,18 +3841,16 @@
 operties
-()
 , authRe
@@ -5130,49 +5130,26 @@
 ;%0A%09%09
-authResult = authenticationService.login(
+String username = 
 pass
@@ -5174,17 +5174,37 @@
 ername()
-,
+;%0A%09%09String password =
  passwor
@@ -5226,16 +5226,79 @@
 ssword()
+;%0A%09%09authResult = authenticationService.login(username, password
 );%0A%09%09if 

add null checks for authResult.getProperties()