/********************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ********************************************************************/
package gov.va.med.fw.security;

import gov.va.med.fw.model.UserPrincipalImpl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.TimeZone;

import javax.security.auth.login.LoginException;
import javax.servlet.http.HttpServletRequest;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;

/**
 * Helper class to provide SecurityContext related methods. This class provides
 * methods to load the SecurityContext from a "well known place". For web, well
 * known place is HttpSession, it returns the SecurityContext stored in the
 * HttpSession at the time of logon. For non-web, it creates the SecurityContext
 * from Authentication Object stored in a ThreadLocal bound variable.
 * 
 * @author DNS   MANSOG
 * @date May 5, 2005
 */
public class SecurityContextHelper {

	/**
	 * Find and return the SecurityContext from a well known location, a
	 * ThreadLocal bound variable in this case.
	 * 
	 * @return SecurityContext if exists
	 * @throws IllegalStateException
	 *             if user is not logged in and there is no information stored
	 *             in a well know location.
	 */
	public static SecurityContext getSecurityContext() {
		if (SecurityContextHolder.getContext() == null
				|| !(SecurityContextHolder.getContext() instanceof org.springframework.security.core.context.SecurityContext))
			throw new IllegalStateException("SecurityContext invalid: ["
					+ SecurityContextHolder.getContext() + " context]. Probably not yet logged in.");

		org.springframework.security.core.context.SecurityContext sc = (org.springframework.security.core.context.SecurityContext) SecurityContextHolder
				.getContext();
		Authentication authToken = sc.getAuthentication();
		if (authToken != null) {
			if (authToken instanceof AuthenticationObject) {
				AuthenticationObject auth = (AuthenticationObject) authToken;
				if (!auth.isLoggedIn()) {
					throw new IllegalStateException("SecurityContext invalid: Not yet logged in");
				}
				auth.getUserPrincipal().getUserCredentials().setVerified(true);
				return new SecurityContext(auth.getUserPrincipal(), 
							auth.getAuthorities().toArray(new GrantedAuthority[auth.getAuthorities().size()]), 
							auth
						.getDetails());
			}

			// seems like anonymous authentication. make sure check the
			// getPrincipal() since it can be:
			// 1) String if acegi created the Authentication object for us
			// 2) UserPrincipal if ESR creates the Authentication object via
			// LoginManager.loginAnonymous()
			Object prin = authToken.getPrincipal();
			if (prin instanceof String) {
				prin = new UserPrincipalImpl((String) prin);
				((UserPrincipalImpl) prin).setAnonymous(true);
			}
			return new SecurityContext((UserPrincipal) prin, authToken.getAuthorities().toArray(new GrantedAuthority[authToken.getAuthorities().size()]),
					null);
		}
		// user has not logged in yet return empty security context
		return new SecurityContext(new UserPrincipalImpl(), null, null);
	}

	/**
	 * Get the SecurityContext from HttpServletRequest.
	 * 
	 * @param request
	 * @return SecurityContext
	 */
	public static SecurityContext getSecurityContext(HttpServletRequest request) {
		// as getSecurityContext should always return the same result,
		// therefore not loading from the request.
		// To get from request, may be need to refactor SecureAction class
		// and move the code to put/get from session in the common place.
		return getSecurityContext();
	}

