package ca.corefacility.bioinformatics.irida.web.controller.api.exception;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.DefaultThrowableAnalyzer;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.web.util.ThrowableAnalyzer;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

/**
 * Produces a more helpful error message than
 * "An Authentication object was not found in the SecurityContext" when a client
 * attempts to connect without a token.
 * 
 *
 */
public class CustomOAuth2ExceptionTranslator extends DefaultWebResponseExceptionTranslator {
	private static final Logger logger = LoggerFactory.getLogger(CustomOAuth2ExceptionTranslator.class);

	/** collection of exceptions handled by this class */
	private static final List<Class<? extends RuntimeException>> EXCEPTIONS_HANDLED = ImmutableList.of(
			AuthenticationException.class, InvalidGrantException.class);

	/** map of exception types to a human-readble message for the client */
	private static final Map<Class<? extends Exception>, String> EXCEPTION_MESSAGES = ImmutableMap
			.of(AuthenticationException.class,
					"No client credentials were provided. You must get an access token from the oauth provider at /oauth/token.",
					InvalidGrantException.class,
					"The credentials you provided were invalid. Please provide valid credentials and try again.");

	private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();

	@Override
	public ResponseEntity<OAuth2Exception> translate(Exception ex) throws Exception {
		Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);

		Optional<Throwable> exceptionHandled = EXCEPTIONS_HANDLED.stream()
				.map(c -> throwableAnalyzer.getFirstThrowableOfType(c, causeChain)).filter(Objects::nonNull)
				.findFirst();

		// defer to the default behaviour when dealing with anything
		// except for an unauthorized response.
		if (!exceptionHandled.isPresent()) {
			logger.debug("Passing exception " + ex + " to super to handle.");
			return super.translate(ex);
		}

		// handle the unauthorized request.
		OAuth2Exception e = new OAuth2Exception(EXCEPTION_MESSAGES.get(exceptionHandled.get()), ex);

		HttpHeaders headers = new HttpHeaders();
		headers.set("Cache-Control", "no-store");
		headers.set("Pragma", "no-cache");
		headers.set("WWW-Authenticate", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, e.getSummary()));

		ResponseEntity<OAuth2Exception> response = new ResponseEntity<OAuth2Exception>(e, headers,
				HttpStatus.valueOf(401));

		return response;
	}
}

@@ -90,60 +90,11 @@
 til.
-List;%0Aimport java.util.Map;%0Aimport java.util.Objects
+Map
 ;%0Aim
@@ -852,56 +852,8 @@
 r;%0A%0A
-import com.google.common.collect.ImmutableList;%0A
 impo
@@ -1278,229 +1278,8 @@
 );%0A%0A
-%09/** collection of exceptions handled by this class */%0A%09private static final List%3CClass%3C? extends RuntimeException%3E%3E EXCEPTIONS_HANDLED = ImmutableList.of(%0A%09%09%09AuthenticationException.class, InvalidGrantException.class);%0A%0A
 %09/**
@@ -1580,16 +1580,20 @@
 ider at 
+/api
 /oauth/t
@@ -1911,16 +1911,22 @@
 ion %7B%0A%09%09
+final 
 Throwabl
@@ -1988,16 +1988,22 @@
 ex);%0A%0A%09%09
+final 
 Optional
@@ -2007,74 +2007,124 @@
 nal%3C
-Throwable%3E exceptionHandled = EXCEPTIONS_HANDLED.stream()%0A%09%09%09%09.map
+Map.Entry%3CClass%3C? extends Exception%3E, String%3E%3E exceptionHandled = EXCEPTION_MESSAGES.entrySet()%0A%09%09%09%09.stream().filter
 (c -
@@ -2168,16 +2168,25 @@
 OfType(c
+.getKey()
 , causeC
@@ -2194,30 +2194,13 @@
 ain)
-).filter(Objects::nonN
+ != n
 ull)
@@ -2209,21 +2209,19 @@
 %09%09%09.find
-First
+Any
 ();%0A%0A%09%09/
@@ -2506,16 +2506,22 @@
 uest.%0A%09%09
+final 
 OAuth2Ex
@@ -2556,31 +2556,8 @@
 ion(
-EXCEPTION_MESSAGES.get(
 exce
@@ -2574,16 +2574,26 @@
 ed.get()
+.getValue(
 ), ex);%0A
@@ -2595,16 +2595,22 @@
 ex);%0A%0A%09%09
+final 
 HttpHead
@@ -2836,50 +2836,14 @@
 %0A%0A%09%09
-ResponseEntity%3COAuth2Exception%3E response =
+return
  new
@@ -2886,21 +2886,17 @@
 headers,
-%0A%09%09%09%09
+ 
 HttpStat
@@ -2916,28 +2916,8 @@
 1));
-%0A%0A%09%09return response;
 %0A%09%7D%0A

Clean up the exception translator so we don't have multiple objects to lookup exception messages.