package org.yacos.web.system.session;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;

import org.acegisecurity.Authentication;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.providers.dao.DaoAuthenticationProvider;
import org.acegisecurity.providers.dao.UserCache;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.util.WebUtils;
import org.yacos.core.conferences.Conference;
import org.yacos.core.conferences.IConferenceManager;
import org.yacos.core.users.IUserManager;
import org.yacos.core.users.User;

/**
 * Provides Session related Services
 * @author christiancorsano
 *
 */
public class SessionService {
	private static SessionService instance = new SessionService();

	protected static ServletContext servletContext = null;
	protected static HttpServletRequest request = null;

	private IConferenceManager conferenceManager;
	private IUserManager userManager;

	private User currentUser;
	private Conference currentConference;
	private int nbUserConferences;

	private SessionService(){
	}

	public static SessionService getInstance(){
		if(instance == null){
			instance = new SessionService();
		}
		return instance;
	}

	/**
	 * Init method to be called at filter time
	 * Makes the sessionAttributes available in the request context
	 */
	protected void init(){
		if(request != null && servletContext != null){
			try {
				if(userManager == null || conferenceManager == null){
					Context context = new InitialContext();
					userManager = (IUserManager) context.lookup("UserManagerBean/remote");
					conferenceManager = (IConferenceManager) context.lookup("ConferenceManagerBean/remote");
				}
				// Put the current conference in the request context to make it accessible in JSPs
				// This avoid to put the whole object in the session
				request.setAttribute("currentConference", getCurrentConference());
				// Put the current user in the request context to make it accessible in JSPs
				// This avoid to put the whole object in the session
				request.setAttribute("currentUser", getCurrentUser());
				// Put the current roles for this user
				//request.setAttribute("nbUserConferences",getNbUserConferences());

			} catch (NamingException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	public int getNbUserConferences() {
		if(nbUserConferences == -1)
		{
			setNbUserConferences();
		}

		return nbUserConferences;
	}

	public void setNbUserConferences() {
		int nb = conferenceManager.getConferences(getCurrentUser()).size();
		this.nbUserConferences = nb;
		//request.setAttribute("nbUserConferences", getNbUserConferences());
		request.setAttribute("nbUserConferences", this.nbUserConferences);
	}

	public void setConferenceManager(IConferenceManager conferenceManager) {
		this.conferenceManager = conferenceManager;
	}

	public IConferenceManager getConferenceManager() {
		if(conferenceManager == null){
			Context context;
			try {
				context = new InitialContext();
				conferenceManager = (IConferenceManager) context.lookup("ConferenceManagerBean/remote");
			} catch (NamingException e) {
				e.printStackTrace();
			}
		}
		return conferenceManager;
	}

	public void setUserManager(IUserManager userManager) {
		this.userManager = userManager;
	}

	public IUserManager getUserManager() {
		if(userManager == null){
			Context context;
			try {
				context = new InitialContext();
				userManager = (IUserManager) context.lookup("UserManagerBean/remote");
			} catch (NamingException e) {
				e.printStackTrace();
			}
		}
		return userManager;
	}

	/**
	 * Gets the current conference Id
	 * Additionally, put the current conference object in the request context
	 * with the key "currentConference".
	 * @param request The current HTTP request object, necessary for session operations
	 * @return The current conference id
	 */
	public Integer getCurrentConferenceId() {
		// Retrieve the ID from the session
		Integer confId = (Integer) WebUtils.getSessionAttribute(request, "currentConferenceId");
		return confId;
	}

	/**
	 * Sets the current conference id
	 * @param request The current HTTP request object, necessary for session operations
	 * @param currentConferenceId The id to set as the new current conference
	 */
	public void setCurrentConferenceId(Integer currentConferenceId) {
		WebUtils.setSessionAttribute(request, "currentConferenceId", currentConferenceId);
		currentConference = conferenceManager.getConference(currentConferenceId);
		request.setAttribute("currentConference", currentConference);
		// Refreshing user credentials
		refreshAuthentication();
	}

	/**
	 * Sets the current conference using a conference object
	 * @param request The current HTTP request object, necessary for session operations
	 * @param c The conference object to set as current
	 */
	public void setCurrentConference(Conference c){
		setCurrentConferenceId( c.getId() );
		currentConference = c;
	}

	/**
	 * Gets (if necessary) and returns the current conference of the session
	 * @param request The current HTTP request object, necessary for session operations
	 * @return The conference object
	 */
	public Conference getCurrentConference(){
		Integer id = getCurrentConferenceId();

		// The currentConference can be out to date : force the update
		if(currentConference != null && id != currentConference.getId()){
			currentConference = null;
		}

		if( currentConference == null && id != null ){
			currentConference = conferenceManager.getConference(id);
		}

		if (isUserLogged()) {
			setNbUserConferences();
		}

		return currentConference;
	}

	/**
	 * Gets the current user login from the session
	 * Additionally, puts the current User object in the request context
	 * with the key "currentUser"
	 * @param request The current HTTP request object, necessary for session operations
	 * @return The current user login
	 */
	public String getCurrentUserLogin(){
		if(SecurityContextHolder.getContext().getAuthentication() == null){
			return null;
		}
		// Retrieve the login from the acegi security session
		return SecurityContextHolder.getContext().getAuthentication().getName();
	}

	/**
	 * Get (if necessary) and returns the current user object
	 * @return The current User
	 */
	public User getCurrentUser() {
		// Retrieve the login from the session
		String login = this.getCurrentUserLogin();

		// The user can be out to date : force the update
		if(currentUser != null && (! currentUser.getLogin().equals(login)) ){
			currentUser = null;
		}

		// Check if the user object is already in cache
		if(currentUser == null || (currentUser.getLogin().equals(login)) ){
			// Checks login for validity
			if(login == null || login.equals("")){
				currentUser = null;
			} else {
				// Puts the user object in the cache object
				currentUser = userManager.getUser(login);
			}
		}
		return currentUser;
	}

	/**
	 * Returns whether the user is logged (is there a current User for this session)
	 * @return true if there's a current user in the session, false otherwise
	 */
	public boolean isUserLogged(){
		return getCurrentUser() != null;
	}

	/**
	 * Puts an object in the session
	 * @param name The name of the attribute in the session
	 * @param attribute The attribute object
	 */
	public void setSessionAttribute(String name, Object attribute){
		WebUtils.setSessionAttribute(request, name, attribute);
	}

	/**
	 * Retrieves an object from the Session
	 * @param name The name of the attribute to retrieve
	 * @return The attribute object
	 */
	public Object getSessionAttribute(String name){
		return WebUtils.getSessionAttribute(request, name);
	}

	public void refreshAuthentication(){
		refreshAuthentication(null);
	}

	public void refreshAuthentication(String newPassword) {
		// First step : remove user from the cache
		DaoAuthenticationProvider authProvider = (DaoAuthenticationProvider) RequestContextUtils.getWebApplicationContext(request).getBean("daoAuthenticationProvider");
		UserCache userCache = authProvider.getUserCache();

		if(userCache != null){
			userCache.removeUserFromCache(getCurrentUserLogin());
		}

		// Second step : Refreshing user credentials
		Authentication currentAuthentication = SecurityContextHolder.getContext().getAuthentication();
		if(currentAuthentication instanceof UsernamePasswordAuthenticationToken){

			if(newPassword != null){
				// Setting the new password in the authentication token
				currentAuthentication = new UsernamePasswordAuthenticationToken(currentAuthentication.getPrincipal(),newPassword);
			}

			SecurityContextHolder.getContext().setAuthentication(authProvider.authenticate(currentAuthentication));
		}

	}
}