	public static UserPrincipal getCurrentUser() {
		if (SecurityContextHolder.getContext() == null
				|| !(SecurityContextHolder.getContext() instanceof org.springframework.security.core.context.SecurityContext))
			throw new IllegalStateException("SecurityContext invalid: ["
					+ SecurityContextHolder.getContext() + " context]. Probably not yet logged in.");

		org.springframework.security.core.context.SecurityContext sc = (org.springframework.security.core.context.SecurityContext) SecurityContextHolder
				.getContext();
		Authentication authToken = sc.getAuthentication();
		if (authToken != null) {
			if (authToken instanceof AuthenticationObject) {
				AuthenticationObject auth = (AuthenticationObject) authToken;
				if (!auth.isLoggedIn()) {
					throw new IllegalStateException("SecurityContext invalid: Not yet logged in");
				}
				auth.getUserPrincipal().getUserCredentials().setVerified(true);
				return auth.getUserPrincipal();
			}

			// seems like anonymous authentication. make sure check the
			// getPrincipal() since it can be:
			// 1) String if acegi created the Authentication object for us
			// 2) UserPrincipal if ESR creates the Authentication object via
			// LoginManager.loginAnonymous()
			Object prin = authToken.getPrincipal();
			if (prin instanceof String) {
				prin = new UserPrincipalImpl((String) prin);
			}
			return (UserPrincipal) prin;
		}
		// user has not logged in yet
		return null;
	}

	/**
	 * Return the user name or logical id of the given user. if logical id
	 * exists, its returned otherwise username is returned.
	 * 
	 * @param userPrincipal
	 * @return
	 */
	public static String getUserName(UserPrincipal userPrincipal) {
		if (userPrincipal != null) {
			return getUserName(userPrincipal.getUserCredentials());
		}
		return null;
	}

	public static String getUserTimeZone(UserPrincipal userPrincipal) {
		if (userPrincipal != null && userPrincipal.getCurrentTimeZone() != null) {
			return userPrincipal.getCurrentTimeZone().getID();
		}
		return null;
	}

	public static String getUserName(UserCredentials cred) {
		if (cred != null) {
			return cred.getLogicalID() != null ? cred.getLogicalID() : cred.getUserID();
		}
		return null;
	}

	public static String getUserName() {
		SecurityContext context = getSecurityContext();
		UserPrincipal prin = context != null ? context.getUserPrincipal() : null;
		return getUserName(prin);
	}

	public static String getUserTimeZone() {
		SecurityContext context = getSecurityContext();
		UserPrincipal prin = context != null ? context.getUserPrincipal() : null;
		return getUserTimeZone(prin);
	}

	/**
	 * Get the list of permissions grated for the current user
	 * 
	 * @return
	 */
	public static List<String> getGrantedPermissions() {
		org.springframework.security.core.context.SecurityContext context = (org.springframework.security.core.context.SecurityContext) SecurityContextHolder
				.getContext();

		if (null == context)
			return Collections.emptyList();

		Authentication currentUser = context.getAuthentication();

		if (currentUser == null || currentUser.getAuthorities() == null
				|| currentUser.getAuthorities().size() < 1)
			return Collections.emptyList();

		Collection authorities = currentUser.getAuthorities();

		List<String> permissions = new ArrayList<String>();
		
		permissions.addAll(authorities);

		return permissions;
	}

	public static boolean isAnyPermissionGranted(String[] permissions) {
		List<String> list = new ArrayList<String>(permissions.length);
		for (int i = 0; i < permissions.length; i++)
			list.add(permissions[i]);
		return isAnyPermissionGranted(list);
	}

	public static boolean isAnyPermissionGranted(List<String> permissions) {
		List<String> grantedPermissions = getGrantedPermissions();
		grantedPermissions.retainAll(permissions);
		return grantedPermissions.size() > 0 ? true : false;
	}

	public static void initSecurityContextOnThread(LoginManager loginManager, UserCredentials cred)
			throws LoginException {
		initSecurityContextOnThread(loginManager, cred, null);
	}

	public static void initSecurityContextOnThread(LoginManager loginManager, UserCredentials cred,
			TimeZone currentTimeZone) throws LoginException {
		if (cred != null && cred.isAnonymous()) {
			loginManager.loginAnonymous(cred.getLogicalID(), currentTimeZone);
		} else {
			// ensure Context is non-null for this Thread (needed since flushing
			// occurs during below login)
			loginManager.loginAnonymous(null, currentTimeZone);
		}

		// now that context is non-null for this Thread, if cred exists, use it
		if (cred != null && !cred.isAnonymous()) {
			loginManager.login(cred, currentTimeZone);
		}
	}
}